PageRenderTime 77ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/src/org/openPyro/core/UIControl.as

http://github.com/arpit/openpyro
ActionScript | 541 lines | 314 code | 67 blank | 160 comment | 58 complexity | b6e99e97ac226cd389ac21da8543d02f MD5 | raw file
  1. package org.openPyro.core{
  2. import flash.display.DisplayObject;
  3. import flash.display.Sprite;
  4. import flash.events.Event;
  5. import flash.events.MouseEvent;
  6. import org.openPyro.events.PyroEvent;
  7. import org.openPyro.managers.DragManager;
  8. import org.openPyro.managers.SkinManager;
  9. import org.openPyro.managers.TooltipManager;
  10. import org.openPyro.managers.events.DragEvent;
  11. import org.openPyro.painters.IPainter;
  12. import org.openPyro.skins.ISkin;
  13. import org.openPyro.skins.ISkinClient;
  14. [Event(name="dragDrop", type="org.openPyro.managers.events.DragEvent")]
  15. /**
  16. * The UIControl is the basic building block for
  17. * pyro controls. UIControls extend from Measurable control so
  18. * can use all the different sizing properties like explicit or percent widths/heights.
  19. * UIControls can also include other UIControls
  20. * as children but cannot use layouts to position them. To use layouts,
  21. * use UIContainers.
  22. *
  23. * UIControls also implement the ISkinClient interface so can be skinned and
  24. * the ISkin interface so themselves can be used as base classes for skins
  25. */
  26. public class UIControl extends MeasurableControl implements ISkinClient, ISkin{
  27. public function UIControl() {
  28. super();
  29. this.tabEnabled=false;
  30. }
  31. /**
  32. * @inheritDoc
  33. */
  34. override public function initialize():void
  35. {
  36. super.initialize()
  37. if(_skin || !_styleName ) return;
  38. SkinManager.getInstance().registerSkinClient(this, _styleName);
  39. }
  40. /**
  41. * @inheritDoc
  42. */
  43. override public function addChild(child:DisplayObject):DisplayObject
  44. {
  45. return addChildAt(child, this.numChildren);
  46. }
  47. /**
  48. * @inheritDoc
  49. */
  50. override public function addChildAt(child:DisplayObject,index:int):DisplayObject
  51. {
  52. var ch:DisplayObject = super.$addChildAt(child, index);
  53. if(child is MeasurableControl)
  54. {
  55. var control:MeasurableControl = child as MeasurableControl;
  56. control.parentContainer = this;
  57. control.addEventListener(PyroEvent.SIZE_INVALIDATED, invalidateSize);
  58. control.addEventListener(PyroEvent.SIZE_CHANGED, queueValidateDisplayList);
  59. control.doOnAdded()
  60. }
  61. forceInvalidateDisplayList=true;
  62. this.invalidateSize()
  63. return ch
  64. }
  65. /**
  66. * @inheritDoc
  67. */
  68. override public function measure():void{
  69. if(isNaN(this._explicitWidth) &&
  70. (!isNaN(this._percentWidth) || !isNaN(_percentUnusedWidth)))
  71. {
  72. calculateMeasuredWidth()
  73. }
  74. if(isNaN(this._explicitHeight) &&
  75. (!isNaN(this._percentHeight) || !isNaN(_percentUnusedHeight)))
  76. {
  77. calculateMeasuredHeight();
  78. }
  79. this.needsMeasurement=false;
  80. }
  81. /**
  82. * @inheritDoc
  83. */
  84. override public function checkDisplayListValidation():void
  85. {
  86. doChildBasedValidation()
  87. super.checkDisplayListValidation()
  88. }
  89. /**
  90. * While UIControls can be sized based on the dimensions of the parent
  91. * container, if the explicit or percent dimension values are not specified,
  92. * the UIControl can look at its children's dimensions and base its
  93. * sizing off them.
  94. *
  95. * For example, Label controls can look at the size of the text rendered
  96. * by them to define their own width.
  97. */
  98. protected function doChildBasedValidation():void
  99. {
  100. var child:DisplayObject;
  101. if(isNaN(this._explicitWidth) && isNaN(this._percentWidth) && isNaN(_percentUnusedWidth))
  102. {
  103. var maxW:Number = 0
  104. for(var j:uint=0; j<this.numChildren; j++){
  105. child = this.getChildAt(j);
  106. if(child.width > maxW)
  107. {
  108. maxW = child.width;
  109. }
  110. }
  111. super.measuredWidth = maxW + _padding.left + _padding.right;
  112. }
  113. if(isNaN(this._explicitHeight) && isNaN(this._percentHeight) && isNaN(_percentUnusedHeight))
  114. {
  115. var maxH:Number = 0
  116. for(var k:uint=0; k<this.numChildren; k++){
  117. child = this.getChildAt(k);
  118. if(child.height > maxH)
  119. {
  120. maxH = child.height;
  121. }
  122. }
  123. super.measuredHeight = maxH + _padding.top + _padding.bottom;
  124. }
  125. }
  126. /**
  127. * Overrides the set measuredWidth property from
  128. * <code>MeasurableControl</code> to invalidate children
  129. * (UIControl acknowledges that it can have children whose
  130. * dimensions are based on its own)
  131. */
  132. override public function set measuredWidth(w:Number):void{
  133. if(w == _measuredWidth) return;
  134. dimensionsChanged = true;
  135. _measuredWidth = w;
  136. for(var i:uint=0; i<this.numChildren; i++){
  137. var child:UIControl = this.getChildAt(i) as UIControl;
  138. if(!child) continue;
  139. child.needsMeasurement=true;
  140. }
  141. dispatchEvent(new PyroEvent(PyroEvent.SIZE_INVALIDATED));
  142. invalidateDisplayList()
  143. }
  144. /**
  145. * Overrides the set measuredHeight property from
  146. * <code>MeasurableControl</code> to invalidate children
  147. * (UIControl acknowledges that it can have children whose
  148. * dimensions are based on its own)
  149. */
  150. override public function set measuredHeight(h:Number):void{
  151. if(h == _measuredHeight) return;
  152. this.dimensionsChanged = true;
  153. _measuredHeight = h;
  154. for(var i:uint=0; i<this.numChildren; i++){
  155. var child:UIControl = this.getChildAt(i) as UIControl;
  156. if(!child) continue;
  157. child.needsMeasurement=true;
  158. }
  159. dispatchEvent(new PyroEvent(PyroEvent.SIZE_INVALIDATED));
  160. invalidateDisplayList()
  161. }
  162. /**
  163. * When measure is called, it uses the widthForMeasurement and
  164. * heightForMeasurement to calculate the sizes for
  165. * percent-dimension based children
  166. */
  167. public function widthForMeasurement():Number{
  168. return this.width
  169. }
  170. /**
  171. * When measure is called, it uses the widthForMeasurement and
  172. * heightForMeasurement to calculate the sizes for
  173. * percent-dimension based children
  174. */
  175. public function heightForMeasurement():Number{
  176. return this.height
  177. }
  178. /**
  179. * @inhertiDoc
  180. */
  181. override public function removeChild(d:DisplayObject):DisplayObject{
  182. var d2:DisplayObject = super.removeChild(d);
  183. this.invalidateSize()
  184. return d2;
  185. }
  186. /**
  187. * @inheritDoc
  188. */
  189. override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{
  190. super.updateDisplayList(unscaledWidth, unscaledHeight);
  191. //$width = unscaledWidth
  192. //$height = unscaledHeight;
  193. if(this._backgroundPainter){
  194. this.graphics.clear()
  195. _backgroundPainter.draw(Sprite(this).graphics, unscaledWidth,unscaledHeight);
  196. }
  197. if(_skin && !(_skin is MeasurableControl) && (_skin is DisplayObject)){
  198. DisplayObject(_skin).width = unscaledWidth;
  199. DisplayObject(_skin).height = unscaledHeight;
  200. }
  201. doLayoutChildren()
  202. }
  203. public function doLayoutChildren():void
  204. {
  205. for(var i:uint=0; i<this.numChildren; i++)
  206. {
  207. var child:DisplayObject = this.getChildAt(i);
  208. // Skin elements do not get positioned. Its upto the
  209. // skin to deal with the padding
  210. if(child == _skin) continue;
  211. //child.x = padding.left;
  212. //child.y = padding.top;
  213. }
  214. }
  215. /////////////// Painters Implementation //////////////
  216. protected var _backgroundPainter:IPainter;
  217. /**
  218. * UIControls can have a backgroundPainter object attached that is
  219. * triggered everytime updateDisplayList is called.
  220. *
  221. * @see org.openPyro.painters
  222. */
  223. public function get backgroundPainter():IPainter
  224. {
  225. return _backgroundPainter;
  226. }
  227. /**
  228. * Sets the an IPainter as the backgroundPainter of this control.
  229. *
  230. * BackgroundPainters paint the graphics context of the UIControl everytime
  231. * updateDisplaylist is called.
  232. *
  233. * If the value is set to null, the graphics context is cleared.
  234. *
  235. * @see org.openpyro.painters
  236. */
  237. public function set backgroundPainter(painter:IPainter):void{
  238. this._backgroundPainter = painter;
  239. if(painter == null){
  240. this.graphics.clear();
  241. }
  242. this.invalidateDisplayList();
  243. }
  244. ///////////////////// Skinning implementation ////////////
  245. protected var _styleName:String
  246. /**
  247. * Defines the skin this component is registered to.
  248. * As long as a skin is registered with the same
  249. * name as this value, this control will get that
  250. * skin when instantiated or when that definition
  251. * changes.
  252. *
  253. * @see org.openPyro.managers.SkinManager
  254. */
  255. public function get styleName():String
  256. {
  257. return _styleName;
  258. }
  259. /**
  260. * @private
  261. */
  262. public function set styleName(selector:String):void
  263. {
  264. if(_styleName == selector) return;
  265. if(initialized){
  266. SkinManager.getInstance().unregisterSkinClient(this,_styleName);
  267. SkinManager.getInstance().registerSkinClient(this, selector)
  268. }
  269. this._styleName = selector;
  270. }
  271. public function dispose():void{}
  272. protected var _skinnedControl:UIControl;
  273. public function set skinnedControl(uic:UIControl):void
  274. {
  275. if(_skinnedControl){
  276. _skinnedControl.removeEventListener(Event.RESIZE, onSkinnedControlResize)
  277. }
  278. _skinnedControl = uic;
  279. _skinnedControl.addEventListener(Event.RESIZE, onSkinnedControlResize)
  280. }
  281. public function get skinnedControl():UIControl
  282. {
  283. return _skinnedControl;
  284. }
  285. /**
  286. * Event handler for when the UIControl is applied as a Skin
  287. * and the control it is skinning is resized.
  288. *
  289. * @see org.openPyro.skins
  290. */
  291. protected function onSkinnedControlResize(event:Event):void
  292. {
  293. this.width = _skinnedControl.width;
  294. this.height = _skinnedControl.height;
  295. }
  296. protected var _skin:ISkin;
  297. public function set skin(skinImpl:ISkin):void{
  298. if(!skinImpl) return;
  299. if(this._skin)
  300. {
  301. _skin.dispose();
  302. _skin = null;
  303. }
  304. _skin = skinImpl;
  305. _skin.skinnedControl = this;
  306. if(_skin is UIControl){
  307. addChild(UIControl(_skin))
  308. //UIControl(_skin).percentUnusedWidth = 100
  309. //UIControl(_skin).percentUnusedHeight = 100
  310. }
  311. }
  312. ///////// handle drag /////
  313. protected var _dragEnabled:Boolean = false;
  314. public function get dragEnabled():Boolean{
  315. return _dragEnabled;
  316. }
  317. public function set dragEnabled(b:Boolean):void{
  318. _dragEnabled = b
  319. if(_dragEnabled){
  320. this.addEventListener(MouseEvent.MOUSE_DOWN, handlePreDragMouseDown);
  321. }
  322. else{
  323. this.removeEventListener(MouseEvent.MOUSE_DOWN, handlePreDragMouseDown);
  324. }
  325. }
  326. protected var _dropEnabled:Boolean = false;
  327. public function set dropEnabled(b:Boolean):void{
  328. _dropEnabled = b;
  329. if(_dropEnabled){
  330. DragManager.registerDropClient(this);
  331. }
  332. else{
  333. DragManager.removeDropClient(this);
  334. }
  335. }
  336. protected var isMouseDown:Boolean = false;
  337. public var dragData:Object = {source:this};
  338. /**
  339. * This function is called if the UIControl instance is drag enabled
  340. * and the user clicks the mouse down. The control then waits for the
  341. * mouse to move for dispatching the DragEvent.DRAG_START event. That
  342. * event can be used to trigger DragManager's doDrag() function to
  343. * implement Drag And Drop
  344. */
  345. protected function handlePreDragMouseDown(event:Event):void{
  346. isMouseDown = true;
  347. DragManager.doDrag(this, dragData, getDragImage(), null, true);
  348. //this.addEventListener(MouseEvent.MOUSE_MOVE, dispatchDragStart);
  349. }
  350. protected function getDragImage():DisplayObject{
  351. return null;
  352. }
  353. protected function dispatchDragStart(event:MouseEvent):void{
  354. dispatchEvent(new DragEvent(DragEvent.DRAG_START));
  355. this.removeEventListener(MouseEvent.MOUSE_MOVE, dispatchDragStart);
  356. }
  357. ////////////////////// ToolTip //////////////////////////
  358. protected var _toolTipData:*;
  359. protected var toolTipRenderer:Class;
  360. public function set toolTip(data:*):void{
  361. _toolTipData = data;
  362. if(_toolTipData){
  363. this.addEventListener(MouseEvent.MOUSE_OVER,
  364. function(event:MouseEvent):void{
  365. TooltipManager.getInstance().showToolTip(event, _toolTipData, toolTipRenderer)
  366. })
  367. this.addEventListener(MouseEvent.MOUSE_OUT,
  368. function(event:MouseEvent):void{
  369. TooltipManager.getInstance().hideToolTip()
  370. })
  371. }
  372. }
  373. ///////////////////// Padding //////////////////////////////
  374. protected var _padding:Padding = new Padding();
  375. /**
  376. * Paddings define the unusable space within
  377. * UIContainers that should not be used for
  378. * measurement and layout. Similar to
  379. * HTML/CSS padding
  380. */
  381. public function get padding():Padding
  382. {
  383. return _padding;
  384. }
  385. /*override public function setActualSize(w:Number,h:Number):void
  386. {
  387. this._explicitWidth = w;
  388. this._explicitHeight = h;
  389. for(var i:uint=0; i<this.numChildren; i++){
  390. var uic:UIControl = this.getChildAt(i) as UIControl
  391. if(!uic) continue
  392. uic.validateSize();
  393. uic.validateDisplayList();
  394. }
  395. this.updateDisplayList(this.getExplicitOrMeasuredWidth(), this.getExplicitOrMeasuredHeight());
  396. }*/
  397. /**
  398. * @private
  399. */
  400. public function set padding(p:Padding):void
  401. {
  402. if(_padding == p) return;
  403. _padding = p;
  404. if(!initialized) return;
  405. this.invalidateSize();
  406. }
  407. //////////////////// End Skinning Implementation ///////////
  408. /**
  409. * Convinience function for setting width and height
  410. * in one call. The parameters can either be Strings
  411. * or Numbers. When passing strings, you can append
  412. * a '%' character at the end of the string to set a
  413. * percent value
  414. *
  415. * @param w Width either as a Number or as a String
  416. * ending with a % character
  417. * @param h Height either as a Number or as a String
  418. * ending with a % character
  419. *
  420. * @throws Error if the datatype passed in is not a Number
  421. * or String
  422. */
  423. public function size(w:*, h:*):UIControl{
  424. var str:String
  425. if(w is Number){
  426. this.width = w
  427. }
  428. else if(w is String){
  429. str = String(w)
  430. if(str.charAt(str.length-1)== "%"){
  431. str = str.substring(0, str.length-1)
  432. this.percentUnusedWidth = Number(str);
  433. }
  434. }
  435. else{
  436. throw new Error('SetSize can only take a string or number as a param')
  437. }
  438. if(h is Number){
  439. this.height = h
  440. }
  441. else if(h is String){
  442. str = String(h)
  443. if(str.charAt(str.length-1)== "%"){
  444. str = str.substring(0, str.length-1)
  445. this.percentUnusedHeight = Number(str);
  446. }
  447. }
  448. else{
  449. throw new Error('SetSize can only take a string or number as a param')
  450. }
  451. return this;
  452. }
  453. public function position(x:Number, y:Number):UIControl{
  454. this.x = x;
  455. this.y = y;
  456. return this;
  457. }
  458. /**
  459. * Calls a function after a given number of frames (defaults to 1 frame).
  460. * This a good way to break processing of large data between frames to
  461. * keep ActionScript responsive.
  462. */
  463. public function doLater(callback:Function, numFrames:int=1):void{
  464. var count:int=0;
  465. this.addEventListener(Event.ENTER_FRAME, function(event:Event):void{
  466. count++;
  467. if(count==numFrames){
  468. removeEventListener(Event.ENTER_FRAME, arguments.callee);
  469. callback();
  470. }
  471. });
  472. }
  473. }
  474. }