Преглед на файлове

started uikit: component, css parser, property

ncannasse преди 6 години
родител
ревизия
c273824753
променени са 6 файла, в които са добавени 1126 реда и са изтрити 0 реда
  1. 93 0
      h2d/uikit/BaseComponents.hx
  2. 78 0
      h2d/uikit/Builder.hx
  3. 71 0
      h2d/uikit/Component.hx
  4. 544 0
      h2d/uikit/CssParser.hx
  5. 39 0
      h2d/uikit/Element.hx
  6. 301 0
      h2d/uikit/Property.hx

+ 93 - 0
h2d/uikit/BaseComponents.hx

@@ -0,0 +1,93 @@
+package h2d.uikit;
+
+class Drawable<T:h2d.Drawable> extends Component<T> {
+
+	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;
+		default:
+			return super.handleProp(o, p);
+		}
+		return true;
+	}
+
+}
+
+class Bitmap extends Drawable<h2d.Bitmap> {
+
+	public function new() {
+		super("bitmap",h2d.Bitmap,function(p) return new h2d.Bitmap(h2d.Tile.fromColor(0xFF00FF,16,16),p));
+	}
+
+	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);
+	}
+
+	static var inst = new Bitmap();
+
+}
+
+class Flow extends Component<h2d.Flow> {
+
+	public function new() {
+		super("flow",h2d.Flow,function(p) return new h2d.Flow(p));
+	}
+
+	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;
+			}
+		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;
+		default:
+			return super.handleProp(o,p);
+		}
+		return true;
+	}
+
+	static var inst = new Flow();
+
+}

+ 78 - 0
h2d/uikit/Builder.hx

@@ -0,0 +1,78 @@
+package h2d.uikit;
+import h2d.uikit.BaseComponents;
+
+class Builder {
+
+	static var IS_EMPTY = ~/^[ \r\n\t]*$/;
+
+	public var errors : Array<String> = [];
+	var path : Array<String> = [];
+	var props : Property.PropertyParser;
+
+	public function new() {
+		props = new Property.PropertyParser();
+	}
+
+	function error( msg : String ) {
+		errors.push(path.join(".")+": "+msg);
+	}
+
+	function loadTile( path : String ) {
+		return hxd.res.Loader.currentInstance.load(path).toTile();
+	}
+
+	public function build( x : Xml, ?root : Element ) {
+		if( root == null )
+			root = new Element(new h2d.Flow(), Component.get("flow"));
+		switch( x.nodeType ) {
+		case Document:
+			for( e in x )
+				build(e, root);
+		case Comment, DocType, ProcessingInstruction:
+			// nothing
+		case CData, PCData:
+			if( !IS_EMPTY.match(x.nodeValue) ) {
+				// add text
+			}
+		case Element:
+			path.push(x.nodeName);
+			var comp = @:privateAccess Component.COMPONENTS.get(x.nodeName);
+			if( comp == null ) {
+				error("Uknown node");
+			} else {
+				var inst = new Element(comp.make(root.obj), comp, root);
+				var css = new CssParser();
+				for( a in x.attributes() ) {
+					var v = x.get(a);
+					var pval = try css.parseValue(v) catch( e : Dynamic ) {
+						path.push(a);
+						error("Invalid attribute value '"+v+"' ("+e+")");
+						path.pop();
+						continue;
+					}
+					var p = props.parse(a, pval);
+					if( p == null ) {
+						path.push(a);
+						error("Invalid attribute value '"+v+"'");
+						path.pop();
+						continue;
+					}
+					if( p == PUnknown ) {
+						path.push(a);
+						error("Unknown attribute");
+						path.pop();
+						continue;
+					}
+					if( !inst.setProp(p) )
+						error("Unsupported attribute "+a);
+				}
+				root = inst;
+			}
+			for( e in x )
+				build(e, root);
+			path.pop();
+		}
+		return root;
+	}
+
+}

+ 71 - 0
h2d/uikit/Component.hx

