haxeui/haxeui-core

View on GitHub
haxe/ui/macros/Macros.hx

Summary

Maintainability
Test Coverage
package haxe.ui.macros;


#if macro
import haxe.macro.ComplexTypeTools;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.ExprTools;
import haxe.macro.TypeTools;
import haxe.ui.core.ComponentClassMap;
import haxe.ui.macros.ComponentMacros.BuildData;
import haxe.ui.macros.ComponentMacros.NamedComponentDescription;
import haxe.ui.macros.ModuleMacros;
import haxe.ui.macros.helpers.ClassBuilder;
import haxe.ui.macros.helpers.CodeBuilder;
import haxe.ui.macros.helpers.CodePos;
import haxe.ui.macros.helpers.FieldBuilder;
import haxe.ui.util.EventInfo;
import haxe.ui.util.RTTI;
import haxe.ui.util.StringUtil;
import haxe.ui.util.TypeConverter;

using StringTools;

#end

@:access(haxe.ui.macros.ComponentMacros)
class Macros {
    #if macro

    macro static function buildEvent():Array<Field> {
        var builder = new ClassBuilder(Context.getBuildFields(), Context.getLocalType(), Context.currentPos());
        #if haxeui_expose_all
        builder.addMeta(":expose");
        #end

        #if macro_times_verbose
        var stopComponentTimer = Context.timer(builder.fullPath);
        #end
        #if haxeui_macro_times
        var stopTimer = Context.timer("build event");
        #end

        for (f in builder.fields) {
            if ((f.access.indexOf(AInline) != -1 || f.access.indexOf(AFinal) != -1) && f.access.indexOf(AStatic) != -1) {
                switch (f.kind) {
                    case FVar(t, e):
                        var eventName = ExprTools.toString(e);
                        eventName = StringTools.replace(eventName, "\"", "");
                        eventName = StringTools.replace(eventName, "'", "");
                        eventName = eventName.toLowerCase();
                        eventName = StringTools.replace(eventName, "eventtype.name(", "");
                        eventName = StringTools.replace(eventName, ")", "");
                        EventInfo.nameToType.set(eventName, builder.fullPath);
                        EventInfo.nameToType.set("on" + eventName, builder.fullPath);
                    case _:
                }
            }
        }

        #if haxeui_macro_times
        stopTimer();
        #end
        #if macro_times_verbose
        stopComponentTimer();
        #end

        return builder.fields;
    }
    
    macro static function build():Array<Field> {
        var builder = new ClassBuilder(Context.getBuildFields(), Context.getLocalType(), Context.currentPos());
        #if haxeui_expose_all
        builder.addMeta(":expose");
        #end

        #if macro_times_verbose
        var stopComponentTimer = Context.timer(builder.fullPath);
        #end
        #if haxeui_macro_times
        var stopTimer = Context.timer("build component");
        #end

        ModuleMacros.loadModules();

        ComponentClassMap.register(builder.name, builder.fullPath);

        if (builder.hasClassMeta(["xml"])) {
            buildFromXmlMeta(builder);
        }

        if (builder.hasClassMeta(["composite"])) {
            buildComposite(builder);
        }

        buildEvents(builder);
        applyProperties(builder);
        
        #if haxeui_macro_times
        stopTimer();
        #end
        #if macro_times_verbose
        stopComponentTimer();
        #end

        return builder.fields;
    }

    static function addConstructor(builder:ClassBuilder) {
        if (builder.ctor == null) {
            builder.addFunction("new", macro { super(); });
        }
    }
    
