package qs.controls.fisheyeClasses { import mx.containers.VBox; import mx.core.UIComponent; import mx.controls.Text; import flash.text.TextLineMetrics; import mx.core.UITextField; import flash.events.MouseEvent; import mx.core.UIComponent; import Infinity; import mx.core.IDataRenderer; import flash.geom.Matrix; import mx.core.IFactory; import mx.core.ClassFactory; import flash.utils.Timer; import flash.events.TimerEvent; import flash.geom.Point; import flash.display.Scene; import flash.events.Event; import flash.display.Sprite; import flash.display.Shape; import flash.display.DisplayObject; //import mx.charts.chartClasses.RenderData; import qs.controls.LayoutAnimator; import qs.controls.LayoutTarget; import qs.controls.fisheyeClasses.FisheyeAxis; import qs.controls.fisheyeClasses.FisheyeParameters; import mx.core.UIComponent; import flash.net.FileFilter; import qs.controls.CachedLabel; /** the horizontal alignment. */ [Style(name="horizontalAlign", type="String", enumeration="left,center,right,justified", inherit="no")] /** the vertical alignment */ [Style(name="verticalAlign", type="String", enumeration="top,center,bottom,justified", inherit="no")] /** the amount of space, in pixels, between invidual items */ [Style(name="defaultSpacing", type="Number", inherit="no")] /** the amount of space, in pixels, between invidual items when hilighted*/ [Style(name="hilightSpacing", type="Number", inherit="no")] /** the property on the item renderer to assign to when the renderer' state changes */ [Style(name="stateProperty", type="String", inherit="no")] /** the value to assign to 'stateProperty' on the renderer when the item is hilighted */ [Style(name="rolloverValue", type="String", inherit="no")] /** the value to assign to 'stateProperty' on the renderer when the item is selected */ [Style(name="selectedValue", type="String", inherit="no")] /** the value to assign to 'stateProperty' on the renderer when some other item is selected */ [Style(name="unselectedValue", type="String", inherit="no")] /** the value to assign to 'stateProperty' on the renderer when the item is in its default state */ [Style(name="defaultValue", type="String", inherit="no")] /** the scale factor assigned to renderers when no item is hilighted or selected */ [Style(name="defaultScale", type="Number", inherit="no")] /** the minimum scale factor assigned to renderers on screen. The actual scale factor assigned to * an item will range between minScale and hilightMaxScale based on its distance from the hilighted or selected item */ [Style(name="hilightMinScale", type="Number", inherit="no")] /** the maximum scale factor assigned to renderers on screen. The actual scale factor assigned to * an item will range between minScale and hilightMaxScale based on its distance from the hilighted or selected item */ [Style(name="hilightMaxScale", type="Number", inherit="no")] /** 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. * 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*/ [Style(name="hilightScaleSlope", type="Number", inherit="no")] /** 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 * to either side will scale up. How much each item scales is affected by the scaleSlope style.*/ [Style(name="hilightScaleRadius", type="Number", inherit="no")] /** how quickly items animate to their target location when the layout of the renderers change. A value of 1 will * snap instantly to the new value, while a value of 0 will never change */ [Style(name="animationSpeed", type="Number", inherit="no")] [Event("change")] [DefaultProperty("dataProvider")] public class actionscript_extra_fisheye_qs_controls_fisheyeClasses_FisheyeBase extends UIComponent { /** the data items driving the component */ private var _items:Array = []; /** when a new dataprovider is assigned, we keep it in reserve until we have a chance * to generate new renderers for it. This is the temporary holding pen for those new items */ private var _pendingItems:Array; protected var itemsChanged:Boolean = false; /** true if the renderers need to be regenerated */ protected var renderersDirty:Boolean = true; /** the renderers representing the data items, one for each item */ protected var renderers:Array = []; /** the currently hilighted item */ protected var hilightedItemIndex:Number = NaN; /** the currently selected item */ protected var selectedItemIndex:Number = NaN; /** @private */ private var _selectionEnabled:Boolean = true; /** the factory that generates item renderers */ private var _itemRendererFactory:IFactory; /** * the object that manages animating the children layout */ protected var animator:LayoutAnimator; /** Constructor */ public function actionscript_extra_fisheye_qs_controls_fisheyeClasses_FisheyeBase() { super(); _itemRendererFactory= new ClassFactory(CachedLabel); addEventListener(MouseEvent.MOUSE_MOVE,updateHilight); addEventListener(MouseEvent.ROLL_OUT,removeHilight); addEventListener(MouseEvent.MOUSE_DOWN,updateSelection); var maskShape:Sprite = new Sprite(); addChild(maskShape); mask = maskShape; maskShape.graphics.beginFill(0); maskShape.graphics.drawRect(0,0,10,10); maskShape.graphics.endFill(); animator = new LayoutAnimator(); animator.layoutFunction = generateLayout; } //----------------------------------------------------------------- /** the data source */ public function set dataProvider(value:Array):void { _pendingItems= value; renderersDirty = true; itemsChanged = true; invalidateProperties(); } public function get dataProvider():Array { return _items; } //----------------------------------------------------------------- public function set selectionEnabled(value:Boolean):void { if(_selectionEnabled == value) return; _selectionEnabled = value; selectedIndex = selectedIndex; } public function get selectionEnabled():Boolean { return _selectionEnabled; } [Bindable("change")] public function get selectedItem():Object { return (isNaN(selectedItemIndex)? null:_items[selectedItemIndex]); } public function set selectedItem(value:Object):void { var newIndex:Number; for(var i:int = 0;i<_items.length;i++) { if(value == _items[i]) { newIndex = i; break; } } selectedIndex = newIndex; } [Bindable("change")] public function get selectedIndex():int { return (isNaN(selectedItemIndex)? -1:selectedItemIndex); } public function set selectedIndex(value:int):void { var v:Number = (value < 0 || value >= _items.length)? NaN:value; if(v != selectedItemIndex) { selectedItemIndex = v; updateState(); animator.invalidateLayout(); dispatchEvent(new Event("change")); } } //----------------------------------------------------------------- /* These private get properties are wrappers around styles that return defaults if unset. * It saves me from having to write a CSS selector, which * I really should do at some point */ protected function get defaultSpacingWithDefault():Number { var result:Number= getStyle("defaultSpacing"); if(isNaN(result)) result = 0; return result; } protected function get maxScaleWithDefault():Number { var result:Number= getStyle("hilightMaxScale"); if(isNaN(result)) result = 1; return result; } //----------------------------------------------------------------- /** * by making the itemRenderer be of type IFactory, * developers can define it inline using the tag */ public function get itemRenderer():IFactory { return _itemRendererFactory; } public function set itemRenderer(value:IFactory):void { _itemRendererFactory = value; renderersDirty = true; invalidateProperties(); } //----------------------------------------------------------------- override protected function commitProperties():void { // its now safe to switch over new dataProviders. if(_pendingItems != null) { _items = _pendingItems; _pendingItems = null; } itemsChanged = false; if(renderersDirty) { // something has forced us to reallocate our renderers. start by throwing out the old ones. renderersDirty = false; var mask:DisplayObject = mask; for(var i:int=numChildren-1;i>= 0;i--) removeChildAt(i); addChild(mask); renderers = []; // allocate new renderers, assign the data. for(var i:int = 0;i<_items.length;i++) { var renderer:UIComponent = _itemRendererFactory.newInstance(); IDataRenderer(renderer).data = _items[i]; renderers[i] = renderer; addChild(renderer); } animator.items = renderers; } invalidateSize(); } private function removeHilight(e:MouseEvent):void { // called on rollout. Clear out any hilight, and reset our layout. hilightedItemIndex = NaN; updateState(); animator.invalidateLayout(); } /** finds the item that would be closest to the x/y position if it were hilighted */ protected function findItemForPosition(xPos:Number,yPos:Number):Number { return NaN; } /** called on mouse click to set or clear the selection */ private function updateSelection(e:MouseEvent):void { if(_selectionEnabled == false) return; var newSelection:Number = findItemForPosition(this.mouseX,this.mouseY); if(selectedItemIndex == newSelection) selectedIndex = -1; else selectedIndex = newSelection; updateState(); animator.invalidateLayout(); } /** called on mouse move to update the hilight */ private function updateHilight(e:MouseEvent):void { var newHilight:Number = findItemForPosition(this.mouseX,this.mouseY); if(newHilight == hilightedItemIndex) return; hilightedItemIndex = newHilight; updateState(); animator.invalidateLayout(); } /** * update the state properties of all of the items, based on * the current hilighted and/or selected items */ private function updateState():void { var stateProperty:String = getStyle("stateProperty"); if(stateProperty != null) { var rolloverState:String = getStyle("rolloverValue"); var selectedState:String = getStyle("selectedValue"); var unselectedValue:String = getStyle("unselectedValue"); if (unselectedValue == null || (isNaN(selectedItemIndex) && isNaN(hilightedItemIndex))) unselectedValue = getStyle("defaultValue"); for(var i:int=0;i 0) { var maximumMinScale:Number = (axisLength - summedSpacing) / sizeSum; params.minScale = Math.min(params.minScale,maximumMinScale); } vp = 0; for(var i:int=0;i 0)? ((axisSize - summedSpacing - maxSum) / minSum):0; // 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 // to do that. So let's contrain it to minScale. minScale = Math.min(params.minScale,minScale); params.minScale = minScale; } /** * populate a parameters structure from the various styles */ private function populateParameters(params:FisheyeParameters,hilighted:Boolean):void { if(hilighted == false) { params.minScale = getStyle("defaultScale"); if(isNaN(params.minScale)) params.minScale = .5; params.spacing = defaultSpacingWithDefault; } else { params.minScale = getStyle("hilightMinScale"); if(isNaN(params.minScale)) { params.minScale = getStyle("defaultScale"); if(isNaN(params.minScale)) params.minScale = .5; } params.spacing = getStyle("hilightSpacing"); if(isNaN(params.spacing)) params.spacing = defaultSpacingWithDefault; } params.maxScale = getStyle("hilightMaxScale"); if(isNaN(params.maxScale )) params.maxScale = 1; params.scaleRadius = getStyle("hilightScaleRadius"); if(isNaN(params.scaleRadius)) params.scaleRadius = 2; params.scaleRadius = Math.max(1,params.scaleRadius); params.scaleSlope = getStyle("hilightScaleSlope"); if(isNaN(params.scaleSlope)) params.scaleSlope = .75; } /** * populates a set of items to fit into the distance axisLength, assuming targetIndex is hilighted. */ protected function populateMajorAxisFor(pdata:Array,targetIndex:Number,axisSize:Number,axis:FisheyeAxis):FisheyeParameters { var vp:Number; var itemCount:int = pdata.length; var pdataInst:FisheyeItem; var params:FisheyeParameters = new FisheyeParameters(); populateParameters(params,true); adjustParameters(pdata,targetIndex,params,axisSize,axis); vp = 0; for(var i:int=0;i