initial commit
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
||||
bin/
|
||||
dump/
|
||||
out/
|
||||
target/
|
||||
|
||||
*.hxproj
|
||||
.idea
|
||||
*.iml
|
||||
*.ipr
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "bindx2",
|
||||
"url" : "https://github.com/profelis/bindx2",
|
||||
"license": "MIT",
|
||||
"tags": ["bind", "binding", "bindings", "cross"],
|
||||
"description": "Powerful and fast macro-based data binding engine inspired by Flex Bindings with easy-to-use syntax.",
|
||||
"classPath": "src",
|
||||
"version": "2.0.0-alpha",
|
||||
"releasenote": "bindx reification",
|
||||
"contributors": [
|
||||
"deep"
|
||||
]
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package ;
|
||||
class Main {
|
||||
|
||||
static function main() {
|
||||
trace("tada");
|
||||
|
||||
bindx.BindSignal.BindSignalProvider.register();
|
||||
|
||||
var v = new Value();
|
||||
var s = {t:0};
|
||||
|
||||
bindx.Bind.bindx(v.str, function (from, to) {trace('str changed from $from to $to');});
|
||||
v.strChanged.add(function (from, to) {trace('str changed from $from to $to');});
|
||||
v.str = "12";
|
||||
bindx.Bind.bindx(v.int, function (a, b) { trace(b); });
|
||||
var unbind = bindx.Bind.bindTo(v.int, s.t);
|
||||
v.int = 10;
|
||||
trace(s.t);
|
||||
unbind();
|
||||
v.int = 12;
|
||||
trace(s.t);
|
||||
trace(v.str);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@:bindable
|
||||
class Value implements bindx.IBindable {
|
||||
|
||||
public function new() {
|
||||
}
|
||||
|
||||
@:bindable(lazySignal=true, inlineSignalGetter=false, inlineSetter=true)
|
||||
public var str:String;
|
||||
|
||||
@:bindable(force=true, inlineSetter=true)
|
||||
public var int(default, set):Int;
|
||||
|
||||
private var noBindPrivate:Int;
|
||||
|
||||
function set_int(v):Int {
|
||||
|
||||
if (v < 0) {
|
||||
int = 0;
|
||||
toStringChanged.dispatch();
|
||||
intChanged.dispatch(v, int);
|
||||
return int;
|
||||
}
|
||||
|
||||
intChanged.dispatch(int, int = v);
|
||||
toStringChanged.dispatch();
|
||||
return v;
|
||||
}
|
||||
|
||||
@:bindable()
|
||||
public function toString() {
|
||||
return str + int;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package bindx;
|
||||
|
||||
#if macro
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
import haxe.macro.Context;
|
||||
|
||||
using haxe.macro.Tools;
|
||||
using bindx.MetaUtils;
|
||||
#end
|
||||
using Lambda;
|
||||
|
||||
class Bind {
|
||||
|
||||
@:noUsing macro static public function bindx(field:Expr, listener:Expr):Expr {
|
||||
return bind(field, listener, true);
|
||||
}
|
||||
|
||||
@:noUsing macro static public function bindTo(field:Expr, target:Expr):Expr {
|
||||
var fieldData = checkField(field);
|
||||
return BindMacros.bindingSignalProvider.getClassFieldBindToExpr(fieldData.e, fieldData.field, target);
|
||||
}
|
||||
|
||||
@:noUsing macro static public function unbindx(field:Expr, listener:Expr):Expr {
|
||||
return bind(field, listener, false);
|
||||
}
|
||||
|
||||
@:noUsing macro static public function notify(field:Expr, ?oldValue:Expr, ?newValue:Expr):Expr {
|
||||
var fieldData = checkField(field);
|
||||
return BindMacros.bindingSignalProvider.getClassFieldChangedExpr(fieldData.e, fieldData.field, oldValue, newValue);
|
||||
}
|
||||
|
||||
#if macro
|
||||
static function bind(field:Expr, listener:Expr, doBind:Bool):Expr {
|
||||
var fieldData = checkField(field);
|
||||
return if (fieldData != null) {
|
||||
if (doBind) BindMacros.bindingSignalProvider.getClassFieldBindExpr(fieldData.e, fieldData.field, listener);
|
||||
else BindMacros.bindingSignalProvider.getClassFieldUnbindExpr(fieldData.e, fieldData.field, listener);
|
||||
} else macro {};
|
||||
}
|
||||
|
||||
static function checkField(field:Expr):{e:Expr, field:ClassField} {
|
||||
switch (field.expr) {
|
||||
case EField(e, field):
|
||||
var classType = Context.typeof(e).getClass();
|
||||
if (classType == null || !isBindable(classType)) {
|
||||
Context.error('\'${e.toString()}\' must be bindx.IBindable', e.pos);
|
||||
return null;
|
||||
}
|
||||
|
||||
var field:ClassField = classType.findField(field, null);
|
||||
if (field == null) {
|
||||
Context.error('\'${e.toString()}.$field\' expected', field.pos);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!field.hasBindableMeta()) {
|
||||
Context.error('\'${e.toString()}.$field\' is not bindable', field.pos);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {e:e, field:field};
|
||||
|
||||
case EConst(CIdent(_)):
|
||||
Context.error('can\'t bind \'${field.toString()}\'. Please use \'this.${field.toString()}\'', field.pos);
|
||||
|
||||
case _:
|
||||
Context.error('can\'t bind field \'${field.toString()}\'', field.pos);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static inline function isBindable(classType:ClassType) {
|
||||
return classType.interfaces.exists(function (it) {
|
||||
var t = it.t.get();
|
||||
return t.module == "bindx.IBindable" && t.name == "IBindable";
|
||||
});
|
||||
}
|
||||
#end
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package bindx;
|
||||
|
||||
import haxe.macro.Type;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
|
||||
using haxe.macro.Tools;
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
using bindx.MetaUtils;
|
||||
|
||||
class BindMacros {
|
||||
#if macro
|
||||
static inline var OLD_VALUE = "__oldValue__";
|
||||
static inline var NEW_VALUE = "__newValue__";
|
||||
|
||||
/**
|
||||
* default value: false
|
||||
*/
|
||||
static public inline var INLINE_SETTER = "inlineSetter";
|
||||
/**
|
||||
* default value: false
|
||||
*/
|
||||
static public inline var FORCE = "force";
|
||||
|
||||
static var processed:Array<Type> = [];
|
||||
|
||||
static public var bindingSignalProvider:IBindingSignalProvider;
|
||||
|
||||
static public function setBindingSignalProvider(value:IBindingSignalProvider) {
|
||||
bindingSignalProvider = value;
|
||||
return macro {};
|
||||
}
|
||||
|
||||
macro static public function buildIBindable():Array<Field> {
|
||||
var type = Context.getLocalType();
|
||||
if (processed.indexOf(type) > -1) {
|
||||
return null;
|
||||
}
|
||||
processed.push(type);
|
||||
if (bindingSignalProvider == null) {
|
||||
bindingSignalProvider = new bindx.BindSignal.BindSignalProvider();
|
||||
}
|
||||
|
||||
var classType = type.getClass();
|
||||
var fields = Context.getBuildFields();
|
||||
|
||||
var meta = classType.bindableMeta();
|
||||
if (meta != null) injectBindableMeta(fields, meta);
|
||||
|
||||
var res = [];
|
||||
for (f in fields)
|
||||
if (f.hasBindableMeta()) {
|
||||
if (!isFieldBindable(f, fields)) Context.error('can\'t bind field \'${f.name}\'', f.pos);
|
||||
|
||||
bindField(f, fields, res);
|
||||
} else res.push(f);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static function bindField(field:Field, fields:Array<Field>, res:Array<Field>) {
|
||||
var meta = field.bindableMeta();
|
||||
bindingSignalProvider.getFieldDispatcher(field, res);
|
||||
|
||||
var forceParam = meta.findParam(FORCE);
|
||||
var inlineSetter = meta.findParam(INLINE_SETTER);
|
||||
if (forceParam.isNotNullAndTrue()) {
|
||||
if (inlineSetter != null)
|
||||
Context.warning('\'$INLINE_SETTER\' ingored. \'$FORCE\' mode', inlineSetter.pos);
|
||||
res.push(field);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (field.kind) {
|
||||
case FVar(type, expr):
|
||||
var fieldName = field.name;
|
||||
var setterName = 'set_$fieldName';
|
||||
field.kind = FProp("default", "set", type, expr);
|
||||
res.push(field);
|
||||
var setter = macro function foo(value:$type) {
|
||||
var $OLD_VALUE = this.$fieldName;
|
||||
if ($i{OLD_VALUE} == value) return $i{OLD_VALUE};
|
||||
this.$fieldName = value;
|
||||
${bindingSignalProvider.getFieldChangedExpr(field, macro $i{OLD_VALUE}, macro $i{"value"})}
|
||||
return value;
|
||||
};
|
||||
var setterAccess = [APrivate];
|
||||
if (inlineSetter.isNotNullAndTrue()) setterAccess.push(AInline);
|
||||
res.push({
|
||||
name: setterName,
|
||||
kind: FFun(switch (setter.expr) { case EFunction (_, func): func; case _: throw false; }),
|
||||
pos: field.pos,
|
||||
access: setterAccess
|
||||
});
|
||||
|
||||
case FProp(get, set, type, expr):
|
||||
if (inlineSetter != null)
|
||||
Context.warning('$INLINE_SETTER ingored. Setter already exist', inlineSetter.pos);
|
||||
var fieldName = field.name;
|
||||
var setter = fields.find(function (it) return it.name == 'set_$fieldName');
|
||||
if (setter == null) return;
|
||||
switch (setter.kind) {
|
||||
case FFun(func):
|
||||
patchField = field;
|
||||
func.expr = macro {
|
||||
var $OLD_VALUE = this.$fieldName;
|
||||
if ($i{OLD_VALUE} == $i{func.args[0].name}) return $i{OLD_VALUE};
|
||||
$e{func.expr.map(patchSetter)};
|
||||
};
|
||||
patchField = null;
|
||||
case _:
|
||||
|
||||
}
|
||||
res.push(field);
|
||||
|
||||
case FFun(f):
|
||||
if (inlineSetter != null)
|
||||
Context.warning('methods doesn\'t support \'$INLINE_SETTER\'', inlineSetter.pos);
|
||||
res.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
static var patchField:Field;
|
||||
|
||||
static function patchSetter(expr:Expr):Expr {
|
||||
return switch (expr.expr) {
|
||||
case EReturn(res):
|
||||
var fieldName = patchField.name;
|
||||
|
||||
macro {
|
||||
var $NEW_VALUE = ${res.map(patchSetter)};
|
||||
${bindingSignalProvider.getFieldChangedExpr(patchField, macro $i{OLD_VALUE}, macro $i{NEW_VALUE})};
|
||||
return $i{NEW_VALUE};
|
||||
}
|
||||
|
||||
case _: expr.map(patchSetter);
|
||||
}
|
||||
}
|
||||
|
||||
static inline function injectBindableMeta(fields:Array<Field>, meta:MetadataEntry) {
|
||||
for (f in fields) {
|
||||
if (f.hasBindableMeta()) continue;
|
||||
if (f.access.exists(function (it) return it.equals(APrivate))) continue;
|
||||
|
||||
var forceParam = meta.findParam(FORCE);
|
||||
if (isFieldBindable(f, fields, forceParam.isNotNullAndTrue()))
|
||||
switch (f.kind) {
|
||||
case FFun(_):
|
||||
case _: f.meta.push({name:MetaUtils.BINDABLE_META, pos:f.pos, params:meta.params});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function isFieldBindable(field:Field, fields:Array<Field>, force = false):Bool {
|
||||
if (field.name == "new") return false;
|
||||
|
||||
if (field.access.exists(function (it) return it.equals(AMacro) || it.equals(ADynamic) || it.equals(AStatic)))
|
||||
return false;
|
||||
|
||||
if (field.name.startsWith("set_") || field.name.startsWith("get_")) {
|
||||
var propName = field.name.substr(4);
|
||||
if (fields.exists(function(it) return it.name == propName)) return false;
|
||||
}
|
||||
|
||||
if (!force) {
|
||||
var meta = field.bindableMeta();
|
||||
var forceParam = meta != null ? meta.findParam(FORCE) : null;
|
||||
force = forceParam.isNotNullAndTrue();
|
||||
}
|
||||
if (force)
|
||||
return switch (field.kind) {
|
||||
case FProp("never", _, _, _): false;
|
||||
case _: true;
|
||||
}
|
||||
|
||||
return switch (field.kind) {
|
||||
case FProp("never", _, _, _) | FProp(_, "never", _, _) | FProp(_, "dynamic", _, _) | FProp(_, "null", _, _): false;
|
||||
case _: true;
|
||||
}
|
||||
}
|
||||
#end
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package bindx;
|
||||
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
import haxe.macro.Context;
|
||||
|
||||
using bindx.MetaUtils;
|
||||
|
||||
class BindSignalProvider implements IBindingSignalProvider {
|
||||
|
||||
macro static public function register() {
|
||||
bindx.BindMacros.setBindingSignalProvider(new BindSignalProvider());
|
||||
return macro {};
|
||||
}
|
||||
|
||||
#if macro
|
||||
|
||||
static inline var SIGNAL_POSTFIX = "Changed";
|
||||
|
||||
/**
|
||||
* default value: true
|
||||
*/
|
||||
static inline var LAZY_SIGNAL = "lazySignal";
|
||||
/**
|
||||
* default value: false
|
||||
*/
|
||||
static inline var INLINE_SIGNAL_GETTER = "inlineSignalGetter";
|
||||
|
||||
public function new() {}
|
||||
|
||||
@:expose static inline function signalName(fieldName:String) return fieldName + SIGNAL_POSTFIX;
|
||||
@:expose static inline function signalGetterName(fieldName:String) return "get_" + signalName(fieldName);
|
||||
@:expose static inline function signalPrivateName(fieldName:String) return "_" + signalName(fieldName);
|
||||
|
||||
public function getFieldDispatcher(field:Field, res:Array<Field>) {
|
||||
switch (field.kind) {
|
||||
case FFun(_):
|
||||
generateSignal(field, macro : bindx.BindSignal.MethodSignal, macro new bindx.BindSignal.MethodSignal(), res);
|
||||
case FProp(_, _, type, _) | FVar(type, _):
|
||||
generateSignal(field, macro : bindx.BindSignal.FieldSignal<$type>, macro new bindx.BindSignal.FieldSignal<$type>(), res);
|
||||
}
|
||||
}
|
||||
|
||||
public function getFieldChangedExpr(field:Field, oldValue:Expr, newValue:Expr):Expr {
|
||||
var args = switch (field.kind) { case FFun(_): []; case _: [oldValue, newValue]; }
|
||||
return dispatchSignal(macro this, field.name, hasLazy(field.bindableMeta()), args);
|
||||
}
|
||||
|
||||
public function getClassFieldBindExpr(expr:Expr, field:ClassField, listener:Expr):Expr {
|
||||
var signalName = signalName(field.name);
|
||||
return macro $expr.$signalName.add($listener);
|
||||
}
|
||||
|
||||
public function getClassFieldBindToExpr(expr:Expr, field:ClassField, target:Expr):Expr {
|
||||
var signalName = signalName(field.name);
|
||||
return switch (field.kind) {
|
||||
case FMethod(_):
|
||||
var fieldName = field.name;
|
||||
macro {
|
||||
var listener = function () $target = $expr.$fieldName();
|
||||
$expr.$signalName.add(listener);
|
||||
function __unbind__() $expr.$signalName.remove(listener);
|
||||
}
|
||||
case FVar(_, _):
|
||||
macro {
|
||||
var listener = function (from, to) $target = to;
|
||||
$expr.$signalName.add(listener);
|
||||
function __unbind__() $expr.$signalName.remove(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getClassFieldUnbindExpr(expr:Expr, field:ClassField, listener:Expr):Expr {
|
||||
var signalName = signalName(field.name);
|
||||
return macro $expr.$signalName.remove($listener);
|
||||
}
|
||||
|
||||
public function getClassFieldChangedExpr(expr:Expr, field:ClassField, oldValue:Expr, newValue:Expr):Expr {
|
||||
var args = switch (field.kind) {
|
||||
case FMethod(_): [];
|
||||
case FVar(_, _): [oldValue, newValue];
|
||||
}
|
||||
return dispatchSignal(expr, field.name, hasLazy(field.bindableMeta()), args);
|
||||
}
|
||||
|
||||
function generateSignal(field:Field, type:ComplexType, builder:Expr, res:Array<Field>) {
|
||||
var signalName = signalName(field.name);
|
||||
var meta = field.bindableMeta();
|
||||
var inlineSignalGetter = meta.findParam(INLINE_SIGNAL_GETTER);
|
||||
|
||||
if (hasLazy(meta)) {
|
||||
var signalPrivateName = signalPrivateName(field.name);
|
||||
res.push({
|
||||
name: signalPrivateName,
|
||||
kind: FVar(type, null),
|
||||
pos: field.pos
|
||||
});
|
||||
|
||||
res.push({
|
||||
name: signalName,
|
||||
kind: FProp("get", "never", type, null),
|
||||
pos: field.pos,
|
||||
access: [APublic]
|
||||
});
|
||||
|
||||
var getter = macro function foo() {
|
||||
if (this.$signalPrivateName == null) {
|
||||
this.$signalPrivateName = ${builder}
|
||||
}
|
||||
return $i{signalPrivateName};
|
||||
};
|
||||
var getterAccess = [];
|
||||
if (inlineSignalGetter.isNotNullAndTrue()) getterAccess.push(AInline);
|
||||
|
||||
res.push({
|
||||
name: signalGetterName(field.name),
|
||||
kind: FFun(switch (getter.expr) { case EFunction (_, func): func; case _: throw false; }),
|
||||
pos: field.pos,
|
||||
access: getterAccess
|
||||
});
|
||||
} else {
|
||||
if (inlineSignalGetter != null)
|
||||
Context.warning('$INLINE_SIGNAL_GETTER works only with lazy signals', inlineSignalGetter.pos);
|
||||
|
||||
res.push({
|
||||
name: signalName,
|
||||
kind: FProp("default", "null", type, builder),
|
||||
pos: field.pos,
|
||||
access: [APublic]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
inline function dispatchSignal(expr:Expr, fieldName:String, lazy:Bool, args:Array<Expr>) {
|
||||
return
|
||||
if (lazy) {
|
||||
var signalPrivateName = signalPrivateName(fieldName);
|
||||
macro if ($expr.$signalPrivateName != null) {
|
||||
$expr.$signalPrivateName.dispatch($a{args});
|
||||
}
|
||||
} else {
|
||||
var signalName = signalName(fieldName);
|
||||
macro $expr.$signalName.dispatch($a{args});
|
||||
}
|
||||
}
|
||||
|
||||
@:expose inline function hasLazy(meta:MetadataEntry) {
|
||||
return meta.findParam(LAZY_SIGNAL).isNullOrTrue();
|
||||
}
|
||||
#end
|
||||
}
|
||||
|
||||
class MethodSignal extends Signal<Void -> Void> {
|
||||
|
||||
public function dispatch():Void {
|
||||
lock ++;
|
||||
for (l in listeners) l();
|
||||
if (lock > 0) lock --;
|
||||
}
|
||||
}
|
||||
|
||||
class FieldSignal<T> extends Signal<T -> T -> Void> {
|
||||
|
||||
public function dispatch(oldValue:T = null, newValue:T = null):Void {
|
||||
lock ++;
|
||||
for (l in listeners) l(oldValue, newValue);
|
||||
if (lock > 0) lock --;
|
||||
}
|
||||
}
|
||||
|
||||
class Signal<T> {
|
||||
|
||||
var listeners:Array<T>;
|
||||
|
||||
var lock = 0;
|
||||
|
||||
public function new() {
|
||||
removeAll();
|
||||
}
|
||||
|
||||
public inline function removeAll() {
|
||||
listeners = [];
|
||||
}
|
||||
|
||||
public inline function dispose() {
|
||||
listeners = null;
|
||||
}
|
||||
|
||||
public function add(listener:T):Void {
|
||||
if (listeners.indexOf(listener) == -1) {
|
||||
checkLock();
|
||||
listeners.push(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public function remove(listener:T):Void {
|
||||
var pos = listeners.indexOf(listener);
|
||||
if (pos > -1) {
|
||||
checkLock();
|
||||
listeners.splice(pos, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@:expose inline function checkLock() {
|
||||
if (lock > 0) {
|
||||
listeners = listeners.copy();
|
||||
lock --;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package bindx;
|
||||
|
||||
@:autoBuild(bindx.BindMacros.buildIBindable())
|
||||
interface IBindable {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package bindx;
|
||||
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
|
||||
interface IBindingSignalProvider {
|
||||
#if macro
|
||||
function getFieldDispatcher(field:Field, result:Array<Field>):Void;
|
||||
function getFieldChangedExpr(field:Field, oldValue:Expr, newValue:Expr):Expr;
|
||||
|
||||
function getClassFieldBindExpr(expr:Expr, field:ClassField, listener:Expr):Expr;
|
||||
function getClassFieldBindToExpr(expr:Expr, field:ClassField, target:Expr):Expr;
|
||||
function getClassFieldUnbindExpr(expr:Expr, field:ClassField, listener:Expr):Expr;
|
||||
function getClassFieldChangedExpr(expr:Expr, field:ClassField, oldValue:Expr, newValue:Expr):Expr;
|
||||
#end
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package bindx;
|
||||
|
||||
import haxe.macro.Type;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
|
||||
using haxe.macro.Tools;
|
||||
using Lambda;
|
||||
|
||||
class MetaUtils {
|
||||
|
||||
static public inline var BINDABLE_META = ":bindable";
|
||||
|
||||
static public function findParam(meta:MetadataEntry, name:String):Expr {
|
||||
if (meta.params == null) {
|
||||
return null;
|
||||
}
|
||||
for (p in meta.params) {
|
||||
switch (p.expr) {
|
||||
case EBinop(OpAssign, e1, e2):
|
||||
if (e1.toString() == name) return {expr:e2.expr, pos:p.pos};
|
||||
case _:
|
||||
Context.warning('Bindable arguments syntax error. Supported syntax: (flag1=true, flag2=false)', p.pos);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static public inline function bindableMeta(meta:Metadata):MetadataEntry
|
||||
return meta.find(function (it) return it.name == BINDABLE_META);
|
||||
}
|
||||
|
||||
class FieldMetaUtils {
|
||||
static public inline function bindableMeta(field:Field):MetadataEntry
|
||||
return MetaUtils.bindableMeta(field.meta);
|
||||
|
||||
static public inline function hasBindableMeta(field:Field):Bool
|
||||
return bindableMeta(field) != null;
|
||||
}
|
||||
|
||||
class ClassFieldMetaUtils {
|
||||
static public inline function bindableMeta(field:ClassField):MetadataEntry
|
||||
return MetaUtils.bindableMeta(field.meta.get());
|
||||
|
||||
static public inline function hasBindableMeta(field:ClassField):Bool
|
||||
return bindableMeta(field) != null;
|
||||
}
|
||||
|
||||
class ClassTypeMetaUtils {
|
||||
static public inline function bindableMeta(classType:ClassType):MetadataEntry
|
||||
return MetaUtils.bindableMeta(classType.meta.get());
|
||||
|
||||
static public inline function hasBindableMeta(classType:ClassType):Bool
|
||||
return bindableMeta(classType) != null;
|
||||
}
|
||||
|
||||
class ExprMetaUtils {
|
||||
static public inline function isTrue(expr:Expr):Bool
|
||||
return expr.expr.match(EConst(CIdent("true")));
|
||||
|
||||
static public inline function isFalse(expr:Expr):Bool
|
||||
return expr.expr.match(EConst(CIdent("false")));
|
||||
|
||||
static public inline function isNotNullAndTrue(expr:Expr):Bool
|
||||
return expr != null && isTrue(expr);
|
||||
|
||||
static public inline function isNullOrTrue(expr:Expr):Bool
|
||||
return expr == null || isTrue(expr);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package ;
|
||||
|
||||
import haxe.unit.TestCase;
|
||||
|
||||
class BaseTest extends TestCase {
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
}
|
||||
|
||||
function test1() {
|
||||
var b = new Bindable1();
|
||||
b.str = "a";
|
||||
var callNum = 0;
|
||||
b.strChanged.add(function (from, to) {
|
||||
assertEquals(from, "a");
|
||||
assertEquals(to, "b");
|
||||
callNum ++;
|
||||
});
|
||||
|
||||
bindx.Bind.bindx(b.str, function (from, to) {
|
||||
assertEquals(from, "a");
|
||||
assertEquals(to, "b");
|
||||
callNum ++;
|
||||
});
|
||||
b.str = "b";
|
||||
assertEquals(callNum, 2);
|
||||
}
|
||||
|
||||
function test2() {
|
||||
var b = new Bindable1();
|
||||
b.str = null;
|
||||
var callNum = 0;
|
||||
var listener = function (from, to) {
|
||||
assertEquals(from, null);
|
||||
assertEquals(to, "");
|
||||
callNum ++;
|
||||
}
|
||||
|
||||
b.strChanged.add(listener);
|
||||
bindx.Bind.bindx(b.str, listener);
|
||||
b.str = "";
|
||||
assertEquals(callNum, 1);
|
||||
|
||||
b.strChanged.add(listener);
|
||||
bindx.Bind.unbindx(b.str, listener);
|
||||
b.str = "1";
|
||||
assertEquals(callNum, 1);
|
||||
}
|
||||
|
||||
function test3() {
|
||||
var b = new Bindable1();
|
||||
b.str = null;
|
||||
var callNum = 0;
|
||||
|
||||
bindx.Bind.bindx(b.str, function (_, _) callNum++);
|
||||
bindx.Bind.bindx(b.str, function (_, _) callNum++);
|
||||
b.str = "";
|
||||
assertEquals(callNum, 2);
|
||||
|
||||
b.strChanged.removeAll();
|
||||
b.str = "1";
|
||||
assertEquals(callNum, 2);
|
||||
|
||||
b.strChanged.dispose();
|
||||
var addError = false;
|
||||
try {
|
||||
b.strChanged.add(function (_, _) {});
|
||||
} catch (e:Dynamic) {
|
||||
addError = true;
|
||||
}
|
||||
assertTrue(addError);
|
||||
}
|
||||
|
||||
function test4() {
|
||||
var b = new Bindable1();
|
||||
b.str = null;
|
||||
var callNum = 0;
|
||||
var listener = function (from, to) {
|
||||
assertEquals(from, "1");
|
||||
assertEquals(to, "2");
|
||||
callNum ++;
|
||||
}
|
||||
b.strChanged.add(listener);
|
||||
b.strChanged.dispatch("1", "2");
|
||||
assertEquals(callNum, 1);
|
||||
|
||||
bindx.Bind.notify(b.str, "1", "2");
|
||||
assertEquals(callNum, 2);
|
||||
}
|
||||
|
||||
function test5() {
|
||||
var b = new Bindable1();
|
||||
b.str = null;
|
||||
var callNum = 0;
|
||||
b.bindChanged.add(function () callNum++);
|
||||
|
||||
b.i = 10;
|
||||
assertEquals(callNum, 1);
|
||||
assertFalse(Reflect.hasField(b, "noBindChanged"));
|
||||
|
||||
bindx.Bind.notify(b.bind);
|
||||
assertEquals(callNum, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@:bindable
|
||||
class Bindable1 implements bindx.IBindable {
|
||||
|
||||
|
||||
public var str:String;
|
||||
|
||||
@:bindable
|
||||
public var i(default, set):Int;
|
||||
|
||||
@:bindable
|
||||
private var privateVar:Bool;
|
||||
|
||||
public function new() {
|
||||
if (this.privateVarChanged == null)
|
||||
throw "no private binding";
|
||||
}
|
||||
|
||||
function set_i(v) {
|
||||
i = v;
|
||||
bindx.Bind.notify(this.bind);
|
||||
return v;
|
||||
}
|
||||
|
||||
public function noBind() {
|
||||
|
||||
}
|
||||
|
||||
@:bindable
|
||||
public function bind() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package ;
|
||||
|
||||
import bindx.IBindable;
|
||||
|
||||
class InheritanceTest extends haxe.unit.TestCase {
|
||||
public function new() {
|
||||
super();
|
||||
}
|
||||
|
||||
function testChild() {
|
||||
var c = new BindableChild();
|
||||
c.i = 0;
|
||||
c.s = "0";
|
||||
var iChanged = 0;
|
||||
c.iChanged.add(function (from, to) {
|
||||
assertEquals(from, 0);
|
||||
assertEquals(to, 1);
|
||||
iChanged ++;
|
||||
});
|
||||
c.i = 1;
|
||||
assertEquals(iChanged, 1);
|
||||
|
||||
var sChanged = 0;
|
||||
c.sChanged.add(function (from, to) {
|
||||
assertEquals(from, "0");
|
||||
assertEquals(to, "1");
|
||||
sChanged ++;
|
||||
});
|
||||
c.s = "1";
|
||||
assertEquals(sChanged, 1);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@:bindable
|
||||
class BindableParent implements IBindable {
|
||||
public function new() {}
|
||||
|
||||
public var i:Int;
|
||||
}
|
||||
|
||||
@:bindable
|
||||
class BindableChild extends BindableParent {
|
||||
|
||||
public var s:String;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package ;
|
||||
|
||||
class TestProperty extends haxe.unit.TestCase {
|
||||
public function new() {
|
||||
super();
|
||||
}
|
||||
|
||||
function test1() {
|
||||
var p = new BindableProperty();
|
||||
p.s = "1";
|
||||
var callNum = 0;
|
||||
|
||||
p.sChanged.add(function (from, to) {
|
||||
assertEquals(from, "1");
|
||||
assertEquals(to, "");
|
||||
callNum ++;
|
||||
});
|
||||
|
||||
p.s = null;
|
||||
|
||||
p.sChanged.removeAll();
|
||||
|
||||
p.sChanged.add(function (from, to) {
|
||||
assertEquals(from, "");
|
||||
assertEquals(to, "1");
|
||||
callNum ++;
|
||||
});
|
||||
p.s = "1";
|
||||
|
||||
assertEquals(callNum, 2);
|
||||
}
|
||||
}
|
||||
|
||||
class BindableProperty implements bindx.IBindable {
|
||||
public function new() {
|
||||
}
|
||||
|
||||
@:bindable
|
||||
public var s(default, set):String;
|
||||
|
||||
function set_s(v) {
|
||||
if (v == null) {
|
||||
return s = "";
|
||||
}
|
||||
s = v;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ;
|
||||
|
||||
import haxe.unit.TestRunner;
|
||||
|
||||
class Tests {
|
||||
|
||||
static function main() {
|
||||
var runner = new TestRunner();
|
||||
runner.add(new BaseTest());
|
||||
runner.add(new InheritanceTest());
|
||||
runner.add(new TestProperty());
|
||||
runner.run();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user