Revision: 50430
Initial Code
Initial URL
Initial Description
Initial Title
Initial Tags
Initial Language
at August 21, 2011 06:27 by ryanstewart
Initial Code
package layouts { import flash.geom.Matrix; import flash.geom.Matrix3D; import flash.geom.Point; import flash.geom.Rectangle; import flash.geom.Vector3D; import mx.core.ILayoutElement; import mx.core.IVisualElement; import spark.components.supportClasses.GroupBase; import spark.core.NavigationUnit; import spark.layouts.supportClasses.LayoutBase; /*** * Evtim is the man. This is all his - http://evtimmy.com/ - modified by someone who only * half knows what he's doing. Sorry Evtim if I butchered the code. **/ public class MonthWheelLayout extends LayoutBase { //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- public function MonthWheelLayout() { super(); } //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // gap //---------------------------------- private var _gap:Number = 0; /** * The gap between the items */ public function get gap():Number { return _gap; } public function set gap(value:Number):void { _gap = value; var layoutTarget:GroupBase = target; if (layoutTarget) { layoutTarget.invalidateSize(); layoutTarget.invalidateDisplayList(); } } //---------------------------------- // axisAngle //---------------------------------- /** * @private * The total width of all items, including gap space. */ private var _totalWidth:Number = 4440; private var _itemWidth:Number = 370; private var _itemHeight:Number = 144; private var _halfWidthDiagonal:Number = Math.sqrt(_itemWidth * _itemWidth / 4 + _itemHeight * _itemHeight); /** * @private * Cache which item is currently in view, to facilitate scrollposition delta calculations */ private var _centeredItemIndex:int = 0; private var _centeredItemCircumferenceBegin:Number = 0; private var _centeredItemCircumferenceEnd:Number = 0; private var _centeredItemDegrees:Number = 0; /** * The axis to tilt the 3D wheel */ private var _axis:Vector3D = new Vector3D(0, Math.cos(Math.PI * -90 /180), Math.sin(Math.PI * -90 /180)); /** * @private * Given the totalWidth, maxHeight and maxHalfWidthDiagonal, calculate the bounds of the items * on screen. Uses the projection matrix of the layout target to calculate. */ private function projectBounds():Point { var radius:Number = _totalWidth * 0.5 / Math.PI; // Now since we are going to arrange all the items along circle, middle of the item being the tangent point, // we need to calculate the minimum bounding circle. It is easily calculated from the maximum width item: var boundingRadius:Number = Math.sqrt(radius * radius + 0.25 * _itemWidth * _itemWidth); var projectedBoundsW:Number = _axis.z * _axis.z * (_halfWidthDiagonal + 2 * radius); var projectedBoundsH:Number = Math.abs(_axis.z) * (_halfWidthDiagonal + 2 * radius) + _itemHeight * _axis.y * _axis.y; return new Point(projectedBoundsW + 10, projectedBoundsH + 10); } /** * @private * Iterates through all the items, calculates the projected bounds on screen, updates _totalWidth member variable. */ //-------------------------------------------------------------------------- // // Overridden methods: LayoutBase // //-------------------------------------------------------------------------- /** * @private */ override public function set target(value:GroupBase):void { // Make sure that if layout is swapped out, we clean up if (!value && target) { target.maintainProjectionCenter = false; var iter:LayoutIterator = new LayoutIterator(target); var el:ILayoutElement; while (el = iter.nextElement()) { el.setLayoutMatrix(new Matrix(), false /*triggerLayout*/); } } super.target = value; // Make sure we turn on projection the first time the layout // gets assigned to the group if (target) target.maintainProjectionCenter = true; } override public function measure():void { var bounds:Point = projectBounds(); target.measuredWidth = bounds.x; target.measuredHeight = bounds.y; } override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void { // Get the bounds, this will also update _totalWidth var bounds:Point = projectBounds(); // Update the content size target.setContentSize(_totalWidth + unscaledWidth, bounds.y); var radius:Number = _totalWidth * 0.5 / Math.PI; var gap:Number = this.gap; _centeredItemDegrees = Number.MAX_VALUE; var scrollPosition:Number = target.horizontalScrollPosition; var totalWidthSoFar:Number = 0; // Subtract the half width of the first element from totalWidthSoFar: var iter:LayoutIterator = new LayoutIterator(target); var el:ILayoutElement = iter.nextElement(); if (!el) return; totalWidthSoFar -= el.getPreferredBoundsWidth(false /*postTransform*/) / 2; // Set the 3D Matrix for all the elements: iter.reset(); while (el = iter.nextElement()) { // Size the item, no need to position it, since we'd set the computed matrix // which defines the position. el.setLayoutBoundsSize(NaN, NaN, false /*postTransform*/); var elementWidth:Number = el.getLayoutBoundsWidth(false /*postTransform*/); var elementHeight:Number = el.getLayoutBoundsHeight(false /*postTransform*/); var degrees:Number = 360 * (totalWidthSoFar + elementWidth/2 - scrollPosition) / _totalWidth; // Remember which item is centered, this is used during scrolling var curDegrees:Number = degrees % 360; if (Math.abs(curDegrees) < Math.abs(_centeredItemDegrees)) { _centeredItemDegrees = curDegrees; _centeredItemIndex = iter.curIndex; _centeredItemCircumferenceBegin = totalWidthSoFar - gap; _centeredItemCircumferenceEnd = totalWidthSoFar + elementWidth + gap; } // Calculate and set the 3D Matrix var m:Matrix3D = new Matrix3D(); m.appendTranslation(-elementWidth/2, -elementHeight/2 + radius * _axis.z, -radius * _axis.y ); m.appendRotation(-degrees, _axis); m.appendTranslation(unscaledWidth/2, unscaledHeight/2, radius * _axis.y); el.setLayoutMatrix3D(m, false /*triggerLayout*/); // Update the layer for a correct z-order if (el is IVisualElement) IVisualElement(el).depth = Math.abs( Math.floor(180 - Math.abs(degrees % 360)) ); // Move on to next item totalWidthSoFar += elementWidth + gap; } } private function scrollPositionFromCenterToNext(next:Boolean):Number { var iter:LayoutIterator = new LayoutIterator(target, _centeredItemIndex); var el:ILayoutElement = next ? iter.nextElementWrapped() : iter.prevElementWrapped(); if (!el) return 0; var elementWidth:Number = el.getLayoutBoundsWidth(false /*postTransform*/); var value:Number; if (next) { if (_centeredItemDegrees > 0.1) return (_centeredItemCircumferenceEnd + _centeredItemCircumferenceBegin) / 2; value = _centeredItemCircumferenceEnd + elementWidth/2; if (value > _totalWidth) value -= _totalWidth; } else { if (_centeredItemDegrees < -0.1) return (_centeredItemCircumferenceEnd + _centeredItemCircumferenceBegin) / 2; value = _centeredItemCircumferenceBegin - elementWidth/2; if (value < 0) value += _totalWidth; } return value; } override protected function scrollPositionChanged():void { if (target) target.invalidateDisplayList(); } override public function getHorizontalScrollPositionDelta(scrollUnit:uint):Number { var g:GroupBase = target; if (!g || g.numElements == 0) return 0; var value:Number; switch (scrollUnit) { case NavigationUnit.LEFT: { value = target.horizontalScrollPosition - 30; if (value < 0) value += _totalWidth; return value - target.horizontalScrollPosition; } case NavigationUnit.RIGHT: { value = target.horizontalScrollPosition + 30; if (value > _totalWidth) value -= _totalWidth; return value - target.horizontalScrollPosition; } case NavigationUnit.PAGE_LEFT: return scrollPositionFromCenterToNext(false) - target.horizontalScrollPosition; case NavigationUnit.PAGE_RIGHT: return scrollPositionFromCenterToNext(true) - target.horizontalScrollPosition; case NavigationUnit.HOME: return 0; case NavigationUnit.END: return _totalWidth; default: return 0; } } /** * @private */ override public function getScrollPositionDeltaToElement(index:int):Point { var layoutTarget:GroupBase = target; if (!layoutTarget) return null; var gap:Number = this.gap; var totalWidthSoFar:Number = 0; var iter:LayoutIterator = new LayoutIterator(layoutTarget); var el:ILayoutElement = iter.nextElement(); if (!el) return null; totalWidthSoFar -= el.getLayoutBoundsWidth(false /*postTransform*/) / 2; iter.reset(); while (null != (el = iter.nextElement()) && iter.curIndex <= index) { var elementWidth:Number = el.getLayoutBoundsWidth(false /*postTransform*/); totalWidthSoFar += gap + elementWidth; } return new Point(totalWidthSoFar - elementWidth / 2 -gap - layoutTarget.horizontalScrollPosition, 0); } /** * @private */ override public function updateScrollRect(w:Number, h:Number):void { var g:GroupBase = target; if (!g) return; if (clipAndEnableScrolling) { // Since scroll position is reflected in our 3D calculations, // always set the top-left of the srcollRect to (0,0). g.scrollRect = new Rectangle(0, -700, w, h); } else g.scrollRect = null; } } } import spark.components.supportClasses.GroupBase; import mx.core.ILayoutElement; class LayoutIterator { private var _curIndex:int; private var _numVisited:int = 0; private var totalElements:int; private var _target:GroupBase; private var _loopIndex:int = -1; public function get curIndex():int { return _curIndex; } public function LayoutIterator(target:GroupBase, index:int=-1):void { totalElements = target.numElements; _target = target; _curIndex = index; } public function nextElement():ILayoutElement { while (_curIndex < totalElements - 1) { var el:ILayoutElement = _target.getElementAt(++_curIndex); if (el && el.includeInLayout) { ++_numVisited; return el; } } return null; } public function prevElement():ILayoutElement { while (_curIndex > 0) { var el:ILayoutElement = _target.getElementAt(--_curIndex); if (el && el.includeInLayout) { ++_numVisited; return el; } } return null; } public function nextElementWrapped():ILayoutElement { if (_loopIndex == -1) _loopIndex = _curIndex; else if (_loopIndex == _curIndex) return null; var el:ILayoutElement = nextElement(); if (el) return el; else if (_curIndex == totalElements - 1) _curIndex = -1; return nextElement(); } public function prevElementWrapped():ILayoutElement { if (_loopIndex == -1) _loopIndex = _curIndex; else if (_loopIndex == _curIndex) return null; var el:ILayoutElement = prevElement(); if (el) return el; else if (_curIndex == 0) _curIndex = totalElements; return prevElement(); } public function reset():void { _curIndex = -1; _numVisited = 0; _loopIndex = -1; } public function get numVisited():int { return _numVisited; } }
Initial URL
Initial Description
Taken from Evtim's source - http://evtimmy.com
Initial Title
Custom Wheel Layout
Initial Tags
Initial Language
ActionScript