Bladeren bron

component-specific css property parsing

ncannasse 6 jaren geleden
bovenliggende
commit
91045af67a
9 gewijzigde bestanden met toevoegingen van 240 en 192 verwijderingen
  1. 62 69
      h2d/uikit/BaseComponents.hx
  2. 8 13
      h2d/uikit/Builder.hx
  3. 31 24
      h2d/uikit/Component.hx
  4. 9 35
      h2d/uikit/CssParser.hx
  5. 29 7
      h2d/uikit/CssStyle.hx
  6. 15 0
      h2d/uikit/CssValue.hx
  7. 47 23
      h2d/uikit/Element.hx
  8. 25 0
      h2d/uikit/Error.hx
  9. 14 21
      h2d/uikit/Property.hx

+ 62 - 69
h2d/uikit/BaseComponents.hx

@@ -1,28 +1,24 @@
 package h2d.uikit;
 package h2d.uikit;
 import h2d.uikit.Property;
 import h2d.uikit.Property;
-import h2d.uikit.CssParser.Value;
+import h2d.uikit.CssValue;
 
 
-final class Object extends Component<h2d.Object> {
-
-	static var p_min_width : Property<Null<Int>>;
-	static var p_min_height : Property<Null<Int>>;
-	static var p_max_width : Property<Null<Int>>;
+final class Object2D extends Component<h2d.Object> {
 
 
 	function new() {
 	function new() {
-		super("object",h2d.Object,h2d.Object.new,null);
+		super("object",h2d.Object.new,null);
 
 
 		// h2d.Object properties
 		// h2d.Object properties
-		addHandler( defineProp("name",parseIdent,null), (o,v) -> o.name = v);
-		addHandler( defineProp("x", parseFloat, 0), (o,v) -> o.x = v);
-		addHandler( defineProp("y", parseFloat, 0), (o,v) -> o.y = v);
-		addHandler( defineProp("alpha", parseFloat, 1), (o,v) -> o.alpha = v);
-		addHandler( defineProp("rotation", parseFloat, 0), (o,v) -> o.rotation = v * Math.PI / 180);
-		addHandler( defineProp("visible", parseBool, true), (o,v) -> o.visible = v);
-		addHandler( defineProp("scale", parseScale, { x : 1, y : 1 }), (o,v) -> { o.scaleX = v.x; o.scaleY = v.y; });
-		addHandler( defineProp("blend", parseBlend, Alpha), (o,v) -> o.blendMode = v);
+		addHandler( "name", parseIdent, null, (o,v) -> o.name = v);
+		addHandler( "x", parseFloat, 0, (o,v) -> o.x = v);
+		addHandler( "y", parseFloat, 0, (o,v) -> o.y = v);
+		addHandler( "alpha", parseFloat, 1, (o,v) -> o.alpha = v);
+		addHandler( "rotation", parseFloat, 0, (o,v) -> o.rotation = v * Math.PI / 180);
+		addHandler( "visible", parseBool, true, (o,v) -> o.visible = v);
+		addHandler( "scale", parseScale, { x : 1, y : 1 }, (o,v) -> { o.scaleX = v.x; o.scaleY = v.y; });
+		addHandler( "blend", parseBlend, Alpha, (o,v) -> o.blendMode = v);
 
 
 		// flow properties
 		// flow properties
-		addHandler( defineProp("margin", parseBox, { left : 0, top : 0, right : 0, bottom : 0 }), (o,v) -> {
+		addHandler( "margin", parseBox, { left : 0, top : 0, right : 0, bottom : 0 }, (o,v) -> {
 			var p = getFlowProps(o);
 			var p = getFlowProps(o);
 			if( p != null ) {
 			if( p != null ) {
 				p.paddingLeft = v.left;
 				p.paddingLeft = v.left;
@@ -31,67 +27,64 @@ final class Object extends Component<h2d.Object> {
 				p.paddingBottom = v.bottom;
 				p.paddingBottom = v.bottom;
 			}
 			}
 		});
 		});
-		addHandler( defineProp("margin-left", parseInt, 0), (o,v) -> {
+		addHandler( "margin-left", parseInt, 0, (o,v) -> {
 			var p = getFlowProps(o);
 			var p = getFlowProps(o);
 			if( p != null ) p.paddingLeft = v;
 			if( p != null ) p.paddingLeft = v;
 		});
 		});
-		addHandler( defineProp("margin-top", parseInt, 0), (o,v) -> {
+		addHandler( "margin-top", parseInt, 0, (o,v) -> {
 			var p = getFlowProps(o);
 			var p = getFlowProps(o);
 			if( p != null ) p.paddingTop = v;
 			if( p != null ) p.paddingTop = v;
 		});
 		});
-		addHandler( defineProp("margin-right", parseInt, 0), (o,v) -> {
+		addHandler( "margin-right", parseInt, 0, (o,v) -> {
 			var p = getFlowProps(o);
 			var p = getFlowProps(o);
 			if( p != null ) p.paddingRight = v;
 			if( p != null ) p.paddingRight = v;
 		});
 		});
-		addHandler( defineProp("margin-bottom", parseInt, 0), (o,v) -> {
+		addHandler( "margin-bottom", parseInt, 0, (o,v) -> {
 			var p = getFlowProps(o);
 			var p = getFlowProps(o);
 			if( p != null ) p.paddingBottom = v;
 			if( p != null ) p.paddingBottom = v;
 		});
 		});
-		addHandler( defineProp("align", parseAlign, { h : null, v : null }), (o,v) -> {
+		addHandler( "align", parseAlign, { h : null, v : null }, (o,v) -> {
 			var p = getFlowProps(o);
 			var p = getFlowProps(o);
 			if( p != null ) {
 			if( p != null ) {
 				p.horizontalAlign = v.h;
 				p.horizontalAlign = v.h;
 				p.verticalAlign = v.v;
 				p.verticalAlign = v.v;
 			}
 			}
 		});
 		});
-		addHandler( defineProp("halign", parseHAlign, null), (o,v) -> {
+		addHandler( "halign", parseHAlign, null, (o,v) -> {
 			var p = getFlowProps(o);
 			var p = getFlowProps(o);
 			if( p != null ) p.horizontalAlign = v;
 			if( p != null ) p.horizontalAlign = v;
 		});
 		});
-		addHandler( defineProp("valign", parseVAlign, null), (o,v) -> {
+		addHandler( "valign", parseVAlign, null, (o,v) -> {
 			var p = getFlowProps(o);
 			var p = getFlowProps(o);
 			if( p != null ) p.verticalAlign = v;
 			if( p != null ) p.verticalAlign = v;
 		});
 		});
-		addHandler( defineProp("position",parsePosition,false), (o,v) -> {
+		addHandler( "position",parsePosition, false, (o,v) -> {
 			var p = getFlowProps(o);
 			var p = getFlowProps(o);
 			if( p != null ) p.isAbsolute = v;
 			if( p != null ) p.isAbsolute = v;
 		});
 		});
-		addHandler( defineProp("offset", parseXY, { x : 0, y : 0 }), (o,v) -> {
+		addHandler( "offset", parseXY, { x : 0, y : 0 }, (o,v) -> {
 			var p = getFlowProps(o);
 			var p = getFlowProps(o);
 			if( p != null ) {
 			if( p != null ) {
 				p.offsetX = Std.int(v.x);
 				p.offsetX = Std.int(v.x);
 				p.offsetY = Std.int(v.y);
 				p.offsetY = Std.int(v.y);
 			}
 			}
 		});
 		});
-		addHandler( defineProp("offset-x", parseInt, 0), (o,v) -> {
+		addHandler( "offset-x", parseInt, 0, (o,v) -> {
 			var p = getFlowProps(o);
 			var p = getFlowProps(o);
 			if( p != null ) p.offsetX = v;
 			if( p != null ) p.offsetX = v;
 		});
 		});
-		addHandler( defineProp("offset-y", parseInt, 0), (o,v) -> {
+		addHandler( "offset-y", parseInt, 0, (o,v) -> {
 			var p = getFlowProps(o);
 			var p = getFlowProps(o);
 			if( p != null ) p.offsetY = v;
 			if( p != null ) p.offsetY = v;
 		});
 		});
-
-		addHandler( p_min_width = defineProp("min-width",parseNone.bind(parseInt),null), (o,v) -> {
+		addHandler( "min-width",parseNone.bind(parseInt),null, (o,v) -> {
 			var p = getFlowProps(o);
 			var p = getFlowProps(o);
 			if( p != null ) p.minWidth = v;
 			if( p != null ) p.minWidth = v;
 		});
 		});
-		addHandler( p_min_height = defineProp("min-height",parseNone.bind(parseInt),null), (o,v) -> {
+		addHandler( "min-height",parseNone.bind(parseInt),null, (o,v) -> {
 			var p = getFlowProps(o);
 			var p = getFlowProps(o);
 			if( p != null ) p.minHeight = v;
 			if( p != null ) p.minHeight = v;
 		});
 		});
-
-		p_max_width = defineProp("max-width",parseNone.bind(parseInt),null);
 	}
 	}
 
 
 	function getFlowProps( o : h2d.Object ) {
 	function getFlowProps( o : h2d.Object ) {
@@ -129,19 +122,19 @@ final class Object extends Component<h2d.Object> {
 		}
 		}
 	}
 	}
 
 
-	public static var inst = new Object();
+	public static var inst = new Object2D();
 }
 }
 
 
 final class Drawable extends Component<h2d.Drawable> {
 final class Drawable extends Component<h2d.Drawable> {
 
 
 	function new() {
 	function new() {
-		super("drawable",h2d.Drawable,function(_) throw "assert",Object.inst);
-		addHandler(defineProp("color", parseColorF, new h3d.Vector(1,1,1,1)), (o,v) -> o.color.load(v));
-		addHandler(defineProp("smooth", parseAuto.bind(parseBool), null), (o,v) -> o.smooth = v);
-		addHandler(defineProp("tilewrap", parseBool, false), (o,v) -> o.tileWrap = v);
+		super("drawable",function(_) throw "assert",Object2D.inst);
+		addHandler("color", parseColorF, new h3d.Vector(1,1,1,1), (o,v) -> o.color.load(v));
+		addHandler("smooth", parseAuto.bind(parseBool), null, (o,v) -> o.smooth = v);
+		addHandler("tilewrap", parseBool, false, (o,v) -> o.tileWrap = v);
 	}
 	}
 
 
-	function parseColorF( v : CssParser.Value ) : h3d.Vector {
+	function parseColorF( v : CssValue ) : h3d.Vector {
 		var c = parseColor(v);
 		var c = parseColor(v);
 		var v = new h3d.Vector();
 		var v = new h3d.Vector();
 		v.setColor(c);
 		v.setColor(c);
@@ -155,8 +148,8 @@ final class Drawable extends Component<h2d.Drawable> {
 final class Bitmap extends Component<h2d.Bitmap> {
 final class Bitmap extends Component<h2d.Bitmap> {
 
 
 	public function new() {
 	public function new() {
-		super("bitmap",h2d.Bitmap,function(p) return new h2d.Bitmap(h2d.Tile.fromColor(0xFF00FF,16,16),p), Drawable.inst);
-		addHandler(defineProp("src",parseTile,null),(o,v) -> o.tile = v);
+		super("bitmap",function(p) return new h2d.Bitmap(h2d.Tile.fromColor(0xFF00FF,16,16),p), Drawable.inst);
+		addHandler("src",parseTile,null,(o,v) -> o.tile = v);
 	}
 	}
 
 
 	static var inst = new Bitmap();
 	static var inst = new Bitmap();
@@ -166,14 +159,14 @@ final class Bitmap extends Component<h2d.Bitmap> {
 final class Flow extends Component<h2d.Flow> {
 final class Flow extends Component<h2d.Flow> {
 
 
 	public function new() {
 	public function new() {
-		super("flow",h2d.Flow,h2d.Flow.new, Object.inst);
-		addHandler(defineProp("width",parseAuto.bind(parseInt),null),(o,v) -> { o.minWidth = o.maxWidth = v; });
-		addHandler(defineProp("height",parseAuto.bind(parseInt),null),(o,v) -> { o.minHeight = o.maxHeight = v; });
-		addHandler(@:privateAccess Object.p_min_width,(o,v) -> o.minWidth = v);
-		addHandler(@:privateAccess Object.p_min_height,(o,v) -> o.minHeight = v);
-		addHandler(@:privateAccess Object.p_max_width,(o,v) -> o.maxWidth = v);
-		addHandler(defineProp("max-height",parseNone.bind(parseInt),null),(o,v) -> o.maxHeight = v);
-		addHandler(defineProp("background",parseBackground,null),(o,v) -> {
+		super("flow",h2d.Flow.new, Object2D.inst);
+		addHandler("width",parseAuto.bind(parseInt),null,(o,v) -> { o.minWidth = o.maxWidth = v; });
+		addHandler("height",parseAuto.bind(parseInt),null,(o,v) -> { o.minHeight = o.maxHeight = v; });
+		addHandler("min-width",parseNone.bind(parseInt),null,(o,v) -> o.minWidth = v);
+		addHandler("max-width",parseNone.bind(parseInt),null,(o,v) -> o.maxWidth = v);
+		addHandler("min-height",parseNone.bind(parseInt),null,(o,v) -> o.minHeight = v);
+		addHandler("max-height",parseNone.bind(parseInt),null,(o,v) -> o.maxHeight = v);
+		addHandler("background",parseBackground,null,(o,v) -> {
 			if( v == null ) {
 			if( v == null ) {
 				o.backgroundTile = null;
 				o.backgroundTile = null;
 				o.borderWidth = o.borderHeight = 0;
 				o.borderWidth = o.borderHeight = 0;
@@ -183,23 +176,23 @@ final class Flow extends Component<h2d.Flow> {
 				o.borderHeight = v.borderH;
 				o.borderHeight = v.borderH;
 			}
 			}
 		});
 		});
-		addHandler(defineProp("debug", parseBool, false), (o,v) -> o.debug = v);
-		addHandler(defineProp("padding", parseBox, { left : 0, top : 0, bottom : 0, right : 0 }), (o,v) -> {
+		addHandler("debug", parseBool, false, (o,v) -> o.debug = v);
+		addHandler("padding", parseBox, { left : 0, top : 0, bottom : 0, right : 0 }, (o,v) -> {
 			o.paddingLeft = v.left;
 			o.paddingLeft = v.left;
 			o.paddingRight = v.right;
 			o.paddingRight = v.right;
 			o.paddingTop = v.top;
 			o.paddingTop = v.top;
 			o.paddingBottom = v.bottom;
 			o.paddingBottom = v.bottom;
 		});
 		});
-		addHandler(defineProp("padding-left", parseInt, 0), (o,v) -> o.paddingLeft = v);
-		addHandler(defineProp("padding-right", parseInt, 0), (o,v) -> o.paddingRight = v);
-		addHandler(defineProp("padding-top", parseInt, 0), (o,v) -> o.paddingTop = v);
-		addHandler(defineProp("padding-bottom", parseInt, 0), (o,v) -> o.paddingBottom = v);
-		addHandler(defineProp("content-align", parseAlign, { h : null, v : null }), (o,v) -> {
+		addHandler("padding-left", parseInt, 0, (o,v) -> o.paddingLeft = v);
+		addHandler("padding-right", parseInt, 0, (o,v) -> o.paddingRight = v);
+		addHandler("padding-top", parseInt, 0, (o,v) -> o.paddingTop = v);
+		addHandler("padding-bottom", parseInt, 0, (o,v) -> o.paddingBottom = v);
+		addHandler("content-align", parseAlign, { h : null, v : null }, (o,v) -> {
 			o.horizontalAlign = v.h;
 			o.horizontalAlign = v.h;
 			o.verticalAlign = v.v;
 			o.verticalAlign = v.v;
 		});
 		});
-		addHandler(defineProp("content-halign", parseVAlign, null), (o,v) -> o.horizontalAlign = v);
-		addHandler(defineProp("content-valign", parseHAlign, null), (o,v) -> o.verticalAlign = v);
+		addHandler("content-halign", parseVAlign, null, (o,v) -> o.horizontalAlign = v);
+		addHandler("content-valign", parseHAlign, null, (o,v) -> o.verticalAlign = v);
 	}
 	}
 
 
 	function parseBackground(value) {
 	function parseBackground(value) {
@@ -219,17 +212,17 @@ final class Flow extends Component<h2d.Flow> {
 final class Text extends Component<h2d.Text> {
 final class Text extends Component<h2d.Text> {
 
 
 	public function new() {
 	public function new() {
-		super("text",h2d.Text,function(p) return new h2d.Text(hxd.res.DefaultFont.get(),p), Drawable.inst);
-		addHandler( defineProp("text",parseText,""), (o,v) -> o.text = v);
-		addHandler( defineProp("font",parseFont,null), (o,v) -> o.font = v == null ? hxd.res.DefaultFont.get() : v );
-		addHandler( defineProp("letter-spacing",parseInt,1), (o,v) -> o.letterSpacing = v);
-		addHandler( defineProp("line-spacing",parseInt,0), (o,v) -> o.lineSpacing = v);
-		addHandler( @:privateAccess Object.p_max_width, (o,v) -> o.maxWidth = v);
-		addHandler( defineProp("text-align",parseTextAlign, Left), (o,v) -> o.textAlign = v);
-		addHandler( defineProp("text-shadow",parseNone.bind(parseTextShadow), null), (o,v) -> o.dropShadow = v);
+		super("text",function(p) return new h2d.Text(hxd.res.DefaultFont.get(),p), Drawable.inst);
+		addHandler("text",parseText,"", (o,v) -> o.text = v);
+		addHandler("font",parseFont,null, (o,v) -> o.font = v == null ? hxd.res.DefaultFont.get() : v );
+		addHandler("letter-spacing",parseInt,1, (o,v) -> o.letterSpacing = v);
+		addHandler("line-spacing",parseInt,0, (o,v) -> o.lineSpacing = v);
+		addHandler("max-width",parseNone.bind(parseInt),null,(o,v) -> o.maxWidth = v);
+		addHandler("text-align",parseTextAlign, Left, (o,v) -> o.textAlign = v);
+		addHandler("text-shadow",parseNone.bind(parseTextShadow), null, (o,v) -> o.dropShadow = v);
 	}
 	}
 
 
-	function parseTextAlign( value : CssParser.Value ) : h2d.Text.Align {
+	function parseTextAlign( value : CssValue ) : h2d.Text.Align {
 		return switch( parseIdent(value) ) {
 		return switch( parseIdent(value) ) {
 		case "left": Left;
 		case "left": Left;
 		case "right": Right;
 		case "right": Right;
@@ -238,7 +231,7 @@ final class Text extends Component<h2d.Text> {
 		}
 		}
 	}
 	}
 
 
-	function parseText( value : CssParser.Value ) {
+	function parseText( value : CssValue ) {
 		return switch( value ) {
 		return switch( value ) {
 		case VString(str): str;
 		case VString(str): str;
 		case VIdent(i): i;
 		case VIdent(i): i;
@@ -246,7 +239,7 @@ final class Text extends Component<h2d.Text> {
 		}
 		}
 	}
 	}
 
 
-	function parseTextShadow( value : CssParser.Value ) {
+	function parseTextShadow( value : CssValue ) {
 		return switch( value ) {
 		return switch( value ) {
 		case VGroup(vl):
 		case VGroup(vl):
 			return { dx : parseFloat(vl[0]), dy : parseFloat(vl[1]), color : vl.length >= 3 ? parseColor(vl[2]) : 0, alpha : vl.length >= 4 ? parseFloat(vl[3]) : 1 };
 			return { dx : parseFloat(vl[0]), dy : parseFloat(vl[1]), color : vl.length >= 3 ? parseColor(vl[2]) : 0, alpha : vl.length >= 4 ? parseFloat(vl[3]) : 1 };
@@ -255,7 +248,7 @@ final class Text extends Component<h2d.Text> {
 		}
 		}
 	}
 	}
 
 
-	function parseFont( value : CssParser.Value ) {
+	function parseFont( value : CssValue ) {
 		var path = parsePath(value);
 		var path = parsePath(value);
 		return loadResource(path).to(hxd.res.BitmapFont).toFont();
 		return loadResource(path).to(hxd.res.BitmapFont).toFont();
 	}
 	}

+ 8 - 13
h2d/uikit/Builder.hx

@@ -45,7 +45,7 @@ class Builder {
 				var txml = Xml.createElement("text");
 				var txml = Xml.createElement("text");
 				inst = buildRec(txml, root);
 				inst = buildRec(txml, root);
 				if( inst != null )
 				if( inst != null )
-					inst.setAttribute(Property.get("text"), StringTools.trim(x.nodeValue));
+					inst.setAttribute("text", VString(StringTools.trim(x.nodeValue)));
 			}
 			}
 		case Element:
 		case Element:
 			path.push(x.nodeName);
 			path.push(x.nodeName);
@@ -63,24 +63,19 @@ class Builder {
 						path.pop();
 						path.pop();
 						continue;
 						continue;
 					}
 					}
-					var p = Property.get(a.toLowerCase());
-					if( p == null ) {
+					switch( inst.setAttribute(a.toLowerCase(),pval) ) {
+					case Ok:
+					case Unknown:
 						path.push(a);
 						path.push(a);
 						error("Unknown attribute");
 						error("Unknown attribute");
 						path.pop();
 						path.pop();
-						continue;
-					}
-					var value : Dynamic;
-					try {
-						value = p.parser(pval);
-					} catch( e : Property.InvalidProperty ) {
+					case Unsupported:
+						error("Unsupported attribute "+a+" in");
+					case InvalidValue(msg):
 						path.push(a);
 						path.push(a);
-						error("Invalid attribute value"+(e.message == null ? "" : " ("+e.message+") for"));
+						error("Invalid attribute value"+(msg == null ? "" : " ("+msg+") for"));
 						path.pop();
 						path.pop();
-						continue;
 					}
 					}
-					if( !inst.setAttribute(p,value) )
-						error("Unsupported attribute "+a+" in");
 				}
 				}
 			}
 			}
 			for( e in x )
 			for( e in x )

+ 31 - 24
h2d/uikit/Component.hx

@@ -1,36 +1,47 @@
 package h2d.uikit;
 package h2d.uikit;
 import h2d.uikit.Property;
 import h2d.uikit.Property;
 
 
+class PropertyHandler<O,P> {
+
+	public var defaultValue(default,null) : P;
+	public var parser(default,null) : CssValue -> P;
+	public var apply(default,null) : O -> P -> Void;
+
+	public function new(parser,def,apply) {
+		this.parser = parser;
+		this.defaultValue = def;
+		this.apply = apply;
+	}
+}
+
 class Component<T:h2d.Object> {
 class Component<T:h2d.Object> {
 
 
 	public var name : String;
 	public var name : String;
-	public var cl : Class<T>;
 	public var make : h2d.Object -> T;
 	public var make : h2d.Object -> T;
 	public var parent : Component<Dynamic>;
 	public var parent : Component<Dynamic>;
-	var propsHandler : Array<T -> Dynamic -> Void>;
+	var propsHandler : Array<PropertyHandler<T,Dynamic>>;
 
 
-	public function new(name, cl, make, parent) {
+	public function new(name, make, parent) {
 		this.name = name;
 		this.name = name;
-		this.cl = cl;
 		this.make = make;
 		this.make = make;
 		this.parent = parent;
 		this.parent = parent;
 		propsHandler = parent == null ? [] : cast parent.propsHandler.copy();
 		propsHandler = parent == null ? [] : cast parent.propsHandler.copy();
 		COMPONENTS.set(name, this);
 		COMPONENTS.set(name, this);
 	}
 	}
 
 
-	public inline function getHandler<P>( p : Property<P> ) : T -> P -> Void {
-		return propsHandler[p.id];
+	public inline function getHandler<P>( p : Property ) : PropertyHandler<T,P> {
+		return cast propsHandler[p.id];
 	}
 	}
 
 
 	function invalidProp( ?msg ) : Dynamic {
 	function invalidProp( ?msg ) : Dynamic {
 		throw new InvalidProperty(msg);
 		throw new InvalidProperty(msg);
 	}
 	}
 
 
-	function parseIdent( v : CssParser.Value ) {
+	function parseIdent( v : CssValue ) {
 		return switch( v ) { case VIdent(v): v; default: invalidProp(); }
 		return switch( v ) { case VIdent(v): v; default: invalidProp(); }
 	}
 	}
 
 
-	function parseColor( v : CssParser.Value ) {
+	function parseColor( v : CssValue ) {
 		switch( v ) {
 		switch( v ) {
 		case VHex(h,color):
 		case VHex(h,color):
 			if( h.length == 3 ) {
 			if( h.length == 3 ) {
@@ -52,7 +63,7 @@ class Component<T:h2d.Object> {
 		return try hxd.res.Loader.currentInstance.load(path) catch( e : hxd.res.NotFound ) invalidProp("Resource not found "+path);
 		return try hxd.res.Loader.currentInstance.load(path) catch( e : hxd.res.NotFound ) invalidProp("Resource not found "+path);
 	}
 	}
 
 
-	function parseTile( v : CssParser.Value ) {
+	function parseTile( v : CssValue) {
 		try {
 		try {
 			var c = parseColor(v);
 			var c = parseColor(v);
 			return h2d.Tile.fromColor(c,1,1,(c>>>24)/255);
 			return h2d.Tile.fromColor(c,1,1,(c>>>24)/255);
@@ -62,7 +73,7 @@ class Component<T:h2d.Object> {
 		}
 		}
 	}
 	}
 
 
-	function parsePath( v : CssParser.Value ) {
+	function parsePath( v : CssValue ) {
 		return switch( v ) {
 		return switch( v ) {
 		case VString(v): v;
 		case VString(v): v;
 		case VIdent(v): v;
 		case VIdent(v): v;
@@ -71,7 +82,7 @@ class Component<T:h2d.Object> {
 		}
 		}
 	}
 	}
 
 
-	function parseBool( v : CssParser.Value ) : Null<Bool> {
+	function parseBool( v : CssValue ) : Null<Bool> {
 		return switch( v ) {
 		return switch( v ) {
 		case VIdent("true") | VInt(1): true;
 		case VIdent("true") | VInt(1): true;
 		case VIdent("false") | VInt(0): false;
 		case VIdent("false") | VInt(0): false;
@@ -79,22 +90,22 @@ class Component<T:h2d.Object> {
 		}
 		}
 	}
 	}
 
 
-	function parseAuto<T>( either : CssParser.Value -> T, v : CssParser.Value ) : Null<T> {
+	function parseAuto<T>( either : CssValue -> T, v : CssValue ) : Null<T> {
 		return v.match(VIdent("auto")) ? null : either(v);
 		return v.match(VIdent("auto")) ? null : either(v);
 	}
 	}
 
 
-	function parseNone<T>( either : CssParser.Value -> T, v : CssParser.Value ) : Null<T> {
+	function parseNone<T>( either : CssValue -> T, v : CssValue ) : Null<T> {
 		return v.match(VIdent("none")) ? null : either(v);
 		return v.match(VIdent("none")) ? null : either(v);
 	}
 	}
 
 
-	function parseInt( v : CssParser.Value ) : Null<Int> {
+	function parseInt( v : CssValue ) : Null<Int> {
 		return switch( v ) {
 		return switch( v ) {
 		case VInt(i): i;
 		case VInt(i): i;
 		default: invalidProp();
 		default: invalidProp();
 		}
 		}
 	}
 	}
 
 
-	function parseFloat( v : CssParser.Value ) : Float {
+	function parseFloat( v : CssValue ) : Float {
 		return switch( v ) {
 		return switch( v ) {
 		case VInt(i): i;
 		case VInt(i): i;
 		case VFloat(f): f;
 		case VFloat(f): f;
@@ -102,7 +113,7 @@ class Component<T:h2d.Object> {
 		}
 		}
 	}
 	}
 
 
-	function parseXY( v : CssParser.Value ) {
+	function parseXY( v : CssValue ) {
 		return switch( v ) {
 		return switch( v ) {
 		case VGroup([x,y]): { x : parseFloat(x), y : parseFloat(y) };
 		case VGroup([x,y]): { x : parseFloat(x), y : parseFloat(y) };
 		default: invalidProp();
 		default: invalidProp();
@@ -139,7 +150,7 @@ class Component<T:h2d.Object> {
 		}
 		}
 	}
 	}
 
 
-	function parseAlign( value : CssParser.Value ) {
+	function parseAlign( value : CssValue ) {
 		switch( value ) {
 		switch( value ) {
 		case VIdent("auto"):
 		case VIdent("auto"):
 			return { h : null, v : null };
 			return { h : null, v : null };
@@ -160,7 +171,7 @@ class Component<T:h2d.Object> {
 		}
 		}
 	}
 	}
 
 
-	function parseBox( v : CssParser.Value ) {
+	function parseBox( v : CssValue ) {
 		switch( v ) {
 		switch( v ) {
 		case VInt(v):
 		case VInt(v):
 			return { top : v, right : v, bottom : v, left : v };
 			return { top : v, right : v, bottom : v, left : v };
@@ -175,12 +186,8 @@ class Component<T:h2d.Object> {
 		}
 		}
 	}
 	}
 
 
-	inline function defineProp<P>( name : String, parser : CssParser.Value -> P, def : P ) {
-		return new Property<P>(name, parser, def);
-	}
-
-	function addHandler<P>( p : Property<P>, f : T -> P -> Void ) {
-		propsHandler[p.id] = f;
+	function addHandler<P>( p : String, parser : CssValue -> P, def : P, apply : T -> P -> Void ) {
+		propsHandler[Property.get(p).id] = new PropertyHandler(parser,def,apply);
 	}
 	}
 
 
 	public static function get( name : String ) {
 	public static function get( name : String ) {

+ 9 - 35
h2d/uikit/CssParser.hx

@@ -1,4 +1,5 @@
 package h2d.uikit;
 package h2d.uikit;
+import h2d.uikit.CssValue;
 
 
 enum Token {
 enum Token {
 	TIdent( i : String );
 	TIdent( i : String );
@@ -22,20 +23,6 @@ enum Token {
 	TStar;
 	TStar;
 }
 }
 
 
-enum Value {
-	VIdent( i : String );
-	VString( s : String );
-	VUnit( v : Float, unit : String );
-	VFloat( v : Float );
-	VInt( v : Int );
-	VHex( h : String, v : Int );
-	VList( l : Array<Value> );
-	VGroup( l : Array<Value> );
-	VCall( f : String, vl : Array<Value> );
-	VLabel( v : String, val : Value );
-	VSlash;
-}
-
 class CssClass {
 class CssClass {
 	public var parent : Null<CssClass>;
 	public var parent : Null<CssClass>;
 	public var node : Null<String>;
 	public var node : Null<String>;
@@ -46,16 +33,7 @@ class CssClass {
 	}
 	}
 }
 }
 
 
-typedef CssSheet = Array<{ classes : Array<CssClass>, style : Array<Property.PValue<Dynamic>> }>;
-
-class CssParserError {
-	public var message : String;
-	public var position : Int;
-	public function new(msg,pos) {
-		this.message = msg;
-		this.position = pos;
-	}
-}
+typedef CssSheet = Array<{ classes : Array<CssClass>, style : Array<{ p : Property, value : CssValue }> }>;
 
 
 class CssParser {
 class CssParser {
 
 
@@ -70,7 +48,7 @@ class CssParser {
 	}
 	}
 
 
 	function error( msg : String ) {
 	function error( msg : String ) {
-		throw new CssParserError(msg,pos);
+		throw new Error(msg,pos);
 	}
 	}
 
 
 	function unexpected( t : Token ) : Dynamic {
 	function unexpected( t : Token ) : Dynamic {
@@ -163,15 +141,11 @@ class CssParser {
 			var name = readIdent();
 			var name = readIdent();
 			expect(TDblDot);
 			expect(TDblDot);
 			var value = readValue();
 			var value = readValue();
-			var p = Property.get(name);
+			var p = Property.get(name, false);
 			if( p == null )
 			if( p == null )
 				warnings.push({ start : start, end : pos, msg : "Unknown property "+name });
 				warnings.push({ start : start, end : pos, msg : "Unknown property "+name });
-			else try {
-				var value = p.parser(value);
-				style.push(new Property.PValue(p,value));
-			} catch( e : Property.InvalidProperty ) {
-				warnings.push({ start : start, end : pos, msg : "Invalid property value "+valueStr(value)+(e.message == null ? "" : "("+e.message+")") });
-			}
+			else
+				style.push({ p : p, value : value });
 			if( isToken(eof) )
 			if( isToken(eof) )
 				break;
 				break;
 			expect(TSemicolon);
 			expect(TSemicolon);
@@ -184,7 +158,7 @@ class CssParser {
 		pos = 0;
 		pos = 0;
 		tokens = [];
 		tokens = [];
 		warnings = [];
 		warnings = [];
-		var rules = [];
+		var rules : CssSheet = [];
 		while( true ) {
 		while( true ) {
 			if( isToken(TEof) )
 			if( isToken(TEof) )
 				break;
 				break;
@@ -270,7 +244,7 @@ class CssParser {
 		}
 		}
 	}
 	}
 
 
-	function readValue(?opt)  : Value {
+	function readValue(?opt) : CssValue {
 		var t = readToken();
 		var t = readToken();
 		var v = switch( t ) {
 		var v = switch( t ) {
 		case TSharp:
 		case TSharp:
@@ -334,7 +308,7 @@ class CssParser {
 		};
 		};
 	}
 	}
 
 
-	function readValueNext( v : Value ) : Value {
+	function readValueNext( v : CssValue ) : CssValue {
 		var t = readToken();
 		var t = readToken();
 		return switch( t ) {
 		return switch( t ) {
 		case TPOpen:
 		case TPOpen:

+ 29 - 7
h2d/uikit/CssStyle.hx

@@ -1,10 +1,21 @@
 package h2d.uikit;
 package h2d.uikit;
 
 
+private class RuleStyle {
+	public var p : Property;
+	public var value : CssValue;
+	public var lastHandler : Component.PropertyHandler<Dynamic,Dynamic>;
+	public var lastValue : Dynamic;
+	public function new(p,value) {
+		this.p = p;
+		this.value = value;
+	}
+}
+
 private class Rule {
 private class Rule {
 	public var id : Int;
 	public var id : Int;
 	public var priority : Int;
 	public var priority : Int;
 	public var cl : CssParser.CssClass;
 	public var cl : CssParser.CssClass;
-	public var style : Array<Property.PValue<Dynamic>>;
+	public var style : Array<RuleStyle>;
 	public var next : Rule;
 	public var next : Rule;
 	public function new() {
 	public function new() {
 	}
 	}
@@ -36,8 +47,8 @@ class CssStyle {
 			e.needStyleRefresh = false;
 			e.needStyleRefresh = false;
 			var head = null;
 			var head = null;
 			var tag = ++TAG;
 			var tag = ++TAG;
-			for( p in e.style )
-				p.p.tag = tag;
+			for( p in e.currentSet )
+				p.tag = tag;
 			for( r in rules ) {
 			for( r in rules ) {
 				if( !ruleMatch(r.cl,e) ) continue;
 				if( !ruleMatch(r.cl,e) ) continue;
 				var match = false;
 				var match = false;
@@ -62,7 +73,8 @@ class CssStyle {
 				else {
 				else {
 					changed = true;
 					changed = true;
 					e.currentSet.remove(p);
 					e.currentSet.remove(p);
-					e.component.getHandler(p)(e.obj,p.defaultValue);
+					var h = e.component.getHandler(p);
+					h.apply(e.obj,h.defaultValue);
 				}
 				}
 			}
 			}
 			// apply new properties
 			// apply new properties
@@ -72,7 +84,17 @@ class CssStyle {
 					var pr = p.p;
 					var pr = p.p;
 					var h = e.component.getHandler(pr);
 					var h = e.component.getHandler(pr);
 					if( h == null ) continue;
 					if( h == null ) continue;
-					h(e.obj, p.v);
+					if( p.lastHandler != h ) {
+						try {
+							var value = h.parser(p.value);
+							p.lastHandler = h;
+							p.lastValue = value;
+						} catch( e : Property.InvalidProperty ) {
+							// invalid property
+							continue;
+						}
+					}
+					h.apply(e.obj, p.lastValue);
 					changed = true;
 					changed = true;
 					if( pr.tag != ntag ) {
 					if( pr.tag != ntag ) {
 						e.currentSet.push(pr);
 						e.currentSet.push(pr);
@@ -87,7 +109,7 @@ class CssStyle {
 			if( changed )
 			if( changed )
 				for( p in e.style ) {
 				for( p in e.style ) {
 					var h = e.component.getHandler(p.p);
 					var h = e.component.getHandler(p.p);
-					if( h != null ) h(e.obj, p.v);
+					if( h != null ) h.apply(e.obj, p.value);
 				}
 				}
 			// parent style has changed, we need to sync children
 			// parent style has changed, we need to sync children
 			force = true;
 			force = true;
@@ -112,7 +134,7 @@ class CssStyle {
 				var rule = new Rule();
 				var rule = new Rule();
 				rule.id = rules.length;
 				rule.id = rules.length;
 				rule.cl = cl;
 				rule.cl = cl;
-				rule.style = r.style;
+				rule.style = [for( s in r.style ) new RuleStyle(s.p,s.value)];
 				rule.priority = priority;
 				rule.priority = priority;
 				rules.push(rule);
 				rules.push(rule);
 			}
 			}

+ 15 - 0
h2d/uikit/CssValue.hx

@@ -0,0 +1,15 @@
+package h2d.uikit;
+
+enum CssValue {
+	VIdent( i : String );
+	VString( s : String );
+	VUnit( v : Float, unit : String );
+	VFloat( v : Float );
+	VInt( v : Int );
+	VHex( h : String, v : Int );
+	VList( l : Array<CssValue> );
+	VGroup( l : Array<CssValue> );
+	VCall( f : String, vl : Array<CssValue> );
+	VLabel( v : String, val : CssValue );
+	VSlash;
+}

+ 47 - 23
h2d/uikit/Element.hx

@@ -1,5 +1,12 @@
 package h2d.uikit;
 package h2d.uikit;
 
 
+enum SetAttributeResult {
+	Ok;
+	Unknown;
+	Unsupported;
+	InvalidValue( ?msg : String );
+}
+
 class Element {
 class Element {
 
 
 	public var id : String;
 	public var id : String;
@@ -8,8 +15,8 @@ class Element {
 	public var classes : Array<String>;
 	public var classes : Array<String>;
 	public var parent : Element;
 	public var parent : Element;
 	public var children : Array<Element> = [];
 	public var children : Array<Element> = [];
-	public var style : Array<Property.PValue<Dynamic>> = [];
-	var currentSet : Array<Property<Dynamic>> = [];
+	var style : Array<{ p : Property, value : Any }> = [];
+	var currentSet : Array<Property> = [];
 	var needStyleRefresh : Bool = true;
 	var needStyleRefresh : Bool = true;
 
 
 	public function new(obj,component,?parent) {
 	public function new(obj,component,?parent) {
@@ -27,43 +34,60 @@ class Element {
 		obj.remove();
 		obj.remove();
 	}
 	}
 
 
-	public function setAttribute<P>( p : Property<P>, value : P ) {
+	/*
+	public function initAttributes( attr : haxe.DynamicAccess<String> ) {
+		var p = new CssParser();
+		for( a in attr.keys() ) {
+			var h = component.getHandler()
+			var v = attr.get(a);
+			var value = p.parseValue(v);
+		}
+	}*/
+
+	public function setAttribute( p : String, value : CssValue ) : SetAttributeResult {
+		var p = Property.get(p,false);
+		if( p == null )
+			return Unknown;
 		if( p.id == pclass.id ) {
 		if( p.id == pclass.id ) {
-			classes = cast value;
-			classes = classes.copy();
+			switch( value ) {
+			case VIdent(i): classes = [i];
+			case VGroup(vl): classes = [for( v in vl ) switch( v ) { case VIdent(i): i; default: return InvalidValue(); }];
+			default: return InvalidValue();
+			}
 			needStyleRefresh = true;
 			needStyleRefresh = true;
-			return true;
+			return Ok;
 		}
 		}
 		var handler = component.getHandler(p);
 		var handler = component.getHandler(p);
 		if( handler == null )
 		if( handler == null )
-			return false;
+			return Unsupported;
+		var v : Dynamic;
+		try {
+			v = handler.parser(value);
+		} catch( e : Property.InvalidProperty ) {
+			return InvalidValue(e.message);
+		}
 		var found = false;
 		var found = false;
 		for( s in style )
 		for( s in style )
 			if( s.p == p ) {
 			if( s.p == p ) {
+				s.value = v;
 				style.remove(s);
 				style.remove(s);
-				s.v = value;
 				style.push(s);
 				style.push(s);
 				found = true;
 				found = true;
 				break;
 				break;
 			}
 			}
 		if( !found ) {
 		if( !found ) {
-			currentSet.push(p);
-			style.push(new Property.PValue(p,value));
+			style.push({ p : p , value : v });
+			for( s in currentSet )
+				if( s == p ) {
+					found = true;
+					break;
+				}
+			if( !found ) currentSet.push(p);
 		}
 		}
-		handler(obj,value);
-		return true;
+		handler.apply(obj,v);
+		return Ok;
 	}
 	}
 
 
-	static var pclass = new Property("class", parseClass, []);
-	static function parseClass( v : CssParser.Value ) {
-		return switch( v ) {
-		case VIdent(i):
-			return [i];
-		case VGroup(l):
-			return [for( v in l ) switch(v) { case VIdent(i): i; default: throw new Property.InvalidProperty(); }];
-		default:
-			throw new Property.InvalidProperty();
-		}
-	}
+	static var pclass = Property.get("class");
 
 
 }
 }

+ 25 - 0
h2d/uikit/Error.hx

@@ -0,0 +1,25 @@
+package h2d.uikit;
+
+class Error {
+	public var message : String;
+	public var pmin : Int;
+	public var pmax : Int;
+
+	public function new( messsage, pmin = -1, pmax = -1 ) {
+		this.message = messsage;
+		this.pmin = pmin;
+		this.pmax = pmax < 0 ? pmin : pmax;
+	}
+
+	public function toString() {
+		var msg = "UIKitError("+message+")";
+		if( pmin < 0 )
+			return msg;
+		msg += " "+pmin;
+		if( pmax == pmin )
+			return msg;
+		msg += ":"+(pmax - pmin);
+		return msg;
+	}
+
+}

+ 14 - 21
h2d/uikit/Property.hx

@@ -7,37 +7,30 @@ class InvalidProperty {
 	}
 	}
 }
 }
 
 
-class Property<T> {
+class Property {
 	public var name(default,null) : String;
 	public var name(default,null) : String;
 	public var id(default,null) : Int;
 	public var id(default,null) : Int;
-	public var defaultValue(default,null) : T;
-	public var parser(default,null) : CssParser.Value -> Dynamic;
 	@:allow(h2d.uikit.CssStyle) var tag : Int = 0;
 	@:allow(h2d.uikit.CssStyle) var tag : Int = 0;
 
 
-	public function new(name,parser,def) {
-		if( MAP.exists(name) )
-			throw "Duplicate property "+name;
+	function new(name) {
 		this.id = ALL.length;
 		this.id = ALL.length;
 		this.name = name;
 		this.name = name;
-		this.defaultValue = def;
-		this.parser = parser;
 		ALL.push(this);
 		ALL.push(this);
 		MAP.set(name, this);
 		MAP.set(name, this);
 	}
 	}
 
 
-	public static function get( name : String ) {
-		return MAP.get(name);
+	public static function get( name : String, create = true ) {
+		if( MAP == null ) {
+			MAP = new Map();
+			ALL = [];
+		}
+		var p = MAP.get(name);
+		if( p == null && create )
+			p = new Property(name);
+		return p;
 	}
 	}
-	static var ALL = new Array<Property<Dynamic>>();
-	static var MAP = new Map<String, Property<Dynamic>>();
 
 
-}
+	static var ALL : Array<Property>;
+	static var MAP : Map<String, Property>;
 
 
-class PValue<T> {
-	public var p : Property<T>;
-	public var v : T;
-	public function new(p,v) {
-		this.p = p;
-		this.v = v;
-	}
-}
+}