PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/parser/lib/as3-parser/src/test/resources/examples/FisheyeBase.as

http://moonshineproject.googlecode.com/
ActionScript | 576 lines | 381 code | 65 blank | 130 comment | 51 complexity | 6a9b3a02b9b35d5a23e890e99349593f MD5 | raw file
Possible License(s): LGPL-3.0, MIT, Apache-2.0, GPL-3.0
  1. /**
  2. * Copyright (c) 2009, Adobe Systems, Incorporated
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions
  7. * are met:
  8. *
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * * Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in
  13. * the documentation and/or other materials provided with the
  14. * distribution.
  15. * * Neither the name of the Adobe Systems, Incorporated. nor the names of
  16. * its contributors may be used to endorse or promote products derived
  17. * from this software without specific prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
  22. * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
  23. * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  24. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  25. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  26. * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  27. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  28. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. */
  31. package qs.controls.fisheyeClasses
  32. {
  33. import flash.display.DisplayObject;
  34. import flash.display.Sprite;
  35. import flash.events.Event;
  36. import flash.events.MouseEvent;
  37. import mx.core.ClassFactory;
  38. import mx.core.IDataRenderer;
  39. import mx.core.IFactory;
  40. import mx.core.UIComponent;
  41. import qs.controls.CachedLabel;
  42. import qs.controls.LayoutAnimator;
  43. /** the horizontal alignment. */
  44. [Style( name="horizontalAlign", type="String", enumeration="left,center,right,justified", inherit="no" )]
  45. /** the vertical alignment */
  46. [Style( name="verticalAlign", type="String", enumeration="top,center,bottom,justified", inherit="no" )]
  47. /** the amount of space, in pixels, between invidual items */
  48. [Style( name="defaultSpacing", type="Number", inherit="no" )]
  49. /** the amount of space, in pixels, between invidual items when hilighted*/
  50. [Style( name="hilightSpacing", type="Number", inherit="no" )]
  51. /** the property on the item renderer to assign to when the renderer' state changes */
  52. [Style( name="stateProperty", type="String", inherit="no" )]
  53. /** the value to assign to 'stateProperty' on the renderer when the item is hilighted */
  54. [Style( name="rolloverValue", type="String", inherit="no" )]
  55. /** the value to assign to 'stateProperty' on the renderer when the item is selected */
  56. [Style( name="selectedValue", type="String", inherit="no" )]
  57. /** the value to assign to 'stateProperty' on the renderer when some other item is selected */
  58. [Style( name="unselectedValue", type="String", inherit="no" )]
  59. /** the value to assign to 'stateProperty' on the renderer when the item is in its default state */
  60. [Style( name="defaultValue", type="String", inherit="no" )]
  61. /** the scale factor assigned to renderers when no item is hilighted or selected */
  62. [Style( name="defaultScale", type="Number", inherit="no" )]
  63. /** the minimum scale factor assigned to renderers on screen. The actual scale factor assigned to
  64. * an item will range between minScale and hilightMaxScale based on its distance from the hilighted or selected item */
  65. [Style( name="hilightMinScale", type="Number", inherit="no" )]
  66. /** the maximum scale factor assigned to renderers on screen. The actual scale factor assigned to
  67. * an item will range between minScale and hilightMaxScale based on its distance from the hilighted or selected item */
  68. [Style( name="hilightMaxScale", type="Number", inherit="no" )]
  69. /** how quickly or slowly items scale down from the hilightMaxScale value to minScale value. A value of 1 will scale linearly down from the hilighted item out to the item at scaleRadius.
  70. * A value higher than wone will descend slowly from the hilight, then drop off quicker at the edge. A value lower than one will drop off quickly from the hilight Should be greater than 0*/
  71. [Style( name="hilightScaleSlope", type="Number", inherit="no" )]
  72. /** The radius, in items, around the hilighted item that are affected by the hilight. A value of 1 means only the hilighted item will scale. A value of three means the hilighted item plus the two items
  73. * to either side will scale up. How much each item scales is affected by the scaleSlope style.*/
  74. [Style( name="hilightScaleRadius", type="Number", inherit="no" )]
  75. /** how quickly items animate to their target location when the layout of the renderers change. A value of 1 will
  76. * snap instantly to the new value, while a value of 0 will never change */
  77. [Style( name="animationSpeed", type="Number", inherit="no" )]
  78. [Event( name="change", type="flash.events.Event" )]
  79. [Event( name="loaded", type="flash.events.Event" )]
  80. [DefaultProperty( "dataProvider" )]
  81. public class FisheyeBase extends UIComponent
  82. {
  83. public static const LOADED : String = "loaded";
  84. /** the data items driving the component
  85. */
  86. private var _items : Array = [];
  87. /** when a new dataprovider is assigned, we keep it in reserve until we have a chance
  88. * to generate new renderers for it. This is the temporary holding pen for those new items
  89. */
  90. private var _pendingItems : Array;
  91. protected var itemsChanged : Boolean = false;
  92. /** true if the renderers need to be regenerated */
  93. protected var renderersDirty : Boolean = true;
  94. /** the renderers representing the data items, one for each item */
  95. protected var renderers : Array = [];
  96. /** the currently hilighted item
  97. */
  98. protected var hilightedItemIndex : Number = NaN;
  99. /** the currently selected item
  100. */
  101. protected var selectedItemIndex : Number = NaN;
  102. /** @private */
  103. private var _selectionEnabled : Boolean = true;
  104. /** the factory that generates item renderers
  105. */
  106. private var _itemRendererFactory : IFactory;
  107. /**
  108. * the object that manages animating the children layout
  109. */
  110. protected var animator : LayoutAnimator;
  111. /** Constructor */
  112. public function FisheyeBase()
  113. {
  114. super();
  115. _itemRendererFactory = new ClassFactory( CachedLabel );
  116. addEventListener( MouseEvent.MOUSE_MOVE, updateHilight );
  117. addEventListener( MouseEvent.ROLL_OUT, removeHilight );
  118. addEventListener( MouseEvent.MOUSE_DOWN, updateSelection );
  119. var maskShape : Sprite = new Sprite();
  120. addChild( maskShape );
  121. mask = maskShape;
  122. maskShape.graphics.beginFill( 0 );
  123. maskShape.graphics.drawRect( 0, 0, 10, 10 );
  124. maskShape.graphics.endFill();
  125. animator = new LayoutAnimator();
  126. animator.layoutFunction = generateLayout;
  127. }
  128. //-----------------------------------------------------------------
  129. /** the data source
  130. */
  131. public function set dataProvider( value : Array ) : void
  132. {
  133. _pendingItems = value;
  134. renderersDirty = true;
  135. itemsChanged = true;
  136. invalidateProperties();
  137. dispatchEvent( new Event( LOADED ) );
  138. }
  139. public function get dataProvider() : Array
  140. {
  141. return _items;
  142. }
  143. //-----------------------------------------------------------------
  144. public function set selectionEnabled( value : Boolean ) : void
  145. {
  146. if ( _selectionEnabled == value )
  147. return;
  148. _selectionEnabled = value;
  149. selectedIndex = selectedIndex;
  150. }
  151. public function get selectionEnabled() : Boolean
  152. {
  153. return _selectionEnabled;
  154. }
  155. [Bindable( "change" )]
  156. public function get selectedItem() : Object
  157. {
  158. return ( isNaN( selectedItemIndex ) ? null : _items[ selectedItemIndex ] );
  159. }
  160. public function set selectedItem( value : Object ) : void
  161. {
  162. var newIndex : Number;
  163. for ( var i : int = 0; i < _items.length; i++ )
  164. {
  165. if ( value == _items[ i ] )
  166. {
  167. newIndex = i;
  168. break;
  169. }
  170. }
  171. selectedIndex = newIndex;
  172. }
  173. [Bindable( "change" )]
  174. public function get selectedIndex() : int
  175. {
  176. return ( isNaN( selectedItemIndex ) ? -1 : selectedItemIndex );
  177. }
  178. public function set selectedIndex( value : int ) : void
  179. {
  180. var v : Number = ( value < 0 || value >= _items.length ) ? NaN : value;
  181. if ( v != selectedItemIndex )
  182. {
  183. selectedItemIndex = v;
  184. updateState();
  185. animator.invalidateLayout();
  186. dispatchEvent( new Event( LOADED ) );
  187. }
  188. }
  189. //-----------------------------------------------------------------
  190. /* These private get properties are wrappers around styles that return defaults if unset.
  191. * It saves me from having to write a CSS selector, which
  192. * I really should do at some point */
  193. protected function get defaultSpacingWithDefault() : Number
  194. {
  195. var result : Number = getStyle( "defaultSpacing" );
  196. if ( isNaN( result ) )
  197. result = 0;
  198. return result;
  199. }
  200. protected function get maxScaleWithDefault() : Number
  201. {
  202. var result : Number = getStyle( "hilightMaxScale" );
  203. if ( isNaN( result ) )
  204. result = 1;
  205. return result;
  206. }
  207. //-----------------------------------------------------------------
  208. /**
  209. * by making the itemRenderer be of type IFactory,
  210. * developers can define it inline using the <Component> tag
  211. */
  212. public function get itemRenderer() : IFactory
  213. {
  214. return _itemRendererFactory;
  215. }
  216. public function set itemRenderer( value : IFactory ) : void
  217. {
  218. _itemRendererFactory = value;
  219. renderersDirty = true;
  220. invalidateProperties();
  221. }
  222. //-----------------------------------------------------------------
  223. override protected function commitProperties() : void
  224. {
  225. // its now safe to switch over new dataProviders.
  226. if ( _pendingItems != null )
  227. {
  228. _items = _pendingItems;
  229. _pendingItems = null;
  230. }
  231. itemsChanged = false;
  232. if ( renderersDirty )
  233. {
  234. // something has forced us to reallocate our renderers. start by throwing out the old ones.
  235. renderersDirty = false;
  236. var mask : DisplayObject = mask;
  237. for ( var i : int = numChildren - 1; i >= 0; i-- )
  238. removeChildAt( i );
  239. addChild( mask );
  240. renderers = [];
  241. // allocate new renderers, assign the data.
  242. for ( i = 0; i < _items.length; i++ )
  243. {
  244. var renderer : UIComponent = _itemRendererFactory.newInstance();
  245. IDataRenderer( renderer ).data = _items[ i ];
  246. renderers[ i ] = renderer;
  247. addChild( renderer );
  248. }
  249. animator.items = renderers;
  250. }
  251. invalidateSize();
  252. }
  253. private function removeHilight( e : MouseEvent ) : void
  254. {
  255. // called on rollout. Clear out any hilight, and reset our layout.
  256. hilightedItemIndex = NaN;
  257. updateState();
  258. animator.invalidateLayout();
  259. }
  260. /** finds the item that would be closest to the x/y position if it were hilighted
  261. */
  262. protected function findItemForPosition( xPos : Number, yPos : Number ) : Number
  263. {
  264. return NaN;
  265. }
  266. /** called on mouse click to set or clear the selection */
  267. protected function updateSelection( e : MouseEvent ) : void
  268. {
  269. if ( _selectionEnabled == false )
  270. return;
  271. var newSelection : Number = findItemForPosition( this.mouseX, this.mouseY );
  272. if ( selectedItemIndex == newSelection )
  273. selectedIndex = -1;
  274. else
  275. selectedIndex = newSelection;
  276. updateState();
  277. animator.invalidateLayout();
  278. }
  279. /** called on mouse move to update the hilight */
  280. private function updateHilight( e : MouseEvent ) : void
  281. {
  282. var newHilight : Number = findItemForPosition( this.mouseX, this.mouseY );
  283. if ( newHilight == hilightedItemIndex )
  284. return;
  285. hilightedItemIndex = newHilight;
  286. updateState();
  287. animator.invalidateLayout();
  288. }
  289. /**
  290. * update the state properties of all of the items, based on
  291. * the current hilighted and/or selected items
  292. */
  293. protected function updateState() : void
  294. {
  295. var stateProperty : String = getStyle( "stateProperty" );
  296. if ( stateProperty != null )
  297. {
  298. var rolloverState : String = getStyle( "rolloverValue" );
  299. var selectedState : String = getStyle( "selectedValue" );
  300. var unselectedValue : String = getStyle( "unselectedValue" );
  301. if ( unselectedValue == null || ( isNaN( selectedItemIndex ) && isNaN( hilightedItemIndex ) ) )
  302. unselectedValue = getStyle( "defaultValue" );
  303. for ( var i : int = 0; i < renderers.length; i++ )
  304. {
  305. renderers[ i ][ stateProperty ] = ( i == selectedItemIndex ) ? selectedState : ( i == hilightedItemIndex ) ? rolloverState : unselectedValue;
  306. }
  307. }
  308. }
  309. /**
  310. * each item get scaled down based on its distance from the hliighted item. this is the equation we use
  311. * to figure out how much to scale down. The basic idea is this...we have two parameters that play a part
  312. * in how quickly we scale down, scaleRadius and scaleSlope. scaleRadius is the number of items on either
  313. * side of the hilighted item (inclusive) that we should be able to use to scale down. scaleSlope affects
  314. * how whether we scale down quickly with the first few items in the radius, or the last few items.
  315. * This equation essentially does that.
  316. */
  317. private function calcDistanceFactor( params : FisheyeParameters, distance : Number ) : Number
  318. {
  319. var mult : Number = 1 / params.scaleRadius;
  320. return Math.max( 0, 1 - Math.pow( distance * mult, params.scaleSlope ) );
  321. }
  322. /**
  323. * populates a set of items to fit into the distance axisLength, assuming nothing is hilighted, so they
  324. * all scale the same. It will attempt to scale them to match the defaultScale style */
  325. protected function populateMajorAxisForDefault( pdata : Array, axis : FisheyeAxis,
  326. axisLength : Number ) : FisheyeParameters
  327. {
  328. var vp : Number;
  329. var itemCount : int = pdata.length;
  330. var params : FisheyeParameters = new FisheyeParameters();
  331. populateParameters( params, false );
  332. var summedSpacing : Number = params.spacing * ( itemCount - 1 );
  333. var sizeSum : Number = 0;
  334. var pdataInst : FisheyeItem;
  335. for ( var i : int = 0; i < itemCount; i++ )
  336. sizeSum += pdata[ i ][ axis.EOM ];
  337. if ( sizeSum > 0 )
  338. {
  339. var maximumMinScale : Number = ( axisLength - summedSpacing ) / sizeSum;
  340. params.minScale = Math.min( params.minScale, maximumMinScale );
  341. }
  342. vp = 0;
  343. for ( i = 0; i < itemCount; i++ )
  344. {
  345. pdataInst = pdata[ i ];
  346. pdataInst.scale = params.minScale;
  347. pdataInst[ axis.pos ] = vp;
  348. vp += pdataInst[ axis.EOM ] * params.minScale + params.spacing;
  349. }
  350. return params;
  351. }
  352. /**
  353. * takes the parameters used in the fisheye equation, and adjusts them as best as possible to make sure the
  354. * items can fit into distance 'axisScale.' Right now it does this by scaling down the minScale parameter if necessary. That's
  355. * not entirely sufficient, but it does a pretty good job. For future work: If that's not sufficient, adjust the scaleRadius, scaleSlope,
  356. * and spacing parameter
  357. */
  358. private function adjustParameters( pdata : Array, targetIndex : Number, params : FisheyeParameters,
  359. axisSize : Number, axis : FisheyeAxis ) : void
  360. {
  361. var itemCount : int = pdata.length;
  362. var summedSpacing : Number = params.spacing * ( itemCount - 1 );
  363. var maxSum : Number = 0;
  364. var minSum : Number = 0;
  365. // given the constraint:
  366. // W(0) * S(0) + spacing + W(1) * S(1) + spacing + ... + W(N) * S(N) <= unscaledWidth
  367. // here we adjust the numbers that go into the calculation of S(i) to fit.
  368. // right now that just means adjusting minScale downward if necessary. We'll probably add some more complex heuristic later.
  369. for ( var i : int = 0; i < itemCount; i++ )
  370. {
  371. var pdataInst : FisheyeItem = pdata[ i ];
  372. var distanceFromItem : Number = Math.abs( targetIndex - i );
  373. var distanceFactor : Number = calcDistanceFactor( params, distanceFromItem );
  374. var maxFactor : Number = params.maxScale * distanceFactor;
  375. var minFactor : Number = ( 1 - distanceFactor );
  376. var itemSize : Number = pdataInst[ axis.EOM ];
  377. maxSum += itemSize * maxFactor;
  378. minSum += itemSize * minFactor;
  379. }
  380. var minScale : Number = ( minSum > 0 ) ? ( ( axisSize - summedSpacing - maxSum ) / minSum ) : 0;
  381. // if we've got lots of extra space, we might calculate that we need to make our ends _larger_ to fill the space. We don't want
  382. // to do that. So let's contrain it to minScale.
  383. minScale = Math.min( params.minScale, minScale );
  384. params.minScale = minScale;
  385. }
  386. /**
  387. * populate a parameters structure from the various styles
  388. */
  389. private function populateParameters( params : FisheyeParameters, hilighted : Boolean ) : void
  390. {
  391. if ( hilighted == false )
  392. {
  393. params.minScale = getStyle( "defaultScale" );
  394. if ( isNaN( params.minScale ) )
  395. params.minScale = .5;
  396. params.spacing = defaultSpacingWithDefault;
  397. }
  398. else
  399. {
  400. params.minScale = getStyle( "hilightMinScale" );
  401. if ( isNaN( params.minScale ) )
  402. {
  403. params.minScale = getStyle( "defaultScale" );
  404. if ( isNaN( params.minScale ) )
  405. params.minScale = .5;
  406. }
  407. params.spacing = getStyle( "hilightSpacing" );
  408. if ( isNaN( params.spacing ) )
  409. params.spacing = defaultSpacingWithDefault;
  410. }
  411. params.maxScale = getStyle( "hilightMaxScale" );
  412. if ( isNaN( params.maxScale ) )
  413. params.maxScale = 1;
  414. params.scaleRadius = getStyle( "hilightScaleRadius" );
  415. if ( isNaN( params.scaleRadius ) )
  416. params.scaleRadius = 2;
  417. params.scaleRadius = Math.max( 1, params.scaleRadius );
  418. params.scaleSlope = getStyle( "hilightScaleSlope" );
  419. if ( isNaN( params.scaleSlope ) )
  420. params.scaleSlope = .75;
  421. }
  422. /**
  423. * populates a set of items to fit into the distance axisLength, assuming targetIndex is hilighted.
  424. */
  425. protected function populateMajorAxisFor( pdata : Array, targetIndex : Number, axisSize : Number,
  426. axis : FisheyeAxis ) : FisheyeParameters
  427. {
  428. var vp : Number;
  429. var itemCount : int = pdata.length;
  430. var pdataInst : FisheyeItem;
  431. var params : FisheyeParameters = new FisheyeParameters();
  432. populateParameters( params, true );
  433. adjustParameters( pdata, targetIndex, params, axisSize, axis );
  434. vp = 0;
  435. for ( var i : int = 0; i < itemCount; i++ )
  436. {
  437. pdataInst = pdata[ i ];
  438. var distanceFromItem : Number = Math.abs( targetIndex - i );
  439. var distanceFactor : Number = calcDistanceFactor( params, distanceFromItem );
  440. var scale : Number = Math.max( 0,
  441. params.minScale + ( params.maxScale - params.minScale ) * ( distanceFactor ) );
  442. pdataInst[ axis.pos ] = vp;
  443. pdataInst.scale = scale;
  444. vp += pdataInst[ axis.EOM ] * scale + params.spacing;
  445. }
  446. return params;
  447. }
  448. /**
  449. * given a set of scaled and laid out items, adjust them forward or backward to match the align property
  450. */
  451. protected function align( pdata : Array, axis : FisheyeAxis ) : void
  452. {
  453. var majorAlignValue : String = getStyle( axis.align );
  454. var itemCount : int = pdata.length;
  455. var pdataInst : FisheyeItem;
  456. if ( itemCount == 0 )
  457. return;
  458. switch ( majorAlignValue )
  459. {
  460. case "right":
  461. case "bottom":
  462. pdataInst = pdata[ itemCount - 1 ];
  463. var offset : Number = this[ axis.unscaled ] - ( pdataInst[ axis.pos ] + pdata[ itemCount - 1 ][ axis.EOM ] * pdataInst.scale );
  464. for ( var i : int = 0; i < itemCount; i++ )
  465. {
  466. pdata[ i ][ axis.pos ] += offset;
  467. }
  468. break;
  469. case "left":
  470. case "top":
  471. break;
  472. case "center":
  473. default:
  474. var midIndex : int = Math.floor( itemCount / 2 );
  475. pdataInst = pdata[ itemCount - 1 ];
  476. var rightPos : Number = pdataInst[ axis.pos ] + pdataInst[ axis.EOM ] * pdataInst.scale;
  477. offset = ( this[ axis.unscaled ] / 2 - ( rightPos ) / 2 );
  478. for ( i = 0; i < itemCount; i++ )
  479. {
  480. pdata[ i ][ axis.pos ] += offset;
  481. }
  482. break;
  483. }
  484. }
  485. /**
  486. * overridden in the subclasses
  487. */
  488. protected function generateLayout() : void
  489. {
  490. }
  491. override protected function updateDisplayList( unscaledWidth : Number, unscaledHeight : Number ) : void
  492. {
  493. graphics.clear();
  494. graphics.moveTo( 0, 0 );
  495. graphics.beginFill( 0, 0 );
  496. graphics.drawRect( 0, 0, unscaledWidth, unscaledHeight );
  497. // update the mask
  498. mask.width = unscaledWidth;
  499. mask.height = unscaledHeight;
  500. animator.invalidateLayout();
  501. }
  502. override public function styleChanged( styleProp : String ) : void
  503. {
  504. if ( styleProp == "animationSpeed" )
  505. animator.animationSpeed = getStyle( "animationSpeed" );
  506. invalidateSize();
  507. invalidateDisplayList();
  508. animator.invalidateLayout();
  509. }
  510. }
  511. }