    static function applyProperties(builder:ClassBuilder) {
        #if haxeui_macro_times
        var stopTimer = Context.timer("apply properties");
        #end

        var propPrefix = builder.fullPath.toLowerCase();
        if (ModuleMacros.properties.exists(propPrefix + ".style")) {
            var styleNames = ModuleMacros.properties.get(propPrefix + ".style");
            var createDefaultsFn = builder.findFunction("createDefaults");
            if (createDefaultsFn == null) {
                createDefaultsFn = builder.addFunction("createDefaults", macro {
                    super.createDefaults();
                }, null, null, [AOverride, APrivate]);
            }
            for (n in styleNames.split(" ")) {
                if (StringTools.trim(n).length == 0) {
                    continue;
                }
                createDefaultsFn.add(macro addClass($v{n}));
            }
        } else {
            for (key in ModuleMacros.properties.keys()) {
                if (key.startsWith(propPrefix)) {
                    var createDefaultsFn = builder.findFunction("createDefaults");
                    if (createDefaultsFn == null) {
                        createDefaultsFn = builder.addFunction("createDefaults", macro {
                            super.createDefaults();
                        }, null, null, [AOverride, APrivate]);
                    }
                    var propName = key.split(".").pop();
                    var propValue = ModuleMacros.properties.get(key);
                    var convertedPropValue = TypeConverter.convertFrom(propValue);
                    createDefaultsFn.add(macro $i{propName} = $v{convertedPropValue});
                }
            }
        }

        #if haxeui_macro_times
        stopTimer();
        #end
    }
    
    static function buildFromXmlMeta(builder:ClassBuilder) {
        #if haxeui_macro_times
        var stopTimer = Context.timer("build from xml meta");
        #end

        #if !haxeui_dont_impose_base_class
        if (builder.hasSuperClass("haxe.ui.core.Component") == false) {
            Context.error("Must have a superclass of haxe.ui.core.Component", Context.currentPos());
        }
        #end

        addConstructor(builder);
        if (builder.ctor == null) {
            Context.error("A class building component must have a constructor", Context.currentPos());
        }

        var xml:String = builder.getClassMetaValue("xml");
        if (xml.indexOf("@:markup") != -1) { // means it was xml without quotes, lets extract and clean it up a little
            xml = xml.replace("@:markup", "").trim();
            xml = xml.substr(1, xml.length - 2);
            #if (haxe_ver <= 4.2)
            xml = xml.replace("\\r", "\r");
            xml = xml.replace("\\n", "\n");
            #end
            xml = xml.replace("\\\"", "\"");
            xml = xml.replace("\\'", "'");
        }
        var codeBuilder = new CodeBuilder();
        var buildData:BuildData = { };
        ComponentMacros.buildComponentFromStringCommon(codeBuilder, xml, buildData, "this", false, builder);

        for (id in buildData.namedComponents.keys()) {
            var safeId:String = StringUtil.capitalizeHyphens(id);
            var info:NamedComponentDescription = buildData.namedComponents.get(id);
            builder.addVar(safeId, TypeTools.toComplexType(Context.getType(info.type)));
            codeBuilder.add(macro
                $i{safeId} = $i{info.generatedVarName}
            );
        }

        for (expr in buildData.bindingExprs) {
            codeBuilder.add(expr);
        }

        ComponentMacros.buildBindings(codeBuilder, buildData);
        ComponentMacros.buildLanguageBindings(codeBuilder, buildData);
        // TODO: namespace shouldnt always be default
        ComponentClassMap.register("urn::haxeui::org/" + builder.name, builder.fullPath);
        
        builder.ctor.add(codeBuilder, AfterSuper);

        #if haxeui_macro_times
        stopTimer();
        #end
    }

    static function buildComposite(builder:ClassBuilder) {
        #if haxeui_macro_times
        var stopTimer = Context.timer("build composite");
        #end

        var registerCompositeFn = builder.findFunction("registerComposite");
        if (registerCompositeFn == null) {
            registerCompositeFn = builder.addFunction("registerComposite", macro {
                super.registerComposite();
            }, [APrivate, AOverride]);
        }

        for (param in builder.getClassMetaValues("composite")) {
            // probably a better way to do this
            if (Std.string(param).indexOf("Event") != -1) {
                registerCompositeFn.add(macro
                    _internalEventsClass = $p{param.split(".")}
                );
            } else if (Std.string(param).indexOf("Builder") != -1) {
                registerCompositeFn.add(macro
                    _compositeBuilderClass = $p{param.split(".")}
                );
            } else if (Std.string(param).indexOf("Layout") != -1) {
                registerCompositeFn.add(macro
                    _defaultLayoutClass = $p{param.split(".")}
                );
            }
        }

        #if haxeui_macro_times
        stopTimer();
        #end
    }