@@ -0,0 +1,71 @@
+package h2d.uikit;
+
+class Component<T:h2d.Object> {
+
+	public var name : String;
+	public var cl : Class<T>;
+	public var make : h2d.Object -> T;
+
+	public function new(name, cl,make) {
+		this.name = name;
+		this.cl = cl;
+		this.make = make;
+		COMPONENTS.set(name, this);
+	}
+
+	function getFlowParent( o : T ) {
+		return Std.instance(o.parent, h2d.Flow);
+	}
+
+	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;
+				}
+			}
+		default:
+			return false;
+		}
+		return true;
+	}
+
+	public static function get( name : String ) {
+		return COMPONENTS.get(name);
+	}
+
+	static var COMPONENTS = new Map<String,Component<Dynamic>>();
+	static var inst = new Component("object",h2d.Object,h2d.Object.new);
+
+}

+ 544 - 0
h2d/uikit/CssParser.hx

@@ -0,0 +1,544 @@
+package h2d.uikit;
+
+enum Token {
+	TIdent( i : String );
+	TString( s : String );
+	TInt( i : Int );
+	TFloat( f : Float );
+	TDblDot;
+	TSharp;
+	TPOpen;
+	TPClose;
+	TExclam;
+	TComma;
+	TEof;
+	TPercent;
+	TSemicolon;
+	TBrOpen;
+	TBrClose;
+	TDot;
+	TSpaces;
+	TSlash;
+	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 {
+	public var parent : Null<CssClass>;
+	public var node : Null<String>;
+	public var className : Null<String>;
+	public var pseudoClass : Null<String>;
+	public var id : Null<String>;
+	public function new() {
+	}
+}
+
+class CssParser {
+
+	var css : String;
+	var pos : Int;
+
+	var spacesTokens : Bool;
+	var tokens : Array<Token>;
+
+	public function new() {
+	}
+
+	function error( msg : String ) {
+		throw msg;
+	}
+
+	function unexpected( t : Token ) : Dynamic {
+		error("Unexpected " + tokenString(t));
+		return null;
+	}
+
+	function tokenString( t : Token ) {
+		return switch (t) {
+			case TIdent(i): i;
+			case TString(s): '"'+s+'"';
+			case TInt(i): ""+i;
+			case TFloat(f): ""+f;
+			case TDblDot: ":";
+			case TSharp: "#";
+			case TPOpen: "(";
+			case TPClose: ")";
+			case TExclam: "!";
+			case TComma: ",";
+			case TEof: "EOF";
+			case TPercent: "%";
+			case TSemicolon: ",";
+			case TBrOpen: "{";
+			case TBrClose: "}";
+			case TDot: ".";
+			case TSpaces: "space";
+			case TSlash: "/";
+			case TStar: "*";
+		};
+	}
+
+	function expect( t : Token ) {
+		var tk = readToken();
+		if( tk != t ) unexpected(tk);
+	}
+
+	inline function push( t : Token ) {
+		tokens.push(t);
+	}
+
+	function isToken(t) {
+		var tk = readToken();
+		if( tk == t ) return true;
+		push(tk);
+		return false;
+	}
+
+	public function parse( css : String ) {
+		this.css = css;
+		pos = 0;
+		tokens = [];
+		return parseStyle(TEof);
+	}
+
+	public function parseValue( valueStr : String ) {
+		this.css = valueStr;
+		pos = 0;
+		tokens = [];
+		var v = readValue();
+		expect(TEof);
+		return v;
+	}
+
+	function valueStr(v) {
+		return switch( v ) {
+		case VIdent(i): i;
+		case VString(s): '"' + s + '"';
+		case VUnit(f, unit): f + unit;
+		case VFloat(f): Std.string(f);
+		case VInt(v): Std.string(v);
+		case VHex(v,_): "#" + v;
+		case VList(l):
+			[for( v in l ) valueStr(v)].join(", ");
+		case VGroup(l):
+			[for( v in l ) valueStr(v)].join(" ");
+		case VCall(f,args): f+"(" + [for( v in args ) valueStr(v)].join(", ") + ")";
+		case VLabel(label, v): valueStr(v) + " !" + label;
+		case VSlash: "/";
+		}
+	}
+
+	function parseStyle( eof ) {
+		var style = [];
+		while( true ) {
+			if( isToken(eof) )
+				break;
+			var r = readIdent();
+			expect(TDblDot);
+			var v = readValue();
+			style.push({ name : r, value : v });
+			if( isToken(eof) )
+				break;
+			expect(TSemicolon);
+		}
+		return style;
+	}
+
+	public function parseRules( css : String ) {
+		this.css = css;
+		pos = 0;
+		tokens = [];
+		var rules = [];
+		while( true ) {
+			if( isToken(TEof) )
+				break;
+			var classes = readClasses();
+			expect(TBrOpen);
+			rules.push({ classes : classes, style : parseStyle(TBrClose) });
+		}
+		return rules;
+	}
+
+	public function parseClasses( css : String ) {
+		this.css = css;
+		pos = 0;
+		tokens = [];
+		var c = readClasses();
+		expect(TEof);
+		return c;
+	}
+
+	// ----------------- class parser ---------------------------
+
+	function readClasses() {
+		var classes = [];
+		while( true ) {
+			spacesTokens = true;
+			isToken(TSpaces); // skip
+			var c = readClass(null);
+			spacesTokens = false;
+			if( c == null ) break;
+			classes.push(c);
+			if( !isToken(TComma) )
+				break;
+		}
+		if( classes.length == 0 )
+			unexpected(readToken());
+		return classes;
+	}
+
+	function readClass( parent ) : CssClass {
+		var c = new CssClass();
+		c.parent = parent;
+		var def = false;
+		var last = null;
+		while( true ) {
+			var t = readToken();
+			if( last == null )
+				switch( t ) {
+				case TStar: def = true;
+				case TDot, TSharp, TDblDot: last = t;
+				case TIdent(i): c.node = i; def = true;
+				case TSpaces:
+					return def ? readClass(c) : null;
+				case TBrOpen, TComma, TEof:
+					push(t);
+					break;
+				default:
+					unexpected(t);
+				}
+			else
+				switch( t ) {
+				case TIdent(i):
+					switch( last ) {
+					case TDot: c.className = i; def = true;
+					case TSharp: c.id = i; def = true;
+					case TDblDot: c.pseudoClass = i; def = true;
+					default: unexpected(last);
+					}
+					last = null;
+				default:
+					unexpected(t);
+				}
+		}
+		return def ? c : parent;
+	}
+
+	// ----------------- value parser ---------------------------
+
+	function readIdent() {
+		var t = readToken();
+		return switch( t ) {
+		case TIdent(i): i;
+		default: unexpected(t);
+		}
+	}
+
+	function readValue(?opt)  : Value {
+		var t = readToken();
+		var v = switch( t ) {
+		case TSharp:
+			var h = readHex();
+			VHex(h,Std.parseInt("0x"+h));
+		case TIdent(i):
+			var start = pos;
+			var c = next();
+			if( isStrIdentChar(c) ) {
+				do c = next() while( isIdentChar(c) || isNum(c) || isStrIdentChar(c) );
+				pos--;
+				i += css.substr(start, pos - start);
+			} else
+				pos--;
+			VIdent(i);
+		case TString(s):
+			VString(s);
+		case TInt(i):
+			readValueUnit(i, i);
+		case TFloat(f):
+			readValueUnit(f, null);
+		case TSlash:
+			VSlash;
+		default:
+			if( !opt ) unexpected(t);
+			push(t);
+			null;
+		};
+		if( v != null ) v = readValueNext(v);
+		return v;
+	}
+
+	function readHex() {
+		var start = pos;
+		while( true ) {
+			var c = next();
+			if( (c >= "A".code && c <= "F".code) || (c >= "a".code && c <= "f".code) || (c >= "0".code && c <= "9".code) )
+				continue;
+			pos--;
+			break;
+		}
+		return css.substr(start, pos - start);
+	}
+
+	function readValueUnit( f : Float, ?i : Int ) {
+		var t = readToken();
+		return switch( t ) {
+		case TIdent(u):
+			if( u == "px" )
+				(i == null ? VFloat(f) : VInt(i)); // ignore "px" unit suffit
+			else
+				VUnit(f, u);
+		case TPercent:
+			VUnit(f, "%");
+		default:
+			push(t);
+			if( i != null )
+				VInt(i);
+			else
+				VFloat(f);
+		};
+	}
+
+	function readValueNext( v : Value ) : Value {
+		var t = readToken();
+		return switch( t ) {
+		case TPOpen:
+			switch( v ) {
+			case VIdent(i):
+				switch( i ) {
+				case "url":
+					readValueNext(VCall("url",[VString(readUrl())]));
+				default:
+					var args = switch( readValue() ) {
+					case VList(l): l;
+					case x: [x];
+					}
+					expect(TPClose);
+					readValueNext(VCall(i, args));
+				}
+			default:
+				push(t);
+				v;
+			}
+		case TExclam:
+			var t = readToken();
+			switch( t ) {
+			case TIdent(i):
+				VLabel(i, v);
+			default:
+				unexpected(t);
+			}
+		case TComma:
+			loopComma(v, readValue());
+		default:
+			push(t);
+			var v2 = readValue(true);
+			if( v2 == null )
+				v;
+			else
+				loopNext(v, v2);
+		}
+	}
+
+	function loopNext(v, v2) {
+		return switch( v2 ) {
+		case VGroup(l):
+			l.unshift(v);
+			v2;
+		case VList(l):
+			l[0] = loopNext(v, l[0]);
+			v2;
+		case VLabel(lab, v2):
+			VLabel(lab, loopNext(v, v2));
+		default:
+			VGroup([v, v2]);
+		};
+	}
+
+	function loopComma(v,v2) {
+		return switch( v2 ) {
+		case VList(l):
+			l.unshift(v);
+			v2;
+		case VLabel(lab, v2):
+			VLabel(lab, loopComma(v, v2));
+		default:
+			VList([v, v2]);
+		};
+	}
+
+	// ----------------- lexer -----------------------
+
+	inline function isSpace(c) {
+		return (c == " ".code || c == "\n".code || c == "\r".code || c == "\t".code);
+	}
+
+	inline function isIdentChar(c) {
+		return (c >= "a".code && c <= "z".code) || (c >= "A".code && c <= "Z".code) || (c == "-".code) || (c == "_".code);
+	}
+
+	inline function isStrIdentChar(c) {
+		return c == "/".code || c == ".".code;
+	}
+
+	inline function isNum(c) {
+		return c >= "0".code && c <= "9".code;
+	}
+
+	inline function next() {
+		return StringTools.fastCodeAt(css, pos++);
+	}
+
+	function readUrl() {
+		var c0 = next();
+		while( isSpace(c0) )
+			c0 = next();
+		var quote = c0;
+		if( quote == "'".code || quote == '"'.code ) {
+			pos--;
+			switch( readToken() ) {
+			case TString(s):
+				var c0 = next();
+				while( isSpace(c0) )
+					c0 = next();
+				if( c0 != ")".code )
+					error("Invalid char " + String.fromCharCode(c0));
+				return s;
+			case tk:
+				unexpected(tk);
+			}
+
+		}
+		var start = pos - 1;
+		while( true ) {
+			if( StringTools.isEof(c0) )
+				break;
+			c0 = next();
+			if( c0 == ")".code ) break;
+		}
+		return StringTools.trim(css.substr(start, pos - start - 1));
+	}
+
+	#if false
+	function readToken( ?pos : haxe.PosInfos ) {
+		var t = _readToken();
+		haxe.Log.trace(t, pos);
+		return t;
+	}
+
+	function _readToken() {
+	#else
+	function readToken() {
+	#end
+		var t = tokens.pop();
+		if( t != null )
+			return t;
+		while( true ) {
+			var c = next();
+			if( StringTools.isEof(c) )
+				return TEof;
+			if( isSpace(c) ) {
+				if( spacesTokens ) {
+					while( isSpace(next()) ) {
+					}
+					pos--;
+					return TSpaces;
+				}
+
+				continue;
+			}
+			if( isNum(c) || c == '-'.code ) {
+				var i = 0, neg = false;
+				if( c == '-'.code ) { c = "0".code; neg = true; }
+				do {
+					i = i * 10 + (c - "0".code);
+					c = next();
+				} while( isNum(c) );
+				if( c == ".".code ) {
+					var f : Float = i;
+					var k = 0.1;
+					while( isNum(c = next()) ) {
+						f += (c - "0".code) * k;
+						k *= 0.1;
+					}
+					pos--;
+					return TFloat(neg? -f : f);
+				}
+				pos--;
+				return TInt(neg ? -i : i);
+			}
+			if( isIdentChar(c) ) {
+				var pos = pos - 1;
+				var isStr = false;
+				do c = next() while( isIdentChar(c) || isNum(c) );
+				this.pos--;
+				return TIdent(css.substr(pos,this.pos - pos));
+			}
+			switch( c ) {
+			case ":".code: return TDblDot;
+			case "#".code: return TSharp;
+			case "(".code: return TPOpen;
+			case ")".code: return TPClose;
+			case "!".code: return TExclam;
+			case "%".code: return TPercent;
+			case ";".code: return TSemicolon;
+			case ".".code: return TDot;
+			case "{".code: return TBrOpen;
+			case "}".code: return TBrClose;
+			case ",".code: return TComma;
+			case "*".code: return TStar;
+			case "/".code:
+				var start = pos - 1;
+				if( (c = next()) != '*'.code ) {
+					pos--;
+					return TSlash;
+				}
+				while( true ) {
+					while( (c = next()) != '*'.code ) {
+						if( StringTools.isEof(c) ) {
+							pos = start;
+							error("Unclosed comment");
+						}
+					}
+					c = next();
+					if( c == "/".code ) break;
+					if( StringTools.isEof(c) ) {
+						pos = start;
+						error("Unclosed comment");
+					}
+				}
+				return readToken();
+			case "'".code, '"'.code:
+				var pos = pos;
+				var k;
+				while( (k = next()) != c ) {
+					if( StringTools.isEof(k) ) {
+						this.pos = pos;
+						error("Unclosed string constant");
+					}
+					if( k == "\\".code ) {
+						throw "todo";
+						continue;
+					}
+				}
+				return TString(css.substr(pos, this.pos - pos - 1));
+			default:
+			}
+			pos--;
+			error("Invalid char " + css.charAt(pos));
+		}
+		return null;
+	}
+
+}

+ 39 - 0
h2d/uikit/Element.hx

@@ -0,0 +1,39 @@
+package h2d.uikit;
+
+class Element {
+
+	public var obj : h2d.Object;
+	public var component : Component<Dynamic>;
+	public var classes : Array<String> = [];
+	public var parent : Element;
+	public var children : Array<Element> = [];
+	var needStyleRefresh : Bool = true;
+
+	public function new(obj,component,?parent) {
+		this.obj = obj;
+		this.component = component;
+		this.parent = parent;
+		if( parent != null ) parent.children.push(this);
+	}
+
+	public function remove() {
+		if( parent != null ) {
+			parent.children.remove(this);
+			parent = null;
+		}
+		obj.remove();
+	}
+
+	public function setProp( p : Property ) {
+		trace( Std.string(p) );
+		switch( p ) {
+		case PClasses(cl):
+			classes = cl.copy();
+			needStyleRefresh = true;
+			return true;
+		default:
+			return component.handleProp(obj, p);
+		}
+	}
+
+}

+ 301 - 0
h2d/uikit/Property.hx

@@ -0,0 +1,301 @@
+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
+	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;
+		}
+	}
+
+	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;
+	}
+
+	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;
+		}
+	}
+
+	function getIdent( v : CssParser.Value ) {
+		return switch( v ) { case VIdent(v): v; default: null; }
+	}
+
+	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);
+		}
+	}
+
+	static var customParsers = new Map<String,CssParser.Value -> { value : Dynamic, valid : Bool }>();
+
+}