浏览代码

review properties handling

ncannasse 6 年之前
父节点
当前提交
51921b6b7d
共有 7 个文件被更改,包括 312 次插入456 次删除
  1. 126 64
      h2d/uikit/BaseComponents.hx
  2. 8 7
      h2d/uikit/Builder.hx
  3. 99 43
      h2d/uikit/Component.hx
  4. 7 29
      h2d/uikit/CssEngine.hx
  5. 8 16
      h2d/uikit/CssParser.hx
  6. 32 6
      h2d/uikit/Element.hx
  7. 32 291
      h2d/uikit/Property.hx

+ 126 - 64
h2d/uikit/BaseComponents.hx

@@ -1,91 +1,153 @@
 package h2d.uikit;
+import h2d.uikit.Property;
+import h2d.uikit.CssParser.Value;
 
-class Drawable<T:h2d.Drawable> extends Component<T> {
+final class Object extends Component<h2d.Object> {
+	function new() {
+		super("object",h2d.Object,h2d.Object.new,null);
+		addHandler( defineProp("name",parseIdent,null), (o,v) -> o.name = v);
+		addHandler( defineProp("position", parsePosition, { x : 0, y : 0 }), (o,v) -> { o.x = v.x; o.y = v.y; });
+		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("margin", parseBox, { left : 0, top : 0, right : 0, bottom : 0 }), (o,v) -> {
+			var p = getFlowParent(o);
+			if( p != null ) {
+				p.paddingLeft = v.left;
+				p.paddingRight = v.right;
+				p.paddingTop = v.top;
+				p.paddingBottom = v.bottom;
+			}
+		});
+		addHandler( defineProp("margin-left", parseInt, 0), (o,v) -> {
+			var p = getFlowParent(o);
+			if( p != null ) p.paddingLeft = v;
+		});
+		addHandler( defineProp("margin-top", parseInt, 0), (o,v) -> {
+			var p = getFlowParent(o);
+			if( p != null ) p.paddingTop = v;
+		});
+		addHandler( defineProp("margin-right", parseInt, 0), (o,v) -> {
+			var p = getFlowParent(o);
+			if( p != null ) p.paddingRight = v;
+		});
+		addHandler( defineProp("margin-bottom", parseInt, 0), (o,v) -> {
+			var p = getFlowParent(o);
+			if( p != null ) p.paddingBottom = v;
+		});
+		addHandler( defineProp("blend", parseBlend, Alpha), (o,v) -> o.blendMode = v);
+	}
+
+	function getFlowParent( o : h2d.Object ) {
+		return Std.instance(o.parent, h2d.Flow);
+	}
 
-	override function handleProp(o:T, p:Property):Bool {
-		switch( p ) {
-		case PColor(r,g,b,a):
-			o.color.set(r,g,b,a);
-		case PSmooth(v):
-			o.smooth = v;
-		case PTileWrap(v):
-			o.tileWrap = v;
+	function parsePosition( value ) {
+		switch( value ) {
+		case VGroup([x,y]):
+			return { x : parseFloat(x), y : parseFloat(y) };
 		default:
-			return super.handleProp(o, p);
+			return invalidProp();
 		}
-		return true;
 	}
 
+	function parseScale( value ) {
+		switch( value ) {
+		case VGroup([x,y]):
+			return { x : parseFloat(x), y : parseFloat(y) };
+		default:
+			var s = parseFloat(value);
+			return { x : s, y : s };
+		}
+	}
+
+	function parseBlend( value ) : h2d.BlendMode {
+		return switch( parseIdent(value) ) {
+		case "add": Add;
+		case "alpha": Alpha;
+		case "none": None;
+		default: invalidProp();
+		}
+	}
+
+	public static var inst = new Object();
 }
 
-class Bitmap extends Drawable<h2d.Bitmap> {
+final class Drawable extends Component<h2d.Drawable> {
 
-	public function new() {
-		super("bitmap",h2d.Bitmap,function(p) return new h2d.Bitmap(h2d.Tile.fromColor(0xFF00FF,16,16),p));
+	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);
 	}
 
-	override function handleProp(o:h2d.Bitmap, p:Property):Bool {
-		switch( p ) {
-		case PSource(res):
-			o.tile = res.toTile();
-			return true;
-		default:
-		}
-		return super.handleProp(o, p);
+	function parseColorF( v : CssParser.Value ) : h3d.Vector {
+		var c = parseColor(v);
+		var v = new h3d.Vector();
+		v.setColor(c);
+		return v;
 	}
 
-	static var inst = new Bitmap();
+	public static var inst = new Drawable();
 
 }
 
-class Flow extends Component<h2d.Flow> {
+final class Bitmap extends Component<h2d.Bitmap> {
 
 	public function new() {
-		super("flow",h2d.Flow,function(p) return new h2d.Flow(p));
+		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);
 	}
 
-	override function handleProp(o:h2d.Flow, p:Property):Bool {
-		switch( p ) {
-		case PWidth(c,v):
-			switch( c ) {
-			case Whole:
-				o.minWidth = o.maxWidth = v;
-			case Min:
-				o.minWidth = v;
-			case Max:
-				o.maxWidth = v;
-			}
-		case PHeight(c,v):
-			switch( c ) {
-			case Whole:
-				o.minHeight = o.maxHeight = v;
-			case Min:
-				o.minHeight = v;
-			case Max:
-				o.maxHeight = v;
-			}
-		case PPadding(top, right, bottom, left):
-			o.paddingTop = top;
-			o.paddingRight = right;
-			o.paddingBottom = bottom;
-			o.paddingLeft = left;
-		case PPaddingDir(dir, value):
-			switch( dir ) {
-			case Left: o.paddingLeft = value;
-			case Right: o.paddingRight = value;
-			case Top: o.paddingTop = value;
-			case Bottom: o.paddingBottom = value;
+	static var inst = new Bitmap();
+
+}
+
+final class Flow extends Component<h2d.Flow> {
+
+	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(defineProp("min-width",parseAuto.bind(parseInt),null),(o,v) -> o.minWidth = v);
+		addHandler(defineProp("min-height",parseAuto.bind(parseInt),null),(o,v) -> o.minHeight = v);
+		addHandler(defineProp("max-width",parseAuto.bind(parseInt),null),(o,v) -> o.maxWidth = v);
+		addHandler(defineProp("max-height",parseAuto.bind(parseInt),null),(o,v) -> o.maxHeight = v);
+		addHandler(defineProp("background",parseBackground,null),(o,v) -> {
+			if( v == null ) {
+				o.backgroundTile = null;
+				o.borderWidth = o.borderHeight = 0;
+			} else {
+				o.backgroundTile = v.tile;
+				o.borderWidth = v.borderW;
+				o.borderHeight = v.borderH;
 			}
-		case PBackground(t, borderW, borderH):
-			o.backgroundTile = t;
-			if( borderW != null ) o.borderWidth = borderW;
-			if( borderH != null ) o.borderHeight = borderH;
-		case PDebug(b):
-			o.debug = b;
+		});
+		addHandler(defineProp("debug", parseBool, false), (o,v) -> o.debug = v);
+		addHandler(defineProp("padding", parseBox, { left : 0, top : 0, bottom : 0, right : 0 }), (o,v) -> {
+			o.paddingLeft = v.left;
+			o.paddingRight = v.right;
+			o.paddingTop = v.top;
+			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);
+	}
+
+	function parseBackground(value) {
+		return switch( value ) {
+		case VIdent("transparent"): null;
+		case VGroup([tile,VInt(x),VInt(y)]):
+			{ tile : parseTile(tile), borderW : x, borderH : y };
 		default:
-			return super.handleProp(o,p);
+			{ tile : parseTile(value), borderW : 0, borderH : 0 };
 		}
-		return true;
 	}
 
 	static var inst = new Flow();

+ 8 - 7
h2d/uikit/Builder.hx

@@ -7,10 +7,8 @@ class Builder {
 
 	public var errors : Array<String> = [];
 	var path : Array<String> = [];
-	var props : Property.PropertyParser;
 
 	public function new() {
-		props = new Property.PropertyParser();
 	}
 
 	function error( msg : String ) {
@@ -50,20 +48,23 @@ class Builder {
 						path.pop();
 						continue;
 					}
-					var p = props.parse(a, pval);
+					var p = Property.get(a.toLowerCase());
 					if( p == null ) {
 						path.push(a);
-						error("Invalid attribute value '"+v+"'");
+						error("Unknown attribute");
 						path.pop();
 						continue;
 					}
-					if( p == PUnknown ) {
+					var value : Dynamic;
+					try {
+						value = p.parser(pval);
+					} catch( e : Property.InvalidProperty ) {
 						path.push(a);
-						error("Unknown attribute");
+						error("Invalid attribute value"+(e.message == null ? "" : " ("+e.message+")"));
 						path.pop();
 						continue;
 					}
-					if( !inst.setProp(p) )
+					if( !inst.setAttribute(p,value) )
 						error("Unsupported attribute "+a);
 				}
 				root = inst;

+ 99 - 43
h2d/uikit/Component.hx

@@ -1,64 +1,121 @@
 package h2d.uikit;
+import h2d.uikit.Property;
 
 class Component<T:h2d.Object> {
 
 	public var name : String;
 	public var cl : Class<T>;
 	public var make : h2d.Object -> T;
+	public var parent : Component<Dynamic>;
+	var propsHandler : Array<T -> Dynamic -> Void>;
 
-	public function new(name, cl,make) {
+	public function new(name, cl, make, parent) {
 		this.name = name;
 		this.cl = cl;
 		this.make = make;
+		this.parent = parent;
+		propsHandler = parent == null ? [] : cast parent.propsHandler.copy();
 		COMPONENTS.set(name, this);
 	}
 
-	function getFlowParent( o : T ) {
-		return Std.instance(o.parent, h2d.Flow);
+	public inline function getHandler<P>( p : Property<P> ) : T -> P -> Void {
+		return propsHandler[p.id];
 	}
 
-	public function handleProp( o : T, p : Property ) : Bool {
-		switch( p ) {
-		case PName(name):
-			o.name = name;
-		case PPosition(x, y):
-			if( x != null ) o.x = x;
-			if( y != null ) o.y = y;
-		case PScale(sx, sy):
-			if( sx != null ) o.scaleX = sx;
-			if( sy != null ) o.scaleY = sy;
-		case PRotation(v):
-			o.rotation = v;
-		case PAlpha(v):
-			o.alpha = v;
-		case PBlend(mode):
-			o.blendMode = mode;
-		case PVisible(b):
-			o.visible = b;
-		case PMargin(left,right,top,bottom):
-			var parent = getFlowParent(o);
-			if( parent != null ) {
-				var props = parent.getProperties(o);
-				props.paddingLeft = left;
-				props.paddingRight = right;
-				props.paddingTop = top;
-				props.paddingBottom = bottom;
-			}
-		case PMarginDir(dir, v):
-			var parent = getFlowParent(o);
-			if( parent != null ) {
-				var props = parent.getProperties(o);
-				switch( dir ) {
-				case Left: props.paddingLeft = v;
-				case Right: props.paddingRight = v;
-				case Top: props.paddingTop = v;
-				case Bottom: props.paddingBottom = v;
-				}
+	function invalidProp( ?msg ) : Dynamic {
+		throw new InvalidProperty(msg);
+	}
+
+	function parseIdent( v : CssParser.Value ) {
+		return switch( v ) { case VIdent(v): v; default: invalidProp(); }
+	}
+
+	function parseColor( v : CssParser.Value ) : Null<Int> {
+		switch( v ) {
+		case VHex(h,color):
+			if( h.length == 3 ) {
+				var r = color >> 8;
+				var g = (color & 0xF0) >> 4;
+				var b = color & 0xF;
+				r |= r << 4;
+				g |= g << 4;
+				b |= b << 4;
+				color = (r << 16) | (g << 8) | b;
 			}
+			return color | 0xFF000000;
+		default:
+			return invalidProp();
+		}
+	}
+
+	function parseTile( v : CssParser.Value ) {
+		try {
+			var c = parseColor(v);
+			return h2d.Tile.fromColor(c,1,1,(c>>>24)/255);
+		} catch( e : InvalidProperty ) {
+			var path = parsePath(v);
+			var res = try hxd.res.Loader.currentInstance.load(path) catch( e : hxd.res.NotFound ) invalidProp("Resource not found "+path);
+			return res.toTile();
+		}
+	}
+
+	function parsePath( v : CssParser.Value ) {
+		return switch( v ) {
+		case VString(v): v;
+		case VIdent(v): v;
+		case VCall("url",[VIdent(v) | VString(v)]): v;
+		default: invalidProp();
+		}
+	}
+
+	function parseBool( v : CssParser.Value ) : Null<Bool> {
+		return switch( v ) {
+		case VIdent("true") | VInt(1): true;
+		case VIdent("false") | VInt(0): false;
+		default: invalidProp();
+		}
+	}
+
+	function parseAuto<T>( either : CssParser.Value -> T, v : CssParser.Value ) : Null<T> {
+		return v.match(VIdent("auto")) ? null : either(v);
+	}
+
+	function parseInt( v : CssParser.Value ) : Null<Int> {
+		return switch( v ) {
+		case VInt(i): i;
+		default: invalidProp();
+		}
+	}
+
+	function parseFloat( v : CssParser.Value ) : Float {
+		return switch( v ) {
+		case VInt(i): i;
+		case VFloat(f): f;
+		default: invalidProp();
+		}
+	}
+
+	function parseBox( v : CssParser.Value ) {
+		switch( v ) {
+		case VInt(v):
+			return { top : v, right : v, bottom : v, left : v };
+		case VGroup([VInt(v),VInt(h)]):
+			return { top : v, right : h, bottom : v, left : h };
+		case VGroup([VInt(v),VInt(h),VInt(k)]):
+			return { top : v, right : h, bottom : k, left : h };
+		case VGroup([VInt(v),VInt(h),VInt(k),VInt(l)]):
+			return { top : v, right : h, bottom : k, left : l };
 		default:
-			return false;
+			return invalidProp();
 		}
-		return true;
+	}
+
+	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;
 	}
 
 	public static function get( name : String ) {
@@ -66,6 +123,5 @@ class Component<T:h2d.Object> {
 	}
 
 	static var COMPONENTS = new Map<String,Component<Dynamic>>();
-	static var inst = new Component("object",h2d.Object,h2d.Object.new);
 
 }

+ 7 - 29
h2d/uikit/CssEngine.hx

@@ -4,7 +4,7 @@ private class Rule {
 	public var id : Int;
 	public var priority : Int;
 	public var cl : CssParser.CssClass;
-	public var properties : Array<Property>;
+	public var properties : Array<Property.PValue<Dynamic>>;
 	public function new() {
 	}
 }
@@ -39,16 +39,6 @@ class CssEngine {
 
 	public function add( sheet : CssParser.CssSheet ) {
 		for( r in sheet ) {
-			var important = [];
-			for( p in r.style ) {
-				switch( p ) {
-				case PImportant(p): important.push(p);
-				default:
-				}
-			}
-			var style = r.style;
-			if( important.length != 0 )
-				style = [for( p in r.style ) if( !p.match(PImportant(_)) ) p];
 			for( cl in r.classes ) {
 				var nids = 0, nothers = 0, nnodes = 0;
 				var c = cl;
@@ -60,24 +50,12 @@ class CssEngine {
 					c = c.parent;
 				}
 				var priority = (nids << 16) | (nothers << 8) | nnodes;
-
-				if( style.length > 0 ) {
-					var rule = new Rule();
-					rule.id = rules.length;
-					rule.cl = cl;
-					rule.properties = style;
-					rule.priority = priority;
-					rules.push(rule);
-				}
-
-				if( important.length > 0 ) {
-					var rule = new Rule();
-					rule.id = rules.length;
-					rule.cl = cl;
-					rule.properties = important;
-					rule.priority = (1<<24) | priority;
-					rules.push(rule);
-				}
+				var rule = new Rule();
+				rule.id = rules.length;
+				rule.cl = cl;
+				rule.properties = r.style;
+				rule.priority = priority;
+				rules.push(rule);
 			}
 		}
 		needSort = true;

+ 8 - 16
h2d/uikit/CssParser.hx

@@ -46,7 +46,7 @@ class CssClass {
 	}
 }
 
-typedef CssSheet = Array<{ classes : Array<CssClass>, style : Array<Property> }>;
+typedef CssSheet = Array<{ classes : Array<CssClass>, style : Array<Property.PValue<Dynamic>> }>;
 
 class CssParser {
 
@@ -55,11 +55,9 @@ class CssParser {
 
 	var spacesTokens : Bool;
 	var tokens : Array<Token>;
-	var pparser : Property.PropertyParser;
 	public var warnings : Array<{ start : Int, end : Int, msg : String }>;
 
 	public function new() {
-		this.pparser = new Property.PropertyParser();
 	}
 
 	function error( msg : String ) {
@@ -156,21 +154,14 @@ class CssParser {
 			var name = readIdent();
 			expect(TDblDot);
 			var value = readValue();
-			var imp = false;
-			switch( value ) {
-			case VLabel("important", val):
-				imp = true;
-				value = val;
-			default:
-			}
-			var p = pparser.parse(name, value);
+			var p = Property.get(name);
 			if( p == null )
-				warnings.push({ start : start, end : pos, msg : "Invalid property value "+valueStr(value) });
-			else if( p == PUnknown )
 				warnings.push({ start : start, end : pos, msg : "Unknown property "+name });
-			else {
-				if( imp ) p = PImportant(p);
-				style.push(p);
+			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+")") });
 			}
 			if( isToken(eof) )
 				break;
@@ -183,6 +174,7 @@ class CssParser {
 		this.css = css;
 		pos = 0;
 		tokens = [];
+		warnings = [];
 		var rules = [];
 		while( true ) {
 			if( isToken(TEof) )

+ 32 - 6
h2d/uikit/Element.hx

@@ -8,7 +8,7 @@ class Element {
 	public var classes : Array<String>;
 	public var parent : Element;
 	public var children : Array<Element> = [];
-	public var style : Array<Property> = [];
+	public var style : Array<Property.PValue<Dynamic>> = [];
 	var needStyleRefresh : Bool = true;
 
 	public function new(obj,component,?parent) {
@@ -26,14 +26,40 @@ class Element {
 		obj.remove();
 	}
 
-	public function setProp( p : Property ) {
-		switch( p ) {
-		case PClasses(cl):
-			classes = cl.copy();
+	public function setAttribute<P>( p : Property<P>, value : P ) {
+		if( p.id == pclass.id ) {
+			classes = cast value;
+			classes = classes.copy();
 			needStyleRefresh = true;
 			return true;
+		}
+		var handler = component.getHandler(p);
+		if( handler == null )
+			return false;
+		var found = false;
+		for( s in style )
+			if( s.p == p ) {
+				style.remove(s);
+				s.v = value;
+				style.push(s);
+				found = true;
+				break;
+			}
+		if( !found )
+			style.push(new Property.PValue(p,value));
+		handler(obj,value);
+		return true;
+	}
+
+	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:
-			return component.handleProp(obj, p);
+			throw new Property.InvalidProperty();
 		}
 	}
 

+ 32 - 291
h2d/uikit/Property.hx

@@ -1,302 +1,43 @@
 package h2d.uikit;
 
-enum Direction {
-	Left;
-	Right;
-	Top;
-	Bottom;
-}
-
-enum Constraint {
-	Whole;
-	Min;
-	Max;
-}
-
-enum Property {
-	// h2d.Object
-	PClasses( cl : Array<String> );
-	PName( name : String );
-	PPosition( ?x : Float, ?y : Float );
-	PScale( ?x : Float, ?y : Float );
-	PRotation( v : Float );
-	PAlpha( v : Float );
-	PBlend( mode : h2d.BlendMode );
-	PVisible( b : Bool );
-	// Drawable
-	PColor( r : Float, g : Float, b : Float, a : Float );
-	PSmooth( v : Null<Bool> );
-	PTileWrap( v : Bool );
-	// FlowProperties
-	PMargin( top : Int, right : Int, bottom : Int, left : Int );
-	PMarginDir( dir : Direction, value : Int );
-	// Flow
-	PDebug( v : Bool );
-	PPadding( top : Int, right : Int, bottom : Int, left : Int );
-	PPaddingDir( dir : Direction, value : Int );
-	PWidth( c : Constraint, ?v : Int );
-	PHeight( c : Constraint, ?v : Int );
-	PBackground( t : h2d.Tile, ?borderW : Int, ?borderH : Int );
-	// Bitmap
-	PSource( res : hxd.res.Any );
-	// Other
-	PImportant( p : Property );
-	PCustom( name : String, value : Dynamic );
-	PUnknown;
-}
-
-class PropertyParser {
-
-	public function new() {
-	}
-
-	function parseBool( v : CssParser.Value ) : Null<Bool> {
-		return switch( v ) {
-		case VIdent("true") | VInt(1): true;
-		case VIdent("false") | VInt(0): false;
-		default: null;
-		}
-	}
-
-	function parseInt( v : CssParser.Value ) : Null<Int> {
-		return switch( v ) {
-		case VInt(i): i;
-		default: null;
-		}
-	}
-
-	function parseFloat( v : CssParser.Value ) : Float {
-		return switch( v ) {
-		case VInt(i): i;
-		case VFloat(f): f;
-		default: Math.NaN;
-		}
-	}
-
-	inline function isNaN( f : Float ) {
-		return Math.isNaN(f);
-	}
-
-	function parseColor( v : CssParser.Value ) : Null<Int> {
-		switch( v ) {
-		case VHex(h,color):
-			if( h.length == 3 ) {
-				var r = color >> 8;
-				var g = (color & 0xF0) >> 4;
-				var b = color & 0xF;
-				r |= r << 4;
-				g |= g << 4;
-				b |= b << 4;
-				color = (r << 16) | (g << 8) | b;
-			}
-			return color | 0xFF000000;
-		default:
-			return null;
-		}
+class InvalidProperty {
+	public var message : String;
+	public function new(?msg) {
+		this.message = msg;
 	}
+}
 
-	function parseColorF( v : CssParser.Value ) : h3d.Vector {
-		var c = parseColor(v);
-		if( c != null ) {
-			var v = new h3d.Vector();
-			v.setColor(c);
-			return v;
-		}
-		return null;
-	}
+class Property<T> {
+	public var name(default,null) : String;
+	public var id(default,null) : Int;
+	public var defaultValue(default,null) : T;
+	public var parser(default,null) : CssParser.Value -> Dynamic;
+	var tag : Int = 0;
 
-	function parsePath( v : CssParser.Value ) {
-		return switch( v ) {
-		case VString(v): v;
-		case VIdent(v): v;
-		case VCall("url",[VIdent(v) | VString(v)]): v;
-		default: null;
-		}
+	public function new(name,parser,def) {
+		if( MAP.exists(name) )
+			throw "Duplicate property "+name;
+		this.id = ALL.length;
+		this.name = name;
+		this.defaultValue = def;
+		this.parser = parser;
+		ALL.push(this);
+		MAP.set(name, this);
 	}
 
-	function getIdent( v : CssParser.Value ) {
-		return switch( v ) { case VIdent(v): v; default: null; }
+	public static function get( name : String ) {
+		return MAP.get(name);
 	}
+	static var ALL = new Array<Property<Dynamic>>();
+	static var MAP = new Map<String, Property<Dynamic>>();
 
-	function parseTile( v : CssParser.Value ) {
-		var c = parseColor(v);
-		if( c != null )
-			return h2d.Tile.fromColor(c,1,1,(c>>>24)/255);
-		var path = parsePath(v);
-		if( path == null ) return null;
-		var res = try hxd.res.Loader.currentInstance.load(path) catch( e : hxd.res.NotFound ) return null;
-		return res.toTile();
-	}
-
-	function parsePadding( v : CssParser.Value ) {
-		switch( v ) {
-		case VInt(v):
-			return { top : v, right : v, bottom : v, left : v };
-		case VGroup([VInt(v),VInt(h)]):
-			return { top : v, right : h, bottom : v, left : h };
-		case VGroup([VInt(v),VInt(h),VInt(k)]):
-			return { top : v, right : h, bottom : k, left : h };
-		case VGroup([VInt(v),VInt(h),VInt(k),VInt(l)]):
-			return { top : v, right : h, bottom : k, left : l };
-		default:
-			return null;
-		}
-	}
+}
 
-	public function parse( name : String, value : CssParser.Value ) : Property {
-		switch( name ) {
-		case "visible":
-			var b = parseBool(value);
-			return b == null ? null : PVisible(b);
-		case "padding":
-			var p = parsePadding(value);
-			return p == null ? null : PPadding(p.top, p.right, p.bottom, p.left);
-		case "padding-left":
-			var v = parseInt(value);
-			return v == null ? null : PPaddingDir(Left, v);
-		case "padding-right":
-			var v = parseInt(value);
-			return v == null ? null : PPaddingDir(Right, v);
-		case "padding-top":
-			var v = parseInt(value);
-			return v == null ? null : PPaddingDir(Top, v);
-		case "padding-bottom":
-			var v = parseInt(value);
-			return v == null ? null : PPaddingDir(Bottom, v);
-		case "margin":
-			var p = parsePadding(value);
-			return p == null ? null : PMargin(p.top, p.right, p.bottom, p.left);
-		case "margin-left":
-			var v = parseInt(value);
-			return v == null ? null : PMarginDir(Left, v);
-		case "margin-right":
-			var v = parseInt(value);
-			return v == null ? null : PMarginDir(Right, v);
-		case "margin-top":
-			var v = parseInt(value);
-			return v == null ? null : PMarginDir(Top, v);
-		case "margin-bottom":
-			var v = parseInt(value);
-			return v == null ? null : PMarginDir(Bottom, v);
-		case "width":
-			if( getIdent(value) == "auto" ) return PWidth(Whole);
-			var v = parseInt(value);
-			return v == null ? null : PWidth(Whole,v);
-		case "height":
-			if( getIdent(value) == "auto" ) return PHeight(Whole);
-			var v = parseInt(value);
-			return v == null ? null : PHeight(Whole,v);
-		case "min-width":
-			if( getIdent(value) == "auto" ) return PWidth(Min);
-			var v = parseInt(value);
-			return v == null ? null : PWidth(Min,v);
-		case "min-height":
-			if( getIdent(value) == "auto" ) return PHeight(Min);
-			var v = parseInt(value);
-			return v == null ? null : PHeight(Min,v);
-		case "max-width":
-			if( getIdent(value) == "auto" ) return PWidth(Max);
-			var v = parseInt(value);
-			return v == null ? null : PWidth(Max,v);
-		case "max-height":
-			if( getIdent(value) == "auto" ) return PHeight(Max);
-			var v = parseInt(value);
-			return v == null ? null : PHeight(Max,v);
-		case "background":
-			switch( value ) {
-			case VGroup([url,VInt(w),VInt(h)]):
-				var t = parseTile(url);
-				if( t == null ) return null;
-				return PBackground(t, w, h);
-			default:
-				var t = parseTile(value);
-				if( t == null ) return null;
-				return PBackground(t);
-			}
-		case "src":
-			var path = parsePath(value);
-			if( path == null ) return null;
-			return try PSource(hxd.res.Loader.currentInstance.load(path)) catch( e : hxd.res.NotFound ) null;
-		case "class":
-			switch( value ) {
-			case VIdent(i):
-				return PClasses([i]);
-			case VGroup(l):
-				return PClasses([for( v in l ) { var id = getIdent(v); if( id == null ) return null; id; }]);
-			default:
-				return null;
-			}
-		case "name":
-			var name = getIdent(value);
-			if( name == null ) return null;
-			return PName(name);
-		case "position":
-			switch( value ) {
-			case VGroup([x,y]):
-				var x = parseFloat(x);
-				var y = parseFloat(y);
-				if( isNaN(x) || isNaN(y) ) return null;
-				return PPosition(x,y);
-			default:
-				return null;
-			}
-		case "x":
-			var x = parseFloat(value);
-			if( isNaN(x) ) return null;
-			return PPosition(x,null);
-		case "y":
-			var y = parseFloat(value);
-			if( isNaN(y) ) return null;
-			return PPosition(null,y);
-		case "scale":
-			switch( value ) {
-			case VGroup([sx,sy]):
-				var sx = parseFloat(sx);
-				var sy = parseFloat(sy);
-				if( isNaN(sx) || isNaN(sy) ) return null;
-				return PScale(sx,sy);
-			default:
-				var s = parseFloat(value);
-				if( isNaN(s) ) return null;
-				return PScale(s,s);
-			}
-		case "rotation":
-			var r = parseFloat(value);
-			if( isNaN(r) ) return null;
-			return PRotation(r * Math.PI / 180);
-		case "alpha":
-			var a = parseFloat(value);
-			if( isNaN(a) ) return null;
-			return PAlpha(a);
-		case "blend":
-			var b : h2d.BlendMode = switch( getIdent(value) ) {
-			case "add": Add;
-			case "alpha": Alpha;
-			case "none": None;
-			default: return null;
-			}
-			return PBlend(b);
-		case "color":
-			var v = parseColorF(value);
-			if( v == null ) return null;
-			return PColor(v.r, v.g, v.b, v.a);
-		case "smooth":
-			if( getIdent(value) == "auto" ) return PSmooth(null);
-			var b = parseBool(value);
-			return if( b == null ) null else PSmooth(b);
-		case "debug":
-			var b = parseBool(value);
-			return if( b == null ) null else PDebug(b);
-		default:
-			var p = customParsers.get(name);
-			if( p == null )
-				return PUnknown;
-			var v = p(value);
-			if( !v.valid ) return null;
-			return PCustom(name,v);
-		}
+class PValue<T> {
+	public var p : Property<T>;
+	public var v : T;
+	public function new(p,v) {
+		this.p = p;
+		this.v = v;
 	}
-
-	static var customParsers = new Map<String,CssParser.Value -> { value : Dynamic, valid : Bool }>();
-
-}
+}