    static function buildEvents(builder:ClassBuilder) {
        #if haxeui_macro_times
        var stopTimer = Context.timer("build events");
        #end

        for (f in builder.getFieldsWithMeta("event")) {
            f.remove();
            if (builder.hasFieldSuper(f.name)) {
                continue;
            }
            var eventExpr = f.getMetaValueExpr("event");
            var varName = '_internal__${f.name}';
            builder.addVar(varName, f.type, null, null, [{name: ":noCompletion", pos: Context.currentPos()}]);
            var setter = builder.addSetter(f.name, f.type, macro {
                if ($i{varName} != null) {
                    unregisterEvent($e{eventExpr}, $i{varName});
                    $i{varName} = null;
                }
                if (value != null) {
                    $i{varName} = value;
                    registerEvent($e{eventExpr}, value);
                }
                return value;
            });
            setter.addMeta(":dox", [macro group = "Event related properties and methods"]);
        }

        #if haxeui_macro_times
        stopTimer();
        #end
    }

    static function buildStyles(builder:ClassBuilder) {
        #if haxeui_macro_times
        var stopTimer = Context.timer("build styles");
        #end

        for (f in builder.getFieldsWithMeta("style")) {
            f.remove();
            RTTI.addClassProperty(builder.fullPath, f.name, ComplexTypeTools.toString(f.type));

            var defaultValue:Dynamic = null;
            if (f.isNumeric == true) {
                defaultValue = 0;
            } else if (f.isBool == true) {
                defaultValue = false;
            }

            var getter = builder.addGetter(f.name, f.type, macro {
                if ($p{["customStyle", f.name]} != null) {
                    return $p{["customStyle", f.name]};
                }
                if (style == null || $p{["style", f.name]} == null) {
                    return $v{defaultValue};
                }
                return $p{["style", f.name]};
            });
            getter.addMeta(":style");
            getter.addMeta(":keep");
            //getter.addMeta(":clonable");
            getter.addMeta(":dox", [macro group = "Style properties"]);

            var codeBuilder = new CodeBuilder(macro {
                if ($p{["customStyle", f.name]} == value) {
                    return value;
                }
                if (_style == null) {
                    _style = {};
                }
                if (value == null) {
                    $p{["customStyle", f.name]} = null;
                } else {
                    $p{["customStyle", f.name]} = value;
                }
                return value;
            });
            
            if (f.name == "borderColor") {
                codeBuilder.add(macro {
                    customStyle.borderTopColor = value;
                    customStyle.borderLeftColor = value;
                    customStyle.borderBottomColor = value;
                    customStyle.borderRightColor = value;
                });
            } else if (f.name == "borderSize") {
                codeBuilder.add(macro {
                    customStyle.borderTopSize = value;
                    customStyle.borderLeftSize = value;
                    customStyle.borderBottomSize = value;
                    customStyle.borderRightSize = value;
                });
            }
            codeBuilder.add(macro invalidateComponentStyle());
            
            if (f.hasMetaParam("style", "layout")) {
                codeBuilder.add(macro
                    invalidateComponentLayout()
                );
            }
            if (f.hasMetaParam("style", "layoutparent")) {
                codeBuilder.add(macro
                    if (parentComponent != null) parentComponent.invalidateComponentLayout()
                );
            }
            builder.addSetter(f.name, f.type, codeBuilder.expr);
        }

        #if haxeui_macro_times
        stopTimer();
        #end
    }

