Custom Wheel Layout


/ Published in: ActionScript
Save to your folder(s)

Taken from Evtim's source - http://evtimmy.com


Copy this code and paste it in your HTML
  1. package layouts
  2. {
  3.  
  4. import flash.geom.Matrix;
  5. import flash.geom.Matrix3D;
  6. import flash.geom.Point;
  7. import flash.geom.Rectangle;
  8. import flash.geom.Vector3D;
  9.  
  10. import mx.core.ILayoutElement;
  11. import mx.core.IVisualElement;
  12.  
  13. import spark.components.supportClasses.GroupBase;
  14. import spark.core.NavigationUnit;
  15. import spark.layouts.supportClasses.LayoutBase;
  16.  
  17.  
  18. /***
  19. * Evtim is the man. This is all his - http://evtimmy.com/ - modified by someone who only
  20. * half knows what he's doing. Sorry Evtim if I butchered the code.
  21. **/
  22.  
  23.  
  24. public class MonthWheelLayout extends LayoutBase
  25. {
  26. //--------------------------------------------------------------------------
  27. //
  28. // Constructor
  29. //
  30. //--------------------------------------------------------------------------
  31.  
  32. public function MonthWheelLayout()
  33. {
  34. super();
  35. }
  36.  
  37. //--------------------------------------------------------------------------
  38. //
  39. // Properties
  40. //
  41. //--------------------------------------------------------------------------
  42.  
  43. //----------------------------------
  44. // gap
  45. //----------------------------------
  46.  
  47. private var _gap:Number = 0;
  48.  
  49. /**
  50. * The gap between the items
  51. */
  52. public function get gap():Number
  53. {
  54. return _gap;
  55. }
  56.  
  57. public function set gap(value:Number):void
  58. {
  59. _gap = value;
  60. var layoutTarget:GroupBase = target;
  61. if (layoutTarget)
  62. {
  63. layoutTarget.invalidateSize();
  64. layoutTarget.invalidateDisplayList();
  65. }
  66. }
  67.  
  68. //----------------------------------
  69. // axisAngle
  70. //----------------------------------
  71.  
  72. /**
  73. * @private
  74. * The total width of all items, including gap space.
  75. */
  76. private var _totalWidth:Number = 4440;
  77. private var _itemWidth:Number = 370;
  78. private var _itemHeight:Number = 144;
  79. private var _halfWidthDiagonal:Number = Math.sqrt(_itemWidth * _itemWidth / 4 + _itemHeight * _itemHeight);
  80.  
  81. /**
  82. * @private
  83. * Cache which item is currently in view, to facilitate scrollposition delta calculations
  84. */
  85. private var _centeredItemIndex:int = 0;
  86. private var _centeredItemCircumferenceBegin:Number = 0;
  87. private var _centeredItemCircumferenceEnd:Number = 0;
  88. private var _centeredItemDegrees:Number = 0;
  89.  
  90. /**
  91. * The axis to tilt the 3D wheel
  92. */
  93. private var _axis:Vector3D = new Vector3D(0, Math.cos(Math.PI * -90 /180), Math.sin(Math.PI * -90 /180));
  94.  
  95. /**
  96. * @private
  97. * Given the totalWidth, maxHeight and maxHalfWidthDiagonal, calculate the bounds of the items
  98. * on screen. Uses the projection matrix of the layout target to calculate.
  99. */
  100. private function projectBounds():Point
  101. {
  102.  
  103. var radius:Number = _totalWidth * 0.5 / Math.PI;
  104.  
  105. // Now since we are going to arrange all the items along circle, middle of the item being the tangent point,
  106. // we need to calculate the minimum bounding circle. It is easily calculated from the maximum width item:
  107. var boundingRadius:Number = Math.sqrt(radius * radius + 0.25 * _itemWidth * _itemWidth);
  108.  
  109. var projectedBoundsW:Number = _axis.z * _axis.z * (_halfWidthDiagonal + 2 * radius);
  110.  
  111. var projectedBoundsH:Number = Math.abs(_axis.z) * (_halfWidthDiagonal + 2 * radius) +
  112. _itemHeight * _axis.y * _axis.y;
  113.  
  114. return new Point(projectedBoundsW + 10, projectedBoundsH + 10);
  115. }
  116.  
  117. /**
  118. * @private
  119. * Iterates through all the items, calculates the projected bounds on screen, updates _totalWidth member variable.
  120. */
  121.  
  122. //--------------------------------------------------------------------------
  123. //
  124. // Overridden methods: LayoutBase
  125. //
  126. //--------------------------------------------------------------------------
  127.  
  128. /**
  129. * @private
  130. */
  131. override public function set target(value:GroupBase):void
  132. {
  133. // Make sure that if layout is swapped out, we clean up
  134. if (!value && target)
  135. {
  136. target.maintainProjectionCenter = false;
  137.  
  138. var iter:LayoutIterator = new LayoutIterator(target);
  139. var el:ILayoutElement;
  140. while (el = iter.nextElement())
  141. {
  142. el.setLayoutMatrix(new Matrix(), false /*triggerLayout*/);
  143. }
  144. }
  145.  
  146. super.target = value;
  147.  
  148. // Make sure we turn on projection the first time the layout
  149. // gets assigned to the group
  150. if (target)
  151. target.maintainProjectionCenter = true;
  152. }
  153.  
  154. override public function measure():void
  155. {
  156. var bounds:Point = projectBounds();
  157.  
  158. target.measuredWidth = bounds.x;
  159. target.measuredHeight = bounds.y;
  160. }
  161.  
  162. override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
  163. {
  164. // Get the bounds, this will also update _totalWidth
  165. var bounds:Point = projectBounds();
  166.  
  167. // Update the content size
  168. target.setContentSize(_totalWidth + unscaledWidth, bounds.y);
  169. var radius:Number = _totalWidth * 0.5 / Math.PI;
  170. var gap:Number = this.gap;
  171. _centeredItemDegrees = Number.MAX_VALUE;
  172.  
  173. var scrollPosition:Number = target.horizontalScrollPosition;
  174. var totalWidthSoFar:Number = 0;
  175. // Subtract the half width of the first element from totalWidthSoFar:
  176. var iter:LayoutIterator = new LayoutIterator(target);
  177. var el:ILayoutElement = iter.nextElement();
  178. if (!el)
  179. return;
  180. totalWidthSoFar -= el.getPreferredBoundsWidth(false /*postTransform*/) / 2;
  181.  
  182. // Set the 3D Matrix for all the elements:
  183. iter.reset();
  184. while (el = iter.nextElement())
  185. {
  186. // Size the item, no need to position it, since we'd set the computed matrix
  187. // which defines the position.
  188. el.setLayoutBoundsSize(NaN, NaN, false /*postTransform*/);
  189. var elementWidth:Number = el.getLayoutBoundsWidth(false /*postTransform*/);
  190. var elementHeight:Number = el.getLayoutBoundsHeight(false /*postTransform*/);
  191. var degrees:Number = 360 * (totalWidthSoFar + elementWidth/2 - scrollPosition) / _totalWidth;
  192.  
  193. // Remember which item is centered, this is used during scrolling
  194. var curDegrees:Number = degrees % 360;
  195. if (Math.abs(curDegrees) < Math.abs(_centeredItemDegrees))
  196. {
  197. _centeredItemDegrees = curDegrees;
  198. _centeredItemIndex = iter.curIndex;
  199. _centeredItemCircumferenceBegin = totalWidthSoFar - gap;
  200. _centeredItemCircumferenceEnd = totalWidthSoFar + elementWidth + gap;
  201. }
  202.  
  203. // Calculate and set the 3D Matrix
  204. var m:Matrix3D = new Matrix3D();
  205. m.appendTranslation(-elementWidth/2, -elementHeight/2 + radius * _axis.z, -radius * _axis.y );
  206. m.appendRotation(-degrees, _axis);
  207. m.appendTranslation(unscaledWidth/2, unscaledHeight/2, radius * _axis.y);
  208. el.setLayoutMatrix3D(m, false /*triggerLayout*/);
  209.  
  210. // Update the layer for a correct z-order
  211. if (el is IVisualElement)
  212. IVisualElement(el).depth = Math.abs( Math.floor(180 - Math.abs(degrees % 360)) );
  213.  
  214. // Move on to next item
  215. totalWidthSoFar += elementWidth + gap;
  216. }
  217. }
  218.  
  219. private function scrollPositionFromCenterToNext(next:Boolean):Number
  220. {
  221. var iter:LayoutIterator = new LayoutIterator(target, _centeredItemIndex);
  222. var el:ILayoutElement = next ? iter.nextElementWrapped() : iter.prevElementWrapped();
  223. if (!el)
  224. return 0;
  225.  
  226. var elementWidth:Number = el.getLayoutBoundsWidth(false /*postTransform*/);
  227.  
  228. var value:Number;
  229. if (next)
  230. {
  231. if (_centeredItemDegrees > 0.1)
  232. return (_centeredItemCircumferenceEnd + _centeredItemCircumferenceBegin) / 2;
  233.  
  234. value = _centeredItemCircumferenceEnd + elementWidth/2;
  235. if (value > _totalWidth)
  236. value -= _totalWidth;
  237. }
  238. else
  239. {
  240. if (_centeredItemDegrees < -0.1)
  241. return (_centeredItemCircumferenceEnd + _centeredItemCircumferenceBegin) / 2;
  242.  
  243. value = _centeredItemCircumferenceBegin - elementWidth/2;
  244. if (value < 0)
  245. value += _totalWidth;
  246. }
  247. return value;
  248. }
  249.  
  250. override protected function scrollPositionChanged():void
  251. {
  252. if (target)
  253. target.invalidateDisplayList();
  254. }
  255.  
  256. override public function getHorizontalScrollPositionDelta(scrollUnit:uint):Number
  257. {
  258. var g:GroupBase = target;
  259. if (!g || g.numElements == 0)
  260. return 0;
  261.  
  262. var value:Number;
  263.  
  264. switch (scrollUnit)
  265. {
  266. case NavigationUnit.LEFT:
  267. {
  268. value = target.horizontalScrollPosition - 30;
  269. if (value < 0)
  270. value += _totalWidth;
  271. return value - target.horizontalScrollPosition;
  272. }
  273.  
  274. case NavigationUnit.RIGHT:
  275. {
  276. value = target.horizontalScrollPosition + 30;
  277. if (value > _totalWidth)
  278. value -= _totalWidth;
  279. return value - target.horizontalScrollPosition;
  280. }
  281.  
  282. case NavigationUnit.PAGE_LEFT:
  283. return scrollPositionFromCenterToNext(false) - target.horizontalScrollPosition;
  284.  
  285. case NavigationUnit.PAGE_RIGHT:
  286. return scrollPositionFromCenterToNext(true) - target.horizontalScrollPosition;
  287.  
  288. case NavigationUnit.HOME:
  289. return 0;
  290.  
  291. case NavigationUnit.END:
  292. return _totalWidth;
  293.  
  294. default:
  295. return 0;
  296. }
  297. }
  298.  
  299. /**
  300. * @private
  301. */
  302. override public function getScrollPositionDeltaToElement(index:int):Point
  303. {
  304. var layoutTarget:GroupBase = target;
  305. if (!layoutTarget)
  306. return null;
  307.  
  308. var gap:Number = this.gap;
  309. var totalWidthSoFar:Number = 0;
  310. var iter:LayoutIterator = new LayoutIterator(layoutTarget);
  311.  
  312. var el:ILayoutElement = iter.nextElement();
  313. if (!el)
  314. return null;
  315. totalWidthSoFar -= el.getLayoutBoundsWidth(false /*postTransform*/) / 2;
  316.  
  317. iter.reset();
  318. while (null != (el = iter.nextElement()) && iter.curIndex <= index)
  319. {
  320. var elementWidth:Number = el.getLayoutBoundsWidth(false /*postTransform*/);
  321. totalWidthSoFar += gap + elementWidth;
  322. }
  323. return new Point(totalWidthSoFar - elementWidth / 2 -gap - layoutTarget.horizontalScrollPosition, 0);
  324. }
  325.  
  326. /**
  327. * @private
  328. */
  329. override public function updateScrollRect(w:Number, h:Number):void
  330. {
  331. var g:GroupBase = target;
  332. if (!g)
  333. return;
  334.  
  335. if (clipAndEnableScrolling)
  336. {
  337. // Since scroll position is reflected in our 3D calculations,
  338. // always set the top-left of the srcollRect to (0,0).
  339. g.scrollRect = new Rectangle(0, -700, w, h);
  340. }
  341. else
  342. g.scrollRect = null;
  343. }
  344. }
  345. }
  346.  
  347. import spark.components.supportClasses.GroupBase;
  348. import mx.core.ILayoutElement;
  349.  
  350. class LayoutIterator
  351. {
  352. private var _curIndex:int;
  353. private var _numVisited:int = 0;
  354. private var totalElements:int;
  355. private var _target:GroupBase;
  356. private var _loopIndex:int = -1;
  357.  
  358. public function get curIndex():int
  359. {
  360. return _curIndex;
  361. }
  362.  
  363. public function LayoutIterator(target:GroupBase, index:int=-1):void
  364. {
  365. totalElements = target.numElements;
  366. _target = target;
  367. _curIndex = index;
  368. }
  369.  
  370. public function nextElement():ILayoutElement
  371. {
  372. while (_curIndex < totalElements - 1)
  373. {
  374. var el:ILayoutElement = _target.getElementAt(++_curIndex);
  375. if (el && el.includeInLayout)
  376. {
  377. ++_numVisited;
  378. return el;
  379. }
  380. }
  381. return null;
  382. }
  383.  
  384. public function prevElement():ILayoutElement
  385. {
  386. while (_curIndex > 0)
  387. {
  388. var el:ILayoutElement = _target.getElementAt(--_curIndex);
  389. if (el && el.includeInLayout)
  390. {
  391. ++_numVisited;
  392. return el;
  393. }
  394. }
  395. return null;
  396. }
  397.  
  398. public function nextElementWrapped():ILayoutElement
  399. {
  400. if (_loopIndex == -1)
  401. _loopIndex = _curIndex;
  402. else if (_loopIndex == _curIndex)
  403. return null;
  404.  
  405. var el:ILayoutElement = nextElement();
  406. if (el)
  407. return el;
  408. else if (_curIndex == totalElements - 1)
  409. _curIndex = -1;
  410. return nextElement();
  411. }
  412.  
  413. public function prevElementWrapped():ILayoutElement
  414. {
  415. if (_loopIndex == -1)
  416. _loopIndex = _curIndex;
  417. else if (_loopIndex == _curIndex)
  418. return null;
  419.  
  420. var el:ILayoutElement = prevElement();
  421. if (el)
  422. return el;
  423. else if (_curIndex == 0)
  424. _curIndex = totalElements;
  425. return prevElement();
  426. }
  427.  
  428. public function reset():void
  429. {
  430. _curIndex = -1;
  431. _numVisited = 0;
  432. _loopIndex = -1;
  433. }
  434.  
  435. public function get numVisited():int
  436. {
  437. return _numVisited;
  438. }
  439. }

Report this snippet


Comments

RSS Icon Subscribe to comments

You need to login to post a comment.