/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
- /**
- * Copyright (c) 2009, Adobe Systems, Incorporated
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- * * Neither the name of the Adobe Systems, Incorporated. nor the names of
- * its contributors may be used to endorse or promote products derived
- * from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
- * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
- * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
- * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- package qs.controls.fisheyeClasses
- {
- import flash.display.DisplayObject;
- import flash.display.Sprite;
- import flash.events.Event;
- import flash.events.MouseEvent;
- import mx.core.ClassFactory;
- import mx.core.IDataRenderer;
- import mx.core.IFactory;
- import mx.core.UIComponent;
- import qs.controls.CachedLabel;
- import qs.controls.LayoutAnimator;
-
- /** 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( name="change", type="flash.events.Event" )]
- [Event( name="loaded", type="flash.events.Event" )]
- [DefaultProperty( "dataProvider" )]
- public class FisheyeBase extends UIComponent
- {
- public static const LOADED : String = "loaded";
-
- /** 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 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();
- dispatchEvent( new Event( LOADED ) );
- }
-
- 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( LOADED ) );
- }
- }
-
- //-----------------------------------------------------------------
- /* 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 <Component> 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 ( i = 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 */
- protected 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
- */
- protected 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 < renderers.length; i++ )
- {
- renderers[ i ][ stateProperty ] = ( i == selectedItemIndex ) ? selectedState : ( i == hilightedItemIndex ) ? rolloverState : unselectedValue;
- }
- }
- }
-
- /**
- * each item get scaled down based on its distance from the hliighted item. this is the equation we use
- * to figure out how much to scale down. The basic idea is this...we have two parameters that play a part
- * in how quickly we scale down, scaleRadius and scaleSlope. scaleRadius is the number of items on either
- * side of the hilighted item (inclusive) that we should be able to use to scale down. scaleSlope affects
- * how whether we scale down quickly with the first few items in the radius, or the last few items.
- * This equation essentially does that.
- */
- private function calcDistanceFactor( params : FisheyeParameters, distance : Number ) : Number
- {
- var mult : Number = 1 / params.scaleRadius;
- return Math.max( 0, 1 - Math.pow( distance * mult, params.scaleSlope ) );
- }
-
- /**
- * populates a set of items to fit into the distance axisLength, assuming nothing is hilighted, so they
- * all scale the same. It will attempt to scale them to match the defaultScale style */
- protected function populateMajorAxisForDefault( pdata : Array, axis : FisheyeAxis,
- axisLength : Number ) : FisheyeParameters
- {
- var vp : Number;
- var itemCount : int = pdata.length;
- var params : FisheyeParameters = new FisheyeParameters();
- populateParameters( params, false );
- var summedSpacing : Number = params.spacing * ( itemCount - 1 );
- var sizeSum : Number = 0;
- var pdataInst : FisheyeItem;
-
- for ( var i : int = 0; i < itemCount; i++ )
- sizeSum += pdata[ i ][ axis.EOM ];
-
- if ( sizeSum > 0 )
- {
- var maximumMinScale : Number = ( axisLength - summedSpacing ) / sizeSum;
- params.minScale = Math.min( params.minScale, maximumMinScale );
- }
- vp = 0;
-
- for ( i = 0; i < itemCount; i++ )
- {
- pdataInst = pdata[ i ];
- pdataInst.scale = params.minScale;
- pdataInst[ axis.pos ] = vp;
- vp += pdataInst[ axis.EOM ] * params.minScale + params.spacing;
- }
- return params;
- }
-
- /**
- * takes the parameters used in the fisheye equation, and adjusts them as best as possible to make sure the
- * items can fit into distance 'axisScale.' Right now it does this by scaling down the minScale parameter if necessary. That's
- * not entirely sufficient, but it does a pretty good job. For future work: If that's not sufficient, adjust the scaleRadius, scaleSlope,
- * and spacing parameter
- */
- private function adjustParameters( pdata : Array, targetIndex : Number, params : FisheyeParameters,
- axisSize : Number, axis : FisheyeAxis ) : void
- {
- var itemCount : int = pdata.length;
- var summedSpacing : Number = params.spacing * ( itemCount - 1 );
- var maxSum : Number = 0;
- var minSum : Number = 0;
-
- // given the constraint:
- // W(0) * S(0) + spacing + W(1) * S(1) + spacing + ... + W(N) * S(N) <= unscaledWidth
- // here we adjust the numbers that go into the calculation of S(i) to fit.
- // right now that just means adjusting minScale downward if necessary. We'll probably add some more complex heuristic later.
- for ( var i : int = 0; i < itemCount; i++ )
- {
- var pdataInst : FisheyeItem = pdata[ i ];
- var distanceFromItem : Number = Math.abs( targetIndex - i );
- var distanceFactor : Number = calcDistanceFactor( params, distanceFromItem );
- var maxFactor : Number = params.maxScale * distanceFactor;
- var minFactor : Number = ( 1 - distanceFactor );
- var itemSize : Number = pdataInst[ axis.EOM ];
- maxSum += itemSize * maxFactor;
- minSum += itemSize * minFactor;
- }
- var minScale : Number = ( minSum > 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 < itemCount; i++ )
- {
- pdataInst = pdata[ i ];
- var distanceFromItem : Number = Math.abs( targetIndex - i );
- var distanceFactor : Number = calcDistanceFactor( params, distanceFromItem );
- var scale : Number = Math.max( 0,
- params.minScale + ( params.maxScale - params.minScale ) * ( distanceFactor ) );
- pdataInst[ axis.pos ] = vp;
- pdataInst.scale = scale;
- vp += pdataInst[ axis.EOM ] * scale + params.spacing;
- }
- return params;
- }
-
- /**
- * given a set of scaled and laid out items, adjust them forward or backward to match the align property
- */
- protected function align( pdata : Array, axis : FisheyeAxis ) : void
- {
- var majorAlignValue : String = getStyle( axis.align );
- var itemCount : int = pdata.length;
- var pdataInst : FisheyeItem;
-
- if ( itemCount == 0 )
- return;
-
- switch ( majorAlignValue )
- {
- case "right":
- case "bottom":
- pdataInst = pdata[ itemCount - 1 ];
- var offset : Number = this[ axis.unscaled ] - ( pdataInst[ axis.pos ] + pdata[ itemCount - 1 ][ axis.EOM ] * pdataInst.scale );
- for ( var i : int = 0; i < itemCount; i++ )
- {
- pdata[ i ][ axis.pos ] += offset;
- }
- break;
- case "left":
- case "top":
- break;
- case "center":
- default:
- var midIndex : int = Math.floor( itemCount / 2 );
- pdataInst = pdata[ itemCount - 1 ];
- var rightPos : Number = pdataInst[ axis.pos ] + pdataInst[ axis.EOM ] * pdataInst.scale;
- offset = ( this[ axis.unscaled ] / 2 - ( rightPos ) / 2 );
- for ( i = 0; i < itemCount; i++ )
- {
- pdata[ i ][ axis.pos ] += offset;
- }
- break;
- }
- }
-
- /**
- * overridden in the subclasses
- */
- protected function generateLayout() : void
- {
- }
-
- override protected function updateDisplayList( unscaledWidth : Number, unscaledHeight : Number ) : void
- {
- graphics.clear();
- graphics.moveTo( 0, 0 );
- graphics.beginFill( 0, 0 );
- graphics.drawRect( 0, 0, unscaledWidth, unscaledHeight );
- // update the mask
- mask.width = unscaledWidth;
- mask.height = unscaledHeight;
- animator.invalidateLayout();
- }
-
- override public function styleChanged( styleProp : String ) : void
- {
- if ( styleProp == "animationSpeed" )
- animator.animationSpeed = getStyle( "animationSpeed" );
- invalidateSize();
- invalidateDisplayList();
- animator.invalidateLayout();
- }
- }
- }