    private static function buildPropertyBinding(builder:ClassBuilder, f:FieldBuilder, variable:Expr, field:String) {
        #if haxeui_macro_times
        var stopTimer = Context.timer("build property binding");
        #end

        var hasGetter = builder.findFunction("get_" + f.name) != null;
        var hasSetter = builder.findFunction("set_" + f.name) != null;

        if (hasGetter == false && hasSetter == false) {
            f.remove();
        }

        var variable = ExprTools.toString(variable);
        if (hasGetter == false) {
            builder.addGetter(f.name, f.type, macro {
                return $i{variable}.$field;
            });
        }

        if (hasSetter == false) {
            builder.addSetter(f.name, f.type, macro {
                $i{variable}.$field = value;
                return value;
            });
        }

        if (f.expr != null) {
            addConstructor(builder);
            builder.ctor.add(macro $i{f.name} = $e{f.expr}, AfterSuper);
        }

        if (hasSetter == true) {
            builder.ctor.add(macro {
                $i{variable}.registerEvent(haxe.ui.events.UIEvent.CHANGE, function(e) {
                    $i{f.name} = Reflect.getProperty($i{variable}, $v{field});
                });
            });
        }

        #if haxeui_macro_times
        stopTimer();
        #end
    }

    static function buildBindings(builder:ClassBuilder) {
        #if haxeui_macro_times
        var stopTimer = Context.timer("build bindings");
        #end

        for (f in builder.getFieldsWithMeta("bindable")) {
            var setFn = builder.findFunction("set_" + f.name);
            RTTI.addClassProperty(builder.fullPath, f.name, ComplexTypeTools.toString(f.type));
        }

        var bindFields = builder.getFieldsWithMeta("bind");
        if (bindFields.length > 0) {
            #if !haxeui_dont_impose_base_class
            if (builder.hasSuperClass("haxe.ui.core.Component") == false) {
                Context.error("Must have a superclass of haxe.ui.core.Component", Context.currentPos());
            }
            #end

            addConstructor(builder);
            if (builder.ctor == null) {
                Context.error("A class building component must have a constructor", Context.currentPos());
            }

            for (f in bindFields) {
                for (n in 0...f.getMetaCount("bind")) { // single method can be bound to multiple events
                    var meta = f.getMetaByIndex("bind", n);
                    switch (meta.params) {
                        case [{expr: EField(variable, field), pos: pos}]: // one param, lets assume binding to component prop
                            buildPropertyBinding(builder, f, variable, field);
                        case [param1]:
                            buildPropertyBinding(builder, f, param1, "value"); // input component that has value
                        case [component, event]: // two params, lets assume event binding
                            var componentString = ExprTools.toString(component);
                            builder.ctor.add(macro @:pos(component.pos) {
                                if ($v{componentString} == "controller") { // TODO: arguably a hack as its sepcific to MVC, contoller might be null by the time we are linking things
                                    haxe.ui.Toolkit.callLater(function() {
                                        var c:haxe.ui.core.IEventDispatcher<haxe.ui.events.UIEvent> = cast ${component};
                                        if (c != null) {
                                            c.registerEvent($event, $i{f.name});
                                        } else {
                                            trace("WARNING: could not find event dispatcher to register event (" + $v{ExprTools.toString(component)} + ")");
                                        }
                                    });
                                } else {
                                    var c:haxe.ui.core.IEventDispatcher<haxe.ui.events.UIEvent> = cast ${component};
                                    if (c != null) {
                                        c.registerEvent($event, $i{f.name});
                                    } else {
                                        trace("WARNING: could not find event dispatcher to register event (" + $v{ExprTools.toString(component)} + ")");
                                    }
                                }
                            }, End);
                        default:
                            haxe.macro.Context.error("Unsupported bind format, expected bind(component.field) or bind(component, event)", meta.pos);
                    }
                }
            }
        }

        #if haxeui_macro_times
        stopTimer();
        #end
    }

