package { import flash.display.*; import flash.events.*; import flash.geom.*; import flash.text.*; import flash.utils.*; import flash.net.*; import css.*; import cssdom.*; import kyotodemo.*; import org.libspark.thread.*; public class KyotoDemo extends STDOUT { [Embed(source="./kyotodemo/ascsslogo.png")] private var LogoEmbed:Class; private var LINEHEIGHT:Number = 35; private var mPrevCalcLines:int = 0; public static const GRAD_COLORS:Array = [0x111111, 0x333333, 0x555555]; public static const GRAD_ALPHAS:Array = [1, 1, 1]; public static const GRAD_RATIOS:Array = [0, 140, 255]; private var mGradTrans:Matrix = new Matrix(); private var mSelectorMap:Dictionary; private var mWidth:int = 1024; private var mHeight:int = 768; private var mXMLField:TextField; private var mCSSField:TextField; private var mEFormat:TextFormat; private var mRFormat:TextFormat; private var mXMLErrorField:TextField; private var mCSSErrorField:TextField; private var mBGLayer:Sprite; private var mSelectorMarkers:Array; private var mMarkerLayer:Sprite; private var mElementMarkerLayer:Sprite; private var mSelectorEdgeLayer:EdgeLayer; private var mDocument:XMLDocument; private var mPrevMatchedDocument:XMLDocument = null; private var mStyleSheet:StyleList; private var mEntireAnimation:EntireAnimationThread; private var mPrevEdgesCount:int = -1; private var mBGDisplayList:BGDisplayList; private var mImageLib:Dictionary = new Dictionary(); private static const PENDING:uint = 1; private static const FAILED:uint = 2; function KyotoDemo() { Thread.initialize(new EnterFrameThreadExecutor()); var logo:Bitmap = new LogoEmbed(); logo.x = 210; logo.y = 10; addChild(logo); mBGLayer = new Sprite(); addChild(mBGLayer); mEFormat = new TextFormat(); mEFormat.font = "Arial"; mEFormat.size = 30; mEFormat.color = 0xffffff; mRFormat = new TextFormat(); mRFormat.font = "Arial"; mRFormat.size = 14; mRFormat.color = 0xff6633; mXMLField = new TextField(); mXMLField.defaultTextFormat = mEFormat; mXMLField.x = 40; mXMLField.y = 80; mXMLField.text = "\n \n"; new TabSender(mXMLField); setTextFieldProperties(mXMLField); mXMLField.addEventListener(Event.CHANGE, onXMLChanged); mCSSField = new TextField(); mCSSField.defaultTextFormat = mEFormat; mCSSField.x = 540; mCSSField.y = 80; new TabSender(mCSSField); setTextFieldProperties(mCSSField); mCSSField.addEventListener(Event.CHANGE, onCSSChanged); addChild(mXMLField); addChild(mCSSField); mXMLErrorField = new TextField(); mXMLErrorField.x = 40; mXMLErrorField.y = 660; addChild(mXMLErrorField); setErrorTextFieldProperties(mXMLErrorField); mCSSErrorField = new TextField(); mCSSErrorField.x = 540; mCSSErrorField.y = 660; addChild(mCSSErrorField); setErrorTextFieldProperties(mCSSErrorField); mSelectorEdgeLayer = new EdgeLayer(); addChild(mSelectorEdgeLayer); mEntireAnimation = new EntireAnimationThread(mSelectorEdgeLayer); mEntireAnimation.start(); mMarkerLayer = new Sprite(); addChild(mMarkerLayer); mElementMarkerLayer = new Sprite(); addChild(mElementMarkerLayer); drawBase(); removeChild(tx); addChild(tx); tx.width = 300; tx.height = 300; tx.textColor = 0xffffff; // tx.visible = false; onXMLChanged(null); } private function setErrorTextFieldProperties(t:TextField):void { t.width = 460; t.height = 80; t.defaultTextFormat = mRFormat; t.mouseEnabled = false; t.tabEnabled = false; t.multiline = true; t.border = true; t.borderColor = 0x999999; } private function setTextFieldProperties(t:TextField):void { t.width = 460; t.height = 560; t.type = TextFieldType.INPUT; t.tabEnabled = false; t.multiline = true; t.border = true; t.borderColor = 0x999999; } private function drawBase():void { var g:Graphics = graphics; mGradTrans.createGradientBox(mHeight, mHeight, Math.PI/2, 0, 0); g.clear(); g.lineStyle(0, 0x444444); g.beginGradientFill(GradientType.LINEAR, GRAD_COLORS, GRAD_ALPHAS, GRAD_RATIOS, mGradTrans); g.drawRect(0, 0, mWidth, mHeight); } private function onCSSChanged(e:Event):void { mStyleSheet = null; clearCSSError(); var parser:ASCSS = new ASCSS(mCSSField.text, new DebugOutProxy(this)); parser.parse(); createSelectorMarkers(parser.sheet); mStyleSheet = parser.sheet; calcLineHeight(); showSelectorMarkers(); doMatching(); } private function onXMLChanged(e:Event):void { clearXMLError(); mDocument = null; try{ mDocument = new XMLDocument(mXMLField.text); XMLElementWrapper.createFor(mDocument.root, mDocument); } catch(e:Error) { outXMLError(e.toString()); } calcLineHeight(); showElementMarkers(); doMatching(); } private function calcLineHeight():void { var h:int = mXMLField.textHeight; var maxl:int = mXMLField.numLines; if (mCSSField.numLines > maxl) { maxl = mCSSField.numLines; h = mCSSField.textHeight; } if (maxl > mPrevCalcLines && maxl > 0) { LINEHEIGHT = Number(h) / Number(maxl); } } public function outCSSError(s:String):void { mCSSErrorField.appendText(s+"\n"); } public function clearCSSError():void { mCSSErrorField.text = ""; } public function outXMLError(s:String):void { mXMLErrorField.appendText(s+"\n"); } public function clearXMLError():void { mXMLErrorField.text = ""; } // ------------------------------------------------------------------------------ private function createSelectorMarkers(sheet:StyleList):void { mSelectorMarkers = []; mSelectorMap = new Dictionary(true); var len:int = sheet.length; for (var i:int = 0;i < len;i++) { var s:StyleBase = sheet.item(i); if (s is CSSStyleRule) { addSelectorFromRule(mSelectorMarkers, CSSStyleRule(s)); } } } private function addSelectorFromRule(a:Array, r:CSSStyleRule):void { var m:SelectorMarker = new SelectorMarker(); m.selector = r.selector; m.lineno = r.startLineNo; a.push(m); mSelectorMap[m.selector] = m; } private function showElementMarkers():void { var len:int = mElementMarkerLayer.numChildren; for (var i:int = 0;i < len;i++) mElementMarkerLayer.removeChildAt(0); if (!mDocument) return; var list:Array = mDocument.elementMetadatas; var m:Marker; for each(var md:ElementData in list) { m = new Marker(0, (md.end_lineno - md.lineno) * LINEHEIGHT); md.mx = 30 + md.nest*17; md.my = 98 + md.lineno * LINEHEIGHT; m.x = md.mx; m.y = md.my; mElementMarkerLayer.addChild(m); } } private function showSelectorMarkers():void { var len:int = mMarkerLayer.numChildren; var i:int; var m:Marker, md:SelectorMarker; for (i = 0;i < len;i++) mMarkerLayer.removeChildAt(0); len = mSelectorMarkers.length; for (i = 0;i < len;i++) { md = mSelectorMarkers[i] as SelectorMarker; m = new Marker(1); md.mx = 530; md.my = 98 + md.lineno * LINEHEIGHT; m.x = md.mx; m.y = md.my; mMarkerLayer.addChild(m); } } private function doMatching():void { mSelectorEdgeLayer.clear(); if (!mDocument || !mStyleSheet) return; var list:Array = mDocument.elementMetadatas; var sel:CSSStyleSelector; mDocument.clearStyleSheets(); mDocument.appendStyleSheet(mStyleSheet as css.StyleSheet); sel = mDocument.createStyleSelector() if (!sel) return; mPrevMatchedDocument = mDocument; for each(var md:ElementData in list) { var elem:IASCSSElement = md.wrapper; sel.initElementAndPseudoState(elem); sel.initForStyleResolve(elem, null); var authorStyle:CSSRuleSet = sel.authorStyle; var indexes:Array = [-1, -1]; sel.matchRules(authorStyle, indexes); if (!sel.matchedRulesIsEmpty) { var rindex:int = 0; mSelectorEdgeLayer.nextSet(); for each(var rd:CSSRuleData in sel.matchedRulesList) { if (rd.selector._owner) { var mk:SelectorMarker = mSelectorMap[rd.selector._owner.selector]; mSelectorEdgeLayer.addEdge(mk.mx, mk.my, md.mx, md.my, rindex++); } } } sel.style = XMLElementWrapper(elem).newRenderStyle(); // first pass // normal decls sel.applyDeclarations(true, false); // override important decls sel.applyDeclarations(true, true); // second pass // normal decls sel.applyDeclarations(false, false); // override important decls sel.applyDeclarations(false, true); } if (mPrevEdgesCount != mSelectorEdgeLayer.count) mEntireAnimation.startAnimation(); showComputedStyles(); mPrevEdgesCount = mSelectorEdgeLayer.count; } private static const rxStartTag:RegExp = /^<[-_a-zA-Z0-9]+[^>]*>/ private static const rxEndTag:RegExp = /^<\/[-_a-zA-Z0-9]+[^>]*>/ private static const rxEmptyTag:RegExp = /\/[ \t]*>/ private static const rxCommentStart:RegExp = /^/ private function showComputedStyles():void { mXMLField.setTextFormat(mEFormat); mBGLayer.graphics.clear(); var ed:ElementData, wrapper:XMLElementWrapper var list:Array = mDocument.elementMetadatas.slice(); if (!list) return; var contextStack:Array = []; var src:String = mXMLField.text; if (!src) return; var rxr:Object; var curpos:int = 0; var fwd:String; for (;;) { curpos = src.indexOf('<', curpos); if (curpos < 0) break; fwd = src.substring(curpos); rxr = rxStartTag.exec(fwd); if (rxr) { var tag:String = rxr[0]; ed = list.shift(); wrapper = ed.wrapper; if (rxEmptyTag.exec(tag)) { // closed immediately ed.start_index = curpos + rxr.index; ed.end_index = curpos + rxr.index + tag.length; } else { contextStack.push(ed); ed.start_index = curpos + rxr.index; } curpos += rxr.index + tag.length; continue; } rxr = rxCommentStart.exec(fwd); if (rxr) { curpos += rxr.index + rxr[0].length; rxr = rxCommentEnd.exec(fwd); if (!rxr) break; curpos += rxr.index + rxr[0].length; continue; } rxr = rxEndTag.exec(fwd); if (rxr) { curpos += rxr.index + rxr[0].length; ed = contextStack.pop() as ElementData; ed.end_index = curpos; continue; } break; } var cssv:CSSValue; var g:Graphics = mBGLayer.graphics; list = mDocument.elementMetadatas.slice(); mBGDisplayList = new BGDisplayList(); mBGDisplayList.g = g; for each(ed in list) { var rs:RenderStyle = ed.wrapper.renderStyle; var color:uint = lookupColorValue(rs, 0, CSSPropertyID.CSSPropertyColor); var bgcolor:uint = lookupColorValue(rs, 0, CSSPropertyID.CSSPropertyBackgroundColor); var bgurl:String = null; if (color != 0) mXMLField.setTextFormat(createColorFormat(color), ed.start_index, ed.end_index); if (rs.isSetPropId(CSSPropertyID.CSSPropertyBackgroundImage)) { cssv = rs.getCSSValueByPropId(CSSPropertyID.CSSPropertyBackgroundImage); if (cssv is CSSImageValue) bgurl = CSSImageValue(cssv).getStringValue(); } var di:BGDisplayItem = null; if (bgcolor != 0 || bgurl) { di = new BGDisplayItem(); di.color = bgcolor & 0xffffff; di.url = bgurl; di.y = mXMLField.y + ed.lineno * LINEHEIGHT; di.h = (ed.end_lineno-ed.lineno+1) * LINEHEIGHT; mBGDisplayList.list.push(di); } } runDisplayList(); } private function runDisplayList():void { if (!mBGDisplayList) return; var g:Graphics = mBGDisplayList.g; for each(var di:BGDisplayItem in mBGDisplayList.list) { if (di.color != 0) { g.beginFill(di.color); g.drawRect(mXMLField.x, di.y, mXMLField.width, di.h); } if (di.url) { var b:BitmapData = null; if (mImageLib[di.url] != PENDING && mImageLib[di.url] != FAILED) { b = mImageLib[di.url]; if (!b) { var ldr:ImageLoader = new ImageLoader(di.url); ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageLoaded); mImageLib[di.url] = PENDING; ldr.load(new URLRequest(di.url)); } } if (b) { g.beginBitmapFill(b); g.drawRect(mXMLField.x, di.y, mXMLField.width, di.h); } } } } private function onImageLoaded(e:Event):void { var ldr:ImageLoader = e.target.loader as ImageLoader; if (ldr.content is Bitmap) { mImageLib[ldr.key] = Bitmap(ldr.content).bitmapData; runDisplayList(); } else mImageLib[ldr.key] = FAILED; } protected function lookupColorValue(rs:RenderStyle, defaultColor:uint, propid:int):uint { if (rs) { if (rs.isSetPropId(propid)) { var cssv:CSSValue = rs.getCSSValueByPropId(propid); if (cssv is CSSPrimitiveValue) { return CSSStyleSelector.getColorFromPrimitiveValue(CSSPrimitiveValue(cssv), defaultColor); } } } return defaultColor; } private function createColorFormat(c:uint):TextFormat { var f:TextFormat = new TextFormat; f.color = c & 0xffffff; return f; } } } class ImageLoader extends flash.display.Loader { import flash.display.*; import flash.events.*; public var key:String; function ImageLoader(k:String) { key = k; contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIOError); } private function onIOError(e:IOErrorEvent):void {} } class SelectorMarker { import css.*; public var selector:CSSSelector; public var lineno:int; public var mx:int; public var my:int; } class TabSender { import flash.text.*; import flash.events.*; private var mTx:TextField; function TabSender(t:TextField) { mTx = t; t.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); } public function onKeyDown(e:KeyboardEvent):void { if (e.keyCode == 9) { mTx.replaceSelectedText(" "); } e.updateAfterEvent(); } } class DebugOutProxy { private var d:KyotoDemo; function DebugOutProxy(_d:KyotoDemo) { d = _d; } public function puts(s:*):void { d.outCSSError((s != null) ? s.toString() : "null"); } } class EntireAnimationThread extends org.libspark.thread.Thread { import flash.display.*; private var mTarget:Sprite; private var mCount:int = -1; function EntireAnimationThread(s:Sprite) { mTarget = s; } protected override function run():void { if (mCount < 0) { wait(0); next(animate); } else next(animate); } public function startAnimation():void { mCount = 0; notify(); } protected function animate():void { if (++mCount > 5) { wait(0); next(animate); return; } mTarget.alpha = mCount/5.0; next(animate); } } class BGDisplayList { import flash.display.*; public var g:Graphics; public var list:Array = []; } class BGDisplayItem { public var color:uint; public var url:String = null; public var y:int, h:int; }