    static function buildClonable(builder:ClassBuilder) {
        #if haxeui_macro_times
        var stopTimer = Context.timer("build clonable");
        #end
        
        var useSelf:Bool = (builder.fullPath == "haxe.ui.backend.ComponentBase");

        var cloneFn = builder.findFunction("cloneComponent");
        if (cloneFn == null) { // add new clone fn
            var access:Array<Access> = [APublic];
            if (useSelf == false) {
                access.push(AOverride);
            }
            cloneFn = builder.addFunction("cloneComponent", builder.complexType, access);
        }

        var cloneLineExpr = null;
        var typePath = TypeTools.toComplexType(builder.type);
        if (useSelf == false) {
            cloneLineExpr = macro var c:$typePath = cast super.cloneComponent();
        } else {
            cloneLineExpr = macro var c:$typePath = self();
        }
        cloneFn.add(cloneLineExpr, CodePos.Start);

        var n = 1;
        for (f in builder.getFieldsWithMeta("clonable")) {
            if (f.isNullable == true) {
                cloneFn.add(macro if ($p{["this", f.name]} != null) $p{["c", f.name]} = $p{["this", f.name]}, Pos(n));
            } else {
                cloneFn.add(macro $p{["c", f.name]} = $p{["this", f.name]}, Pos(n));
            }
            n++;
        }
        cloneFn.add(macro {
            if (this.childComponents.length != c.childComponents.length) {
                for (child in this.childComponents) {
                    c.addComponent(child.cloneComponent());
                }
            }
            
            postCloneComponent(cast c);
        });
        cloneFn.add(macro return c);

        var hasOverriddenSelf = (builder.findFunction("self") != null);

        var constructorArgExprs = null;
        if (hasOverriddenSelf == false) {
            var hasConstructorArgs = false;
            var constuctor = builder.findFunction("new");
            if (constuctor != null) {
                hasConstructorArgs = (constuctor.argCount > 0);
                if (hasConstructorArgs == true) {
                    constructorArgExprs = [];
                    for (arg in constuctor.fn.args) {
                        var varName = "_constructorParam_" + arg.name;
                        builder.addVar(varName, arg.type, null, null, [{name: ":noCompletion", pos: Context.currentPos()}]);
                        constructorArgExprs.push(macro this.$varName);
                    }
                }
            }

            // add "self" function
            var access:Array<Access> = [APrivate];
            if (useSelf == false) {
                access.push(AOverride);
            }
            var typePath = builder.typePath;
            if (constructorArgExprs == null) {
                builder.addFunction("self", macro {
                    return new $typePath();
                }, builder.complexType, access);
            } else {
                builder.addFunction("self", macro {
                    return new $typePath($a{constructorArgExprs});
                }, builder.complexType, access);
            }
        }

        #if haxeui_macro_times
        stopTimer();
        #end
    }

    static function buildBehaviours():Array<Field> {
        if (Context.getLocalClass().get().isExtern) {
            return null;
        }

        var builder = new ClassBuilder(haxe.macro.Context.getBuildFields(), Context.getLocalType(), Context.currentPos());
        #if macro_times_verbose
        var stopComponentTimer = Context.timer(builder.fullPath);
        #end
        #if haxeui_macro_times
        var stopTimer = Context.timer("build behaviours");
        #end

        var registerBehavioursFn = builder.findFunction("registerBehaviours");
        if (registerBehavioursFn == null) {
            registerBehavioursFn = builder.addFunction("registerBehaviours", macro {
                super.registerBehaviours();
            }, [APrivate, AOverride]);
        }

        var valueField = builder.getFieldMetaValue("value");
        var resolvedValueField = null;
        var fields = builder.getFieldsWithMeta("behaviour");

        // lets find the value field first, so we know we have it
        for (f in fields) {
            if (f.name == valueField) {
                resolvedValueField = f;
                break;
            }
        }

        for (f in fields) {
            RTTI.addClassProperty(builder.fullPath, f.name, ComplexTypeTools.toString(f.type));
            if (f.name == valueField) {
                RTTI.addClassProperty(builder.fullPath, "value", ComplexTypeTools.toString(f.type));
            }
            
            f.remove();
            var defVal:Dynamic = null;
            if (f.isBool) {
                defVal = false;
            } else if (f.isNumeric) {
                defVal = 0;
            }
            if (builder.hasField(f.name, true) == false) { // check to see if it already exists, possibly in a super class
                var newField:FieldBuilder = null;
                if (f.isDynamic == true) { // add a getter that can return dynamic
                    newField = builder.addGetter(f.name, f.type, macro {
                        if (behaviours == null) {
                            return $v{defVal};
                        }
                        return behaviours.getDynamic($v{f.name});
                    }, f.access);
                } else if (f.isComponent) {
                    newField = builder.addGetter(f.name, f.type, macro {
                        if (behaviours == null) {
                            return $v{defVal};
                        }
                        return cast behaviours.get($v{f.name}).toComponent();
                    }, f.access);
                } else { // add a normal (Variant) getter
                    newField = builder.addGetter(f.name, f.type, macro {
                        if (behaviours == null) {
                            return $v{defVal};
                        }
                        return behaviours.get($v{f.name});
                    }, f.access);
                }

                if (f.name == valueField) {
                    if (f.isDynamic == true) {
                        newField = builder.addSetter(f.name, f.type, macro { // add a normal (Variant) setter but let the binding manager know that the value has changed
                            behaviours.setDynamic($v{f.name}, value);
                            dispatch(new haxe.ui.events.UIEvent(haxe.ui.events.UIEvent.PROPERTY_CHANGE, $v{f.name}));
                            dispatch(new haxe.ui.events.UIEvent(haxe.ui.events.UIEvent.PROPERTY_CHANGE, "value"));
                            return value;
                        }, f.access);
                    } else {
                        newField = builder.addSetter(f.name, f.type, macro { // add a normal (Variant) setter but let the binding manager know that the value has changed
                            behaviours.set($v{f.name}, value);
                            dispatch(new haxe.ui.events.UIEvent(haxe.ui.events.UIEvent.PROPERTY_CHANGE, $v{f.name}));
                            dispatch(new haxe.ui.events.UIEvent(haxe.ui.events.UIEvent.PROPERTY_CHANGE, "value"));
                            return value;
                        }, f.access);
                    }
                    resolvedValueField = newField;
                } else {
                    if (f.isDynamic == true) {
                        newField = builder.addSetter(f.name, f.type, macro { // add a normal (Variant) setter
                            behaviours.setDynamic($v{f.name}, value);
                            dispatch(new haxe.ui.events.UIEvent(haxe.ui.events.UIEvent.PROPERTY_CHANGE, $v{f.name}));
                            return value;
                        }, f.access);
                    } else if (f.isString == true) {
                        newField = builder.addSetter(f.name, f.type, macro { // add a normal (Variant) setter
                            switch (Type.typeof(value)) {
                                case TClass(String):
                                    if (value != null && value.indexOf("{{") != -1 && value.indexOf("}}") != -1) {
                                        haxe.ui.locale.LocaleManager.instance.registerComponent(cast this, $v{f.name}, value);
                                        return value;
                                    }
                                case _:    
                            }
                            if (behaviours == null) {
                                behaviours = new haxe.ui.behaviours.Behaviours(cast this);
                                this.registerBehaviours();
                            }
                            behaviours.set($v{f.name}, value);
                            dispatch(new haxe.ui.events.UIEvent(haxe.ui.events.UIEvent.PROPERTY_CHANGE, $v{f.name}));
                            return value;
                        }, f.access);
                    } else {
                        newField = builder.addSetter(f.name, f.type, macro { // add a normal (Variant) setter
                            if (behaviours == null) {
                                return value;
                            }
                            behaviours.set($v{f.name}, value);
                            dispatch(new haxe.ui.events.UIEvent(haxe.ui.events.UIEvent.PROPERTY_CHANGE, $v{f.name}));
                            return value;
                        }, f.access);
                    }
                }

                newField.doc = f.doc;
                newField.addMeta(":behaviour");
                newField.addMeta(":bindable");
                newField.mergeMeta(f.meta, ["behaviour"]);
            }

            if (f.getMetaValueExpr("behaviour", 1) == null) {
                registerBehavioursFn.add(macro
                    behaviours.register($v{f.name}, $p{f.getMetaValueString("behaviour", 0).split(".")})
                );
            } else {
                registerBehavioursFn.add(macro
                    behaviours.register($v{f.name}, $p{f.getMetaValueString("behaviour", 0).split(".")}, $e{f.getMetaValueExpr("behaviour", 1)})
                );
            }
        }

        for (f in builder.findFunctionsWithMeta("call")) {
            var defVal:Dynamic = null;
            if (f.isBool) {
                defVal = false;
            } else if (f.isNumeric) {
                defVal = 0;
            }
            var arg0 = '${f.getArgName(0)}';
            if (f.isVoid == true) {
                f.set(macro {
                    if (behaviours == null) {
                        return;
                    }
                    behaviours.call($v{f.name}, $i{arg0});
                });
            } else if (f.returnsComponent) { // special case for component calls, we will conver the variant to a component and then cast it
                f.set(macro {
                    return cast behaviours.call($v{f.name}, $i{arg0}).toComponent();
                });
            } else {
                f.set(macro {
                    if (behaviours == null) {
                        return $v{defVal};
                    }
                    return behaviours.call($v{f.name}, $i{arg0});
                });
            }

            if (f.getMetaValueExpr("call", 1) == null) {
                registerBehavioursFn.add(macro
                    behaviours.register($v{f.name}, $p{f.getMetaValueString("call", 0).split(".")})
                );
            } else {
                registerBehavioursFn.add(macro
                    behaviours.register($v{f.name}, $p{f.getMetaValueString("call", 0).split(".")}, $e{f.getMetaValueExpr("call", 1)})
                );
            }
        }

        for (f in builder.getFieldsWithMeta("value")) {
            f.remove();

            var propName = f.getMetaValueString("value");
            if (resolvedValueField != null && (resolvedValueField.isVariant || 
                                               resolvedValueField.isString ||
                                               resolvedValueField.isNumeric ||
                                               resolvedValueField.isBool)) {
                builder.addGetter(f.name, macro: Dynamic, macro {
                    return $i{propName};
                }, false, true);

                if (resolvedValueField.isString) {
                    builder.addSetter(f.name, macro: Dynamic, macro {
                        switch (Type.typeof(value)) {
                            case TEnum(haxe.ui.util.Variant.VariantType):
                                var v:haxe.ui.util.Variant = value;
                                $i{propName} = v;
                            case TInt | TFloat | TBool:
                                $i{propName} = Std.string(value);
                            case _:
                                $i{propName} = value;
                        }

                        return value;
                    }, false, true);
                } else if (resolvedValueField.isFloat) {
                    builder.addSetter(f.name, macro: Dynamic, macro {
                        switch (Type.typeof(value)) {
                            case TEnum(haxe.ui.util.Variant.VariantType):
                                var v:haxe.ui.util.Variant = value;
                                $i{propName} = v;
                            case TClass(String):
                                $i{propName} = Std.parseFloat(value);
                            case TBool:
                                $i{propName} = (value == true) ? 1 : 0;
                            case _:
                                $i{propName} = value;
                        }

                        return value;
                    }, false, true);
                } else if (resolvedValueField.isInt) {
                    builder.addSetter(f.name, macro: Dynamic, macro {
                        switch (Type.typeof(value)) {
                            case TEnum(haxe.ui.util.Variant.VariantType):
                                var v:haxe.ui.util.Variant = value;
                                $i{propName} = v;
                            case TClass(String):
                                $i{propName} = Std.parseInt(value);
                            case TBool:
                                $i{propName} = (value == true) ? 1 : 0;
                            case _:
                                $i{propName} = value;
                        }

                        return value;
                    }, false, true);
                } else if (resolvedValueField.isBool) {
                    builder.addSetter(f.name, macro: Dynamic, macro {
                        switch (Type.typeof(value)) {
                            case TEnum(haxe.ui.util.Variant.VariantType):
                                var v:haxe.ui.util.Variant = value;
                                $i{propName} = v;
                            case TInt | TFloat:
                                $i{propName} = (value == 1);
                            case TClass(String):
                                $i{propName} = (value == "true" || value == "1");
                            case _:
                                $i{propName} = value;
                        }

                        return value;
                    }, false, true);
                } else if (resolvedValueField.isVariant) {
                    builder.addSetter(f.name, macro: Dynamic, macro {
                        $i{propName} = haxe.ui.util.Variant.fromDynamic(value);
                        return value;
                    }, false, true);
                }
            } else {
                builder.addGetter(f.name, macro: Dynamic, macro {
                    return $i{propName};
                }, false, true);

                builder.addSetter(f.name, macro: Dynamic, macro {
                    $i{propName} = value;
                    return value;
                }, false, true);
            }
        }

        // lets set the super class for this class so RTTI works for super classes
        var superClass = builder.superClass;
        if (superClass != null) {
            var superFullPath = superClass.t.get().pack.join(".") + "." + superClass.t.get().name;
            RTTI.setSuperClass(builder.fullPath, superFullPath);
        }
        
        //buildEvents(builder);
        buildStyles(builder);
        buildBindings(builder);

        if (builder.hasInterface("haxe.ui.core.IClonable") && !builder.isAbstractClass) {
            buildClonable(builder);
        }

        RTTI.save();
        
        #if haxeui_macro_times
        stopTimer();
        #end
        #if macro_times_verbose
        stopComponentTimer();
        #end

        return builder.fields;
    }

    static function buildData():Array<Field> {
        var builder = new ClassBuilder(haxe.macro.Context.getBuildFields(), Context.getLocalType(), Context.currentPos());
        builder.addMeta(":keep");
        builder.addMeta(":keepSub");
        #if macro_times_verbose
        var stopComponentTimer = Context.timer(builder.fullPath);
        #end
        #if haxeui_macro_times
        var stopTimer = Context.timer("build behaviours");
        #end

        var constructorArgs:Array<FunctionArg> = [];
        var constructorExprs:Array<Expr> = [];
        for (f in builder.vars) {
            if (f.isStatic) {
                continue;
            }
            var fieldName = f.name;
            builder.removeVar(fieldName);
            var optional = f.hasMeta(":optional");
            constructorArgs.push({name: fieldName, type: f.type, value: f.expr, opt: optional});
            constructorExprs.push(macro this.$fieldName = $i{fieldName});

            var name = '_${fieldName}';
            builder.addVar(name, f.type, null, null, [{name: ":noCompletion", pos: Context.currentPos()}, {name: ":optional", pos: Context.currentPos()}]);
            var newField = builder.addGetter(fieldName, f.type, macro {
                return $i{name};
            });
            var newField = builder.addSetter(fieldName, f.type, macro {
                if (value == $i{name}) {
                    return value;
                }
                $i{name} = value;
                if (onDataSourceChanged != null) {
                    onDataSourceChanged();
                }
                return value;
            });
            newField.addMeta(":isVar");
            //builder.addVar(f.name, f.type, null, null, [{name: ":isVar", pos: Context.currentPos()}]);
        }

        builder.addVar("onDataSourceChanged", macro: Void->Void, macro null);

        if (!builder.hasFunction("new")) {
            builder.addFunction("new", macro {
                $b{constructorExprs}
            }, constructorArgs);
        }


        #if haxeui_macro_times
        stopTimer();
        #end
        #if macro_times_verbose
        stopComponentTimer();
        #end

        return builder.fields;
    }

    #end
}