////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package org.apache.flex.html.beads.layouts
{
	import org.apache.flex.core.IBeadLayout;
	import org.apache.flex.core.ILayoutChild;
    import org.apache.flex.core.ILayoutParent;
	import org.apache.flex.core.IParent;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.core.IUIBase;
	import org.apache.flex.core.IViewport;
	import org.apache.flex.core.IViewportModel;
	import org.apache.flex.html.supportClasses.Viewport;
	import org.apache.flex.core.UIBase;
	import org.apache.flex.core.ValuesManager;
	import org.apache.flex.events.Event;
	import org.apache.flex.events.IEventDispatcher;

    /**
     *  The FlexibleFirstChildHorizontalLayout class is a simple layout
     *  bead.  It takes the set of children and lays them out
     *  horizontally in one row, separating them according to
     *  CSS layout rules for margin and padding styles. But it
     *  will size the first child to take up as much or little
     *  room as possible.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class FlexibleFirstChildHorizontalLayout implements IBeadLayout
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function FlexibleFirstChildHorizontalLayout()
		{
		}

        // the strand/host container is also an ILayoutChild because
        // can have its size dictated by the host's parent which is
        // important to know for layout optimization
        private var host:ILayoutChild;

        /**
         *  @copy org.apache.flex.core.IBead#strand
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function set strand(value:IStrand):void
		{
            host = value as ILayoutChild;
		}

		private var _viewportModel:IViewportModel;

		/**
		 *  The data that describes the viewport used by this layout.
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 10.2
		 *  @playerversion AIR 2.6
		 *  @productversion FlexJS 0.0
		 */
		public function get viewportModel():IViewportModel
		{
			return _viewportModel;
		}
		public function set viewportModel(value:IViewportModel):void
		{
			_viewportModel = value;
		}

        private var _maxWidth:Number;

        /**
         *  @copy org.apache.flex.core.IBead#maxWidth
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get maxWidth():Number
        {
            return _maxWidth;
        }

        /**
         *  @private
         */
        public function set maxWidth(value:Number):void
        {
            _maxWidth = value;
        }

        private var _maxHeight:Number;

        /**
         *  @copy org.apache.flex.core.IBead#maxHeight
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get maxHeight():Number
        {
            return _maxHeight;
        }

        /**
         *  @private
         */
        public function set maxHeight(value:Number):void
        {
            _maxHeight = value;
        }

        /**
         * @copy org.apache.flex.core.IBeadLayout#layout
         */
		public function layout():Boolean
		{
			var layoutParent:ILayoutParent = host.getBeadByType(ILayoutParent) as ILayoutParent;
			var contentView:IParent = layoutParent.contentView;
            var hostSizedToContent:Boolean = host.isHeightSizedToContent();

			// this layout will use and modify the IViewportMode
			var viewport:IViewport = host.getBeadByType(IViewport) as IViewport;
			if (viewport) viewportModel = viewport.model;

			var n:int = contentView.numElements;
			var marginLeft:Object;
			var marginRight:Object;
			var marginTop:Object;
			var marginBottom:Object;
			var margin:Object;
			maxHeight = 0;
			var verticalMargins:Array = [];

            var xx:Number = layoutParent.resizableView.width;
            if (isNaN(xx) || xx <= 0)
                return true;
            var padding:Object = determinePadding();
            // some browsers don't like it when you go all the way to the right edge.
            xx -= padding.paddingLeft + padding.paddingRight + 1;

            for (var i:int = n - 1; i >= 0; i--)
			{
				var child:IUIBase = contentView.getElementAt(i) as IUIBase;
				margin = ValuesManager.valuesImpl.getValue(child, "margin");
				if (margin is Array)
				{
					if (margin.length == 1)
						marginLeft = marginTop = marginRight = marginBottom = margin[0];
					else if (margin.length <= 3)
					{
						marginLeft = marginRight = margin[1];
						marginTop = marginBottom = margin[0];
					}
					else if (margin.length == 4)
					{
						marginLeft = margin[3];
						marginBottom = margin[2];
						marginRight = margin[1];
						marginTop = margin[0];
					}
				}
				else if (margin == null)
				{
					marginLeft = ValuesManager.valuesImpl.getValue(child, "margin-left");
					marginTop = ValuesManager.valuesImpl.getValue(child, "margin-top");
					marginRight = ValuesManager.valuesImpl.getValue(child, "margin-right");
					marginBottom = ValuesManager.valuesImpl.getValue(child, "margin-bottom");
				}
				else
				{
					marginLeft = marginTop = marginBottom = marginRight = margin;
				}
				var ml:Number;
				var mr:Number;
				var mt:Number;
				var mb:Number;
				var lastmr:Number;
				mt = Number(marginTop);
				if (isNaN(mt))
					mt = 0;
				mb = Number(marginBottom);
				if (isNaN(mb))
					mb = 0;
				if (marginLeft == "auto")
					ml = 0;
				else
				{
					ml = Number(marginLeft);
					if (isNaN(ml))
						ml = 0;
				}
				if (marginRight == "auto")
					mr = 0;
				else
				{
					mr = Number(marginRight);
					if (isNaN(mr))
						mr = 0;
				}
				child.y = mt;
				maxHeight = Math.max(maxHeight, mt + child.height + mb);
				if (i == 0)
                {
                    child.x = ml;
                    child.width = xx - mr;
                }
				else
                    child.x = xx - child.width - mr;
				xx -= child.width + mr + ml;
				lastmr = mr;
				var valign:Object = ValuesManager.valuesImpl.getValue(child, "vertical-align");
				verticalMargins.push({ marginTop: mt, marginBottom: mb, valign: valign });
			}
			for (i = 0; i < n; i++)
			{
				var obj:Object = verticalMargins[0]
				child = contentView.getElementAt(i) as IUIBase;
				if (obj.valign == "middle")
					child.y = (maxHeight - child.height) / 2;
				else if (valign == "bottom")
					child.y = maxHeight - child.height - obj.marginBottom;
				else
					child.y = obj.marginTop;
			}
            if (hostSizedToContent)
                ILayoutChild(contentView).setHeight(maxHeight, true);

			// Only return true if the contentView needs to be larger; that new
			// size is stored in the model.
			var sizeChanged:Boolean = false;
			if (viewportModel != null) {
				if (viewportModel.contentHeight < maxHeight) {
					viewportModel.contentHeight = maxHeight;
					sizeChanged = true;
				}
				if (viewportModel.contentWidth < xx) {
					viewportModel.contentWidth = xx;
					sizeChanged = true;
				}
			} else {
				sizeChanged = true;
			}

            return sizeChanged;
		}

        // TODO (aharui): utility class or base class
        /**
         *  Determines the top and left padding values, if any, as set by
         *  padding style values. This includes "padding" for all padding values
         *  as well as "padding-left" and "padding-top".
         *
         *  Returns an object with paddingLeft and paddingTop properties.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        protected function determinePadding():Object
        {
            var paddingLeft:Object;
            var paddingTop:Object;
            var paddingRight:Object;
            var padding:Object = ValuesManager.valuesImpl.getValue(host, "padding");
            if (typeof(padding) == "Array")
            {
                if (padding.length == 1)
                    paddingLeft = paddingTop = paddingRight = padding[0];
                else if (padding.length <= 3)
                {
                    paddingLeft = padding[1];
                    paddingTop = padding[0];
                    paddingRight = padding[1];
                }
                else if (padding.length == 4)
                {
                    paddingLeft = padding[3];
                    paddingTop = padding[0];
                    paddingRight = padding[1];
                }
            }
            else if (padding == null)
            {
                paddingLeft = ValuesManager.valuesImpl.getValue(host, "padding-left");
                paddingTop = ValuesManager.valuesImpl.getValue(host, "padding-top");
                paddingRight = ValuesManager.valuesImpl.getValue(host, "padding-right");
            }
            else
            {
                paddingLeft = paddingTop = paddingRight = padding;
            }
            var pl:Number = Number(paddingLeft);
            var pt:Number = Number(paddingTop);
            var pr:Number = Number(paddingRight);
            if (isNaN(pl))
                pl = 0;
            if (isNaN(pt))
                pt = 0;
            if (isNaN(pr))
                pr = 0;
            return {paddingLeft:pl, paddingTop:pt, paddingRight:pr};
        }

    }

}

////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package org.apache.flex.html.beads.layouts
{
	import org.apache.flex.core.IBeadLayout;
	import org.apache.flex.core.ILayoutChild;
    import org.apache.flex.core.ILayoutParent;
	import org.apache.flex.core.IParent;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.core.IUIBase;
	import org.apache.flex.core.IViewport;
	import org.apache.flex.core.IViewportModel;
    import org.apache.flex.core.UIMetrics;
	import org.apache.flex.core.UIBase;
	import org.apache.flex.core.ValuesManager;
	import org.apache.flex.events.Event;
	import org.apache.flex.events.IEventDispatcher;
    import org.apache.flex.html.supportClasses.Viewport;
    import org.apache.flex.utils.BeadMetrics;

    /**
     *  The FlexibleFirstChildHorizontalLayout class is a simple layout
     *  bead.  It takes the set of children and lays them out
     *  horizontally in one row, separating them according to
     *  CSS layout rules for margin and padding styles. But it
     *  will size the first child to take up as much or little
     *  room as possible.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class FlexibleFirstChildHorizontalLayout implements IBeadLayout
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function FlexibleFirstChildHorizontalLayout()
		{
		}

        // the strand/host container is also an ILayoutChild because
        // can have its size dictated by the host's parent which is
        // important to know for layout optimization
        private var host:ILayoutChild;

        /**
         *  @copy org.apache.flex.core.IBead#strand
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function set strand(value:IStrand):void
		{
            host = value as ILayoutChild;
		}

		private var _viewportModel:IViewportModel;

		/**
		 *  The data that describes the viewport used by this layout.
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 10.2
		 *  @playerversion AIR 2.6
		 *  @productversion FlexJS 0.0
		 */
		public function get viewportModel():IViewportModel
		{
			return _viewportModel;
		}
		public function set viewportModel(value:IViewportModel):void
		{
			_viewportModel = value;
		}

        private var _maxWidth:Number;

        /**
         *  @copy org.apache.flex.core.IBead#maxWidth
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get maxWidth():Number
        {
            return _maxWidth;
        }

        /**
         *  @private
         */
        public function set maxWidth(value:Number):void
        {
            _maxWidth = value;
        }

        private var _maxHeight:Number;

        /**
         *  @copy org.apache.flex.core.IBead#maxHeight
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get maxHeight():Number
        {
            return _maxHeight;
        }

        /**
         *  @private
         */
        public function set maxHeight(value:Number):void
        {
            _maxHeight = value;
        }

        /**
         * @copy org.apache.flex.core.IBeadLayout#layout
         */
		public function layout():Boolean
		{
			var layoutParent:ILayoutParent = host.getBeadByType(ILayoutParent) as ILayoutParent;
			var contentView:IParent = layoutParent.contentView;
            var hostSizedToContent:Boolean = host.isHeightSizedToContent();

			// this layout will use and modify the IViewportMode
			var viewport:IViewport = host.getBeadByType(IViewport) as IViewport;
			if (viewport) viewportModel = viewport.model;

			var n:int = contentView.numElements;
			var marginLeft:Object;
			var marginRight:Object;
			var marginTop:Object;
			var marginBottom:Object;
			var margin:Object;
			maxHeight = 0;
			var verticalMargins:Array = [];

            var xx:Number = layoutParent.resizableView.width;
            if (isNaN(xx) || xx <= 0)
                return true;
            var uiMetrics:UIMetrics = BeadMetrics.getMetrics(layoutParent.resizableView);
            xx -= uiMetrics.left + uiMetrics.right + 1; // some browsers won't layout to the edge

            for (var i:int = n - 1; i >= 0; i--)
			{
				var child:IUIBase = contentView.getElementAt(i) as IUIBase;
				margin = ValuesManager.valuesImpl.getValue(child, "margin");
				if (margin is Array)
				{
					if (margin.length == 1)
						marginLeft = marginTop = marginRight = marginBottom = margin[0];
					else if (margin.length <= 3)
					{
						marginLeft = marginRight = margin[1];
						marginTop = marginBottom = margin[0];
					}
					else if (margin.length == 4)
					{
						marginLeft = margin[3];
						marginBottom = margin[2];
						marginRight = margin[1];
						marginTop = margin[0];
					}
				}
				else if (margin == null)
				{
					marginLeft = ValuesManager.valuesImpl.getValue(child, "margin-left");
					marginTop = ValuesManager.valuesImpl.getValue(child, "margin-top");
					marginRight = ValuesManager.valuesImpl.getValue(child, "margin-right");
					marginBottom = ValuesManager.valuesImpl.getValue(child, "margin-bottom");
				}
				else
				{
					marginLeft = marginTop = marginBottom = marginRight = margin;
				}
				var ml:Number;
				var mr:Number;
				var mt:Number;
				var mb:Number;
				var lastmr:Number;
				mt = Number(marginTop);
				if (isNaN(mt))
					mt = 0;
				mb = Number(marginBottom);
				if (isNaN(mb))
					mb = 0;
				if (marginLeft == "auto")
					ml = 0;
				else
				{
					ml = Number(marginLeft);
					if (isNaN(ml))
						ml = 0;
				}
				if (marginRight == "auto")
					mr = 0;
				else
				{
					mr = Number(marginRight);
					if (isNaN(mr))
						mr = 0;
				}
				child.y = mt;
				maxHeight = Math.max(maxHeight, mt + child.height + mb);
				if (i == 0)
                {
                    child.x = ml;
                    child.width = xx - mr;
                }
				else
                    child.x = xx - child.width - mr;
				xx -= child.width + mr + ml;
				lastmr = mr;
				var valign:Object = ValuesManager.valuesImpl.getValue(child, "vertical-align");
				verticalMargins.push({ marginTop: mt, marginBottom: mb, valign: valign });
			}
			for (i = 0; i < n; i++)
			{
				var obj:Object = verticalMargins[0]
				child = contentView.getElementAt(i) as IUIBase;
				if (obj.valign == "middle")
					child.y = (maxHeight - child.height) / 2;
				else if (valign == "bottom")
					child.y = maxHeight - child.height - obj.marginBottom;
				else
					child.y = obj.marginTop;
			}
            if (hostSizedToContent)
                ILayoutChild(contentView).setHeight(maxHeight, true);

			// Only return true if the contentView needs to be larger; that new
			// size is stored in the model.
			var sizeChanged:Boolean = false;
			if (viewportModel != null) {
				if (viewportModel.contentHeight < maxHeight) {
					viewportModel.contentHeight = maxHeight;
					sizeChanged = true;
				}
				if (viewportModel.contentWidth < xx) {
					viewportModel.contentWidth = xx;
					sizeChanged = true;
				}
			} else {
				sizeChanged = true;
			}

            return sizeChanged;
		}

    }

}

upgrade to use BeadMetrics

upgrade to use BeadMetrics


package battlecode.client.viewer.render {
    import battlecode.common.MapLocation;
    import battlecode.common.RobotType;
    import battlecode.common.Team;
    import battlecode.serial.RoundDelta;
    import battlecode.serial.RoundStats;
    import battlecode.world.GameMap;
    import battlecode.world.signals.*;

    public class DrawState extends DefaultSignalHandler {
        // state
        private var mines:Array; // Team[][]
        private var neutralEncampments:Object;
        private var encampments:Object;
        private var groundRobots:Object;

        // stats
        private var aPoints:Number;
        private var bPoints:Number;
        private var aGatheredPoints:Number;
        private var bGatheredPoints:Number;
        private var roundNum:uint;

        // immutables
        private var map:GameMap;
        private var origin:MapLocation;

        public function DrawState(map:GameMap) {
            neutralEncampments = new Object();
            encampments = new Object();
            groundRobots = new Object();

            mines = new Array();
            for (var i:int = 0; i < map.getWidth(); i++) {
                mines[i] = new Array();
                for (var j:int = 0; j < map.getHeight(); j++) {
                    mines[i][j] = null;
                }
            }

            aPoints = 0;
            bPoints = 0;
            aGatheredPoints = 0;
            bGatheredPoints = 0;
            roundNum = 1;

            this.map = map;
            this.origin = map.getOrigin();
        }

        ///////////////////////////////////////////////////////
        ///////////////// PROPERTY GETTERS ////////////////////
        ///////////////////////////////////////////////////////

        public function getMines():Array {
            return mines;
        }

        public function getNeutralEncampments():Object {
            return neutralEncampments;
        }

        public function getEncampments():Object {
            return encampments
        }

        public function getGroundRobots():Object {
            return groundRobots;
        }

        public function getPoints(team:String):uint {
            return (team == Team.A) ? aPoints : bPoints;
        }

        ///////////////////////////////////////////////////////
        /////////////////// CORE FUNCTIONS ////////////////////
        ///////////////////////////////////////////////////////

        private function copyStateFrom(state:DrawState):void {
            var a:*;

            mines = new Array();
            for (var i:int = 0; i < map.getWidth(); i++) {
                mines[i] = new Array();
                for (var j:int = 0; j < map.getHeight(); j++) {
                    mines[i][j] = state.mines[i][j];
                }
            }

            neutralEncampments = new Object();
            for (a in state.neutralEncampments) {
                neutralEncampments[a] = state.neutralEncampments[a].clone();
            }

            encampments = new Object();
            for (a in state.encampments) {
                encampments[a] = state.encampments[a].clone();
            }

            groundRobots = new Object();
            for (a in state.groundRobots) {
                groundRobots[a] = state.groundRobots[a].clone();
            }

            roundNum = state.roundNum;
        }

        public function clone():DrawState {
            var state:DrawState = new DrawState(map);
            state.copyStateFrom(this);
            return state;
        }

        public function applyDelta(delta:RoundDelta):void {
            updateRound();
            for each (var signal:Signal in delta.getSignals()) {
                applySignal(signal);
            }
            processEndOfRound();
        }

        public function applySignal(signal:Signal):void {
            if (signal != null) signal.accept(this);
        }

        public function applyStats(stats:RoundStats):void {
            aPoints = stats.getPoints(Team.A);
            bPoints = stats.getPoints(Team.B);
            aGatheredPoints = stats.getGatheredPoints(Team.A);
            bGatheredPoints = stats.getGatheredPoints(Team.B);
        }

        public function updateRound():void {
            var a:*, i:uint, j:uint;
            var o:DrawRobot;

            for (a in groundRobots) {
                o = groundRobots[a] as DrawRobot;
                o.updateRound();
                if (!o.isAlive()) {
                    if (o.parent) {
                        o.parent.removeChild(o);
                    }
                    delete groundRobots[a];
                }
            }

            for (a in encampments) {
                o = encampments[a] as DrawRobot;
                o.updateRound();
                if (!o.isAlive()) {
                    if (o.parent) {
                        o.parent.removeChild(o);
                    }
                    delete encampments[a];
                }
            }
        }

        private function processEndOfRound():void {
            roundNum++;
        }

        ///////////////////////////////////////////////////////
        ////////////// PRIVATE HELPER FUNCTIONS ///////////////
        ///////////////////////////////////////////////////////

        private function getRobot(id:uint):DrawRobot {
            if (groundRobots[id]) return groundRobots[id] as DrawRobot;
            if (encampments[id]) return encampments[id] as DrawRobot;
            return null;
        }

        private function removeRobot(id:uint):void {
            if (groundRobots[id]) delete groundRobots[id];
            if (encampments[id]) delete encampments[id];
        }

        private function translateCoordinates(loc:MapLocation):MapLocation {
            return new MapLocation(loc.getX() - origin.getX(), loc.getY() - origin.getY());
        }

        ///////////////////////////////////////////////////////
        /////////////////// SIGNAL HANDLERS ///////////////////
        ///////////////////////////////////////////////////////

        override public function visitAttackSignal(s:AttackSignal):* {
            getRobot(s.getRobotID()).attack(s.getTargetLoc());
        }

        override public function visitBroadcastSignal(s:BroadcastSignal):* {
            getRobot(s.getRobotID()).broadcast();
        }

        override public function visitCaptureSignal(s:CaptureSignal):* {
            var o:DrawRobot = neutralEncampments[s.getLocation()];
            if (o) {
                if (o.parent) {
                    o.parent.removeChild(o);
                }
                delete neutralEncampments[s.getLocation()];
            }
        }

        override public function visitDeathSignal(s:DeathSignal):* {
            var robot:DrawRobot = getRobot(s.getRobotID());
            robot.destroyUnit();
        }

        override public function visitEnergonChangeSignal(s:EnergonChangeSignal):* {

        }

        override public function visitFluxChangeSignal(s:FluxChangeSignal):* {

        }

        override public function visitIndicatorStringSignal(s:IndicatorStringSignal):* {
            getRobot(s.getRobotID()).setIndicatorString(s.getIndicatorString(), s.getIndex());
        }

        override public function visitMineSignal(s:MineSignal):* {
            var loc:MapLocation = translateCoordinates(s.getLocation());
            if (s.isBirth()) {
                mines[loc.getX()][loc.getY()] = s.getTeam();
            } else {
                mines[loc.getX()][loc.getY()] = null;
            }
        }

        override public function visitMovementSignal(s:MovementSignal):* {
            getRobot(s.getRobotID()).moveToLocation(s.getTargetLoc());
        }

        override public function visitNodeBirthSignal(s:NodeBirthSignal):* {
            var encampment:DrawRobot = new DrawRobot(0, RobotType.ENCAMPMENT, Team.NEUTRAL);
            encampment.setLocation(s.getLocation());
            neutralEncampments[s.getLocation()] = encampment;
        }

        override public function visitSpawnSignal(s:SpawnSignal):* {
            var robot:DrawRobot = new DrawRobot(s.getRobotID(), s.getRobotType(), s.getTeam());
            robot.setLocation(s.getLocation());
            groundRobots[s.getRobotID()] = robot;
        }

    }

}

package battlecode.client.viewer.render {
    import battlecode.common.MapLocation;
    import battlecode.common.RobotType;
    import battlecode.common.Team;
    import battlecode.serial.RoundDelta;
    import battlecode.serial.RoundStats;
    import battlecode.world.GameMap;
    import battlecode.world.signals.*;

    public class DrawState extends DefaultSignalHandler {
        // state
        private var mines:Array; // Team[][]
        private var neutralEncampments:Object;
        private var encampments:Object;
        private var groundRobots:Object;

        // stats
        private var aPoints:Number;
        private var bPoints:Number;
        private var aGatheredPoints:Number;
        private var bGatheredPoints:Number;
        private var roundNum:uint;

        // immutables
        private var map:GameMap;
        private var origin:MapLocation;

        public function DrawState(map:GameMap) {
            neutralEncampments = new Object();
            encampments = new Object();
            groundRobots = new Object();

            mines = new Array();
            for (var i:int = 0; i < map.getWidth(); i++) {
                mines[i] = new Array();
                for (var j:int = 0; j < map.getHeight(); j++) {
                    mines[i][j] = null;
                }
            }

            aPoints = 0;
            bPoints = 0;
            aGatheredPoints = 0;
            bGatheredPoints = 0;
            roundNum = 1;

            this.map = map;
            this.origin = map.getOrigin();
        }

        ///////////////////////////////////////////////////////
        ///////////////// PROPERTY GETTERS ////////////////////
        ///////////////////////////////////////////////////////

        public function getMines():Array {
            return mines;
        }

        public function getNeutralEncampments():Object {
            return neutralEncampments;
        }

        public function getEncampments():Object {
            return encampments
        }

        public function getGroundRobots():Object {
            return groundRobots;
        }

        public function getPoints(team:String):uint {
            return (team == Team.A) ? aPoints : bPoints;
        }

        ///////////////////////////////////////////////////////
        /////////////////// CORE FUNCTIONS ////////////////////
        ///////////////////////////////////////////////////////

        private function copyStateFrom(state:DrawState):void {
            var a:*;

            mines = new Array();
            for (var i:int = 0; i < map.getWidth(); i++) {
                mines[i] = new Array();
                for (var j:int = 0; j < map.getHeight(); j++) {
                    mines[i][j] = state.mines[i][j];
                }
            }

            neutralEncampments = new Object();
            for (a in state.neutralEncampments) {
                neutralEncampments[a] = state.neutralEncampments[a].clone();
            }

            encampments = new Object();
            for (a in state.encampments) {
                encampments[a] = state.encampments[a].clone();
            }

            groundRobots = new Object();
            for (a in state.groundRobots) {
                groundRobots[a] = state.groundRobots[a].clone();
            }

            roundNum = state.roundNum;
        }

        public function clone():DrawState {
            var state:DrawState = new DrawState(map);
            state.copyStateFrom(this);
            return state;
        }

        public function applyDelta(delta:RoundDelta):void {
            updateRound();
            for each (var signal:Signal in delta.getSignals()) {
                applySignal(signal);
            }
            processEndOfRound();
        }

        public function applySignal(signal:Signal):void {
            if (signal != null) signal.accept(this);
        }

        public function applyStats(stats:RoundStats):void {
            aPoints = stats.getPoints(Team.A);
            bPoints = stats.getPoints(Team.B);
            aGatheredPoints = stats.getGatheredPoints(Team.A);
            bGatheredPoints = stats.getGatheredPoints(Team.B);
        }

        public function updateRound():void {
            var a:*, i:uint, j:uint;
            var o:DrawRobot;

            for (a in groundRobots) {
                o = groundRobots[a] as DrawRobot;
                o.updateRound();
                if (!o.isAlive()) {
                    if (o.parent) {
                        o.parent.removeChild(o);
                    }
                    delete groundRobots[a];
                }
            }

            for (a in encampments) {
                o = encampments[a] as DrawRobot;
                o.updateRound();
                if (!o.isAlive()) {
                    if (o.parent) {
                        o.parent.removeChild(o);
                    }
                    delete encampments[a];
                }
            }
        }

        private function processEndOfRound():void {
            roundNum++;
        }

        ///////////////////////////////////////////////////////
        ////////////// PRIVATE HELPER FUNCTIONS ///////////////
        ///////////////////////////////////////////////////////

        private function getRobot(id:uint):DrawRobot {
            if (groundRobots[id]) return groundRobots[id] as DrawRobot;
            if (encampments[id]) return encampments[id] as DrawRobot;
            return null;
        }

        private function removeRobot(id:uint):void {
            if (groundRobots[id]) delete groundRobots[id];
            if (encampments[id]) delete encampments[id];
        }

        private function translateCoordinates(loc:MapLocation):MapLocation {
            return new MapLocation(loc.getX() - origin.getX(), loc.getY() - origin.getY());
        }

        ///////////////////////////////////////////////////////
        /////////////////// SIGNAL HANDLERS ///////////////////
        ///////////////////////////////////////////////////////

        override public function visitBroadcastSignal(s:BroadcastSignal):* {
            getRobot(s.getRobotID()).broadcast();
        }

        override public function visitCaptureSignal(s:CaptureSignal):* {
            var o:DrawRobot = neutralEncampments[s.getLocation()];
            if (o) {
                if (o.parent) {
                    o.parent.removeChild(o);
                }
                delete neutralEncampments[s.getLocation()];
            }
        }

        override public function visitDeathSignal(s:DeathSignal):* {
            var robot:DrawRobot = getRobot(s.getRobotID());
            robot.destroyUnit();
        }

        override public function visitEnergonChangeSignal(s:EnergonChangeSignal):* {
            // TODO
        }

        override public function visitFluxChangeSignal(s:FluxChangeSignal):* {
            // TODO
        }

        override public function visitIndicatorStringSignal(s:IndicatorStringSignal):* {
            getRobot(s.getRobotID()).setIndicatorString(s.getIndicatorString(), s.getIndex());
        }

        override public function visitMineSignal(s:MineSignal):* {
            var loc:MapLocation = translateCoordinates(s.getLocation());
            if (s.isBirth()) {
                mines[loc.getX()][loc.getY()] = s.getTeam();
            } else {
                mines[loc.getX()][loc.getY()] = null;
            }
        }

        override public function visitMovementSignal(s:MovementSignal):* {
            getRobot(s.getRobotID()).moveToLocation(s.getTargetLoc());
        }

        override public function visitNodeBirthSignal(s:NodeBirthSignal):* {
            var encampment:DrawRobot = new DrawRobot(0, RobotType.ENCAMPMENT, Team.NEUTRAL);
            encampment.setLocation(s.getLocation());
            neutralEncampments[s.getLocation()] = encampment;
        }

        override public function visitSpawnSignal(s:SpawnSignal):* {
            var robot:DrawRobot = new DrawRobot(s.getRobotID(), s.getRobotType(), s.getTeam());
            robot.setLocation(s.getLocation());
            groundRobots[s.getRobotID()] = robot;
        }

    }

}

remove attack animation

remove attack animation


package krewfw.builtin_actor.display {

    import starling.display.Image;

    import krewfw.core.KrewActor;

    //------------------------------------------------------------
    public class KrewMovieClip extends KrewActor {

        protected var _movieImage:Image;

        private var _movieInfoList:Array = null;
        private var _frameCount:int       = 0;
        private var _framePlayTime:Number = 0;

        //------------------------------------------------------------
        public function KrewMovieClip() {}

        /**
         * Set up frame animation info.
         * Please call after init().
         *
         * @param infoList List of [imageName, durationSec]. Example:
         * [
         *     ['frame_1', 0.1],
         *     ['frame_2', 0.1],
         *     ['frame_3', 0.1],
         *     ...
         * ]
         */
        public function setupMovieClip(infoList:Array, width:Number, height:Number,
                                       x:Number=0, y:Number=0):void
        {
            _movieInfoList = infoList;

            var imageName:String = _movieInfoList[0][0];
            _movieImage = getImage(imageName);
            addImage(_movieImage, width, height, x, y);
            _frameCount = 0;
        }

        public function setRandomFrame():void {
            if (!_movieInfoList) { return; }
            if (_movieInfoList.length == 0) { return; }

            _frameCount = krew.randInt(_movieInfoList.length);
        }

        public override function update(passedTime:Number):void {
            super.update(passedTime);
            _updateMovieFrame(passedTime);
        }

        private function _updateMovieFrame(passedTime:Number):void {
            if (!_movieInfoList) { return; }
            if (_movieInfoList.length == 0) { return; }

            var nextDuration:Number = _movieInfoList[_frameCount][1];
            _framePlayTime += passedTime;

            if (_framePlayTime > nextDuration) {
                _framePlayTime -= nextDuration;
                ++_frameCount;
                if (_frameCount >= _movieInfoList.length) { _frameCount = 0; }

                var imageName:String = _movieInfoList[_frameCount][0];
                changeImage(_movieImage, imageName);
            }
        }

    }
}

package krewfw.builtin_actor.display {

    import starling.display.Image;

    import krewfw.core.KrewActor;

    //------------------------------------------------------------
    public class KrewMovieClip extends KrewActor {

        protected var _movieImage:Image;

        private var _movieInfoList:Array = null;
        private var _frameCount:int       = 0;
        private var _framePlayTime:Number = 0;

        //------------------------------------------------------------
        public function KrewMovieClip() {}

        /**
         * Set up frame animation info.
         * Please call after init().
         *
         * @param infoList List of [imageName, durationSec]. Example:
         * <pre>
         * [
         *     ['frame_1', 0.1],
         *     ['frame_2', 0.1],
         *     ['frame_3', 0.1],
         *     ...
         * ]
         * </pre>
         */
        public function setUpMovieClip(infoList:Array, width:Number, height:Number,
                                       x:Number=0, y:Number=0):void
        {
            _movieInfoList = infoList;

            var imageName:String = _movieInfoList[0][0];
            _movieImage = getImage(imageName);
            addImage(_movieImage, width, height, x, y);
            _frameCount = 0;
        }

        public function changeClip(newInfoList:Array):void {
            _movieInfoList = newInfoList;

            _frameCount    = 0;
            _framePlayTime = 0;

            var imageName:String = _movieInfoList[_frameCount][0];
            changeImage(_movieImage, imageName);
        }

        public function setRandomFrame():void {
            if (!_movieInfoList) { return; }
            if (_movieInfoList.length == 0) { return; }

            _frameCount = krew.randInt(_movieInfoList.length);
        }

        public override function update(passedTime:Number):void {
            super.update(passedTime);
            _updateMovieFrame(passedTime);
        }

        private function _updateMovieFrame(passedTime:Number):void {
            if (!_movieInfoList) { return; }
            if (_movieInfoList.length <= 1) { return; }

            var nextDuration:Number = _movieInfoList[_frameCount][1];
            _framePlayTime += passedTime;

            if (_framePlayTime > nextDuration) {
                _framePlayTime -= nextDuration;
                ++_frameCount;
                if (_frameCount >= _movieInfoList.length) { _frameCount = 0; }

                var imageName:String = _movieInfoList[_frameCount][0];
                changeImage(_movieImage, imageName);
            }
        }

    }
}

Update KrewMovieClip

Update KrewMovieClip


package dolly {
import dolly.core.dolly_internal;
import dolly.data.ClassLevelCopyable;
import dolly.data.ClassLevelCopyableCloneable;
import dolly.data.PropertyLevelCopyable;
import dolly.data.PropertyLevelCopyableCloneable;

import org.as3commons.reflect.Field;
import org.as3commons.reflect.Type;
import org.flexunit.asserts.assertEquals;
import org.flexunit.asserts.assertNotNull;
import org.flexunit.asserts.assertNull;

use namespace dolly_internal;

public class CopierTest {

	private var classLevelCopyable:ClassLevelCopyable;
	private var classLevelCopyableType:Type;

	private var classLevelCopyableCloneable:ClassLevelCopyableCloneable;
	private var classLevelCopyableCloneableType:Type;

	private var propertyLevelCopyable:PropertyLevelCopyable;
	private var propertyLevelCopyableType:Type;

	private var propertyLevelCopyableCloneable:PropertyLevelCopyableCloneable;
	private var propertyLevelCopyableCloneableType:Type;

	[Before]
	public function before():void {
		classLevelCopyable = new ClassLevelCopyable();
		classLevelCopyable.property1 = "value 1";
		classLevelCopyable.property2 = "value 2";
		classLevelCopyable.property3 = "value 3";
		classLevelCopyable.writableField = "value 4";
		classLevelCopyableType = Type.forInstance(classLevelCopyable);

		classLevelCopyableCloneable = new ClassLevelCopyableCloneable();
		classLevelCopyableCloneable.property1 = "value 1";
		classLevelCopyableCloneable.property2 = "value 2";
		classLevelCopyableCloneable.property3 = "value 3";
		classLevelCopyableCloneable.writableField = "value 4";
		classLevelCopyableCloneableType = Type.forInstance(classLevelCopyableCloneable);

		propertyLevelCopyable = new PropertyLevelCopyable();
		propertyLevelCopyable.property1 = "value 1";
		propertyLevelCopyable.property2 = "value 2";
		propertyLevelCopyable.property3 = "value 3";
		propertyLevelCopyable.writableField = "value 4";
		propertyLevelCopyableType = Type.forInstance(propertyLevelCopyable);

		propertyLevelCopyableCloneable = new PropertyLevelCopyableCloneable();
		propertyLevelCopyableCloneable.property1 = "value 1";
		propertyLevelCopyableCloneable.property2 = "value 2";
		propertyLevelCopyableCloneable.property3 = "value 3";
		propertyLevelCopyableCloneable.writableField = "value 4";
		propertyLevelCopyableCloneableType = Type.forInstance(propertyLevelCopyableCloneable);
	}

	[After]
	public function after():void {
		classLevelCopyable = null;
		classLevelCopyableType = null;

		classLevelCopyableCloneable = null;
		classLevelCopyableCloneableType = null;

		propertyLevelCopyable = null;
		propertyLevelCopyableType = null;

		propertyLevelCopyableCloneable = null;
		propertyLevelCopyableCloneableType = null;
	}

	[Test]
	public function testGetCopyableFieldsForTypeClassLevelCopyable():void {
		const copyableFields:Vector.<Field> = Copier.getCopyableFieldsForType(classLevelCopyableType);
		assertNotNull(copyableFields);
		assertEquals(4, copyableFields.length);
		assertNotNull(copyableFields[0]);
		assertNotNull(copyableFields[1]);
		assertNotNull(copyableFields[2]);
		assertNotNull(copyableFields[3]);
	}

	[Test]
	public function testGetCopyableFieldsForTypeClassLevelCopyableCloneable():void {
		const copyableFields:Vector.<Field> = Copier.getCopyableFieldsForType(classLevelCopyableCloneableType);
		assertNotNull(copyableFields);
		assertEquals(4, copyableFields.length);
		assertNotNull(copyableFields[0]);
		assertNotNull(copyableFields[1]);
		assertNotNull(copyableFields[2]);
		assertNotNull(copyableFields[3]);
	}

	[Test]
	public function testGetCopyableFieldsForTypePropertyLevelCopyable():void {
		const copyableFields:Vector.<Field> = Copier.getCopyableFieldsForType(propertyLevelCopyableType);
		assertNotNull(copyableFields);
		assertEquals(3, copyableFields.length);
		assertNotNull(copyableFields[0]);
		assertNotNull(copyableFields[1]);
		assertNotNull(copyableFields[2]);
	}

	[Test]
	public function testGetCopyableFieldsForTypePropertyLevelCopyableCloneable():void {
		const copyableFields:Vector.<Field> = Copier.getCopyableFieldsForType(propertyLevelCopyableCloneableType);
		assertNotNull(copyableFields);
		assertEquals(3, copyableFields.length);
		assertNotNull(copyableFields[0]);
		assertNotNull(copyableFields[1]);
		assertNotNull(copyableFields[2]);
	}

	[Test]
	public function testCopyWithClassLevelMetadata():void {
		const copy1:ClassLevelCopyable = Copier.copy(classLevelCopyable);
		assertNotNull(copy1);
		assertNotNull(copy1.property1);
		assertEquals(copy1.property1, classLevelCopyable.property1);
		assertNotNull(copy1.property2);
		assertEquals(copy1.property2, classLevelCopyable.property2);
		assertNotNull(copy1.property3);
		assertEquals(copy1.property3, classLevelCopyable.property3);
		assertNotNull(copy1.writableField);
		assertEquals(copy1.writableField, classLevelCopyable.writableField);

		const copy2:ClassLevelCopyableCloneable = Copier.copy(classLevelCopyableCloneable);
		assertNotNull(copy2);
		assertNotNull(copy2.property1);
		assertEquals(copy2.property1, classLevelCopyable.property1);
		assertNotNull(copy2.property2);
		assertEquals(copy2.property2, classLevelCopyable.property2);
		assertNotNull(copy2.property3);
		assertEquals(copy2.property3, classLevelCopyable.property3);
		assertNotNull(copy2.writableField);
		assertEquals(copy2.writableField, classLevelCopyable.writableField);
	}

	[Test]
	public function testCopyWithPropertyLevelMetadata():void {
		const copy1:PropertyLevelCopyable = Copier.copy(propertyLevelCopyable);
		assertNotNull(copy1);
		assertNull(copy1.property1);
		assertNotNull(copy1.property2);
		assertEquals(copy1.property2, classLevelCopyable.property2);
		assertNotNull(copy1.property3);
		assertEquals(copy1.property3, classLevelCopyable.property3);
		assertNotNull(copy1.writableField);
		assertEquals(copy1.writableField, classLevelCopyable.writableField);

		const copy2:PropertyLevelCopyableCloneable = Copier.copy(propertyLevelCopyableCloneable);
		assertNotNull(copy2);
		assertNull(copy2.property1);
		assertNotNull(copy2.property2);
		assertEquals(copy2.property2, classLevelCopyable.property2);
		assertNotNull(copy2.property3);
		assertEquals(copy2.property3, classLevelCopyable.property3);
		assertNotNull(copy2.writableField);
		assertEquals(copy2.writableField, classLevelCopyable.writableField);
	}
}
}

package dolly {
import dolly.core.dolly_internal;
import dolly.data.ClassLevelCopyable;
import dolly.data.ClassLevelCopyableCloneable;
import dolly.data.PropertyLevelCopyable;
import dolly.data.PropertyLevelCopyableCloneable;

import org.as3commons.reflect.Field;
import org.as3commons.reflect.Type;
import org.flexunit.asserts.assertEquals;
import org.flexunit.asserts.assertNotNull;
import org.flexunit.asserts.assertNull;

use namespace dolly_internal;

public class CopierTest {

	private var classLevelCopyable:ClassLevelCopyable;
	private var classLevelCopyableType:Type;

	private var classLevelCopyableCloneable:ClassLevelCopyableCloneable;
	private var classLevelCopyableCloneableType:Type;

	private var propertyLevelCopyable:PropertyLevelCopyable;
	private var propertyLevelCopyableType:Type;

	private var propertyLevelCopyableCloneable:PropertyLevelCopyableCloneable;
	private var propertyLevelCopyableCloneableType:Type;

	[Before]
	public function before():void {
		classLevelCopyable = new ClassLevelCopyable();
		classLevelCopyable.property1 = "value 1";
		classLevelCopyable.property2 = "value 2";
		classLevelCopyable.property3 = "value 3";
		classLevelCopyable.writableField = "value 4";
		classLevelCopyableType = Type.forInstance(classLevelCopyable);

		classLevelCopyableCloneable = new ClassLevelCopyableCloneable();
		classLevelCopyableCloneable.property1 = "value 1";
		classLevelCopyableCloneable.property2 = "value 2";
		classLevelCopyableCloneable.property3 = "value 3";
		classLevelCopyableCloneable.writableField = "value 4";
		classLevelCopyableCloneableType = Type.forInstance(classLevelCopyableCloneable);

		propertyLevelCopyable = new PropertyLevelCopyable();
		propertyLevelCopyable.property1 = "value 1";
		propertyLevelCopyable.property2 = "value 2";
		propertyLevelCopyable.property3 = "value 3";
		propertyLevelCopyable.writableField = "value 4";
		propertyLevelCopyableType = Type.forInstance(propertyLevelCopyable);

		propertyLevelCopyableCloneable = new PropertyLevelCopyableCloneable();
		propertyLevelCopyableCloneable.property1 = "value 1";
		propertyLevelCopyableCloneable.property2 = "value 2";
		propertyLevelCopyableCloneable.property3 = "value 3";
		propertyLevelCopyableCloneable.writableField = "value 4";
		propertyLevelCopyableCloneableType = Type.forInstance(propertyLevelCopyableCloneable);
	}

	[After]
	public function after():void {
		classLevelCopyable = null;
		classLevelCopyableType = null;

		classLevelCopyableCloneable = null;
		classLevelCopyableCloneableType = null;

		propertyLevelCopyable = null;
		propertyLevelCopyableType = null;

		propertyLevelCopyableCloneable = null;
		propertyLevelCopyableCloneableType = null;
	}

	[Test]
	public function testGetCopyableFieldsForTypeClassLevelCopyable():void {
		const copyableFields:Vector.<Field> = Copier.getCopyableFieldsForType(classLevelCopyableType);
		assertNotNull(copyableFields);
		assertEquals(4, copyableFields.length);
		assertNotNull(copyableFields[0]);
		assertNotNull(copyableFields[1]);
		assertNotNull(copyableFields[2]);
		assertNotNull(copyableFields[3]);
	}

	[Test]
	public function testGetCopyableFieldsForTypeClassLevelCopyableCloneable():void {
		const copyableFields:Vector.<Field> = Copier.getCopyableFieldsForType(classLevelCopyableCloneableType);
		assertNotNull(copyableFields);
		assertEquals(4, copyableFields.length);
		assertNotNull(copyableFields[0]);
		assertNotNull(copyableFields[1]);
		assertNotNull(copyableFields[2]);
		assertNotNull(copyableFields[3]);
	}

	[Test]
	public function testGetCopyableFieldsForTypePropertyLevelCopyable():void {
		const copyableFields:Vector.<Field> = Copier.getCopyableFieldsForType(propertyLevelCopyableType);
		assertNotNull(copyableFields);
		assertEquals(3, copyableFields.length);
		assertNotNull(copyableFields[0]);
		assertNotNull(copyableFields[1]);
		assertNotNull(copyableFields[2]);
	}

	[Test]
	public function testGetCopyableFieldsForTypePropertyLevelCopyableCloneable():void {
		const copyableFields:Vector.<Field> = Copier.getCopyableFieldsForType(propertyLevelCopyableCloneableType);
		assertNotNull(copyableFields);
		assertEquals(3, copyableFields.length);
		assertNotNull(copyableFields[0]);
		assertNotNull(copyableFields[1]);
		assertNotNull(copyableFields[2]);
	}

	[Test]
	public function testCopyClassLevelCopyable():void {
		const copy:ClassLevelCopyable = Copier.copy(classLevelCopyable);
		assertNotNull(copy);
		assertNotNull(copy.property1);
		assertEquals(copy.property1, classLevelCopyable.property1);
		assertNotNull(copy.property2);
		assertEquals(copy.property2, classLevelCopyable.property2);
		assertNotNull(copy.property3);
		assertEquals(copy.property3, classLevelCopyable.property3);
		assertNotNull(copy.writableField);
		assertEquals(copy.writableField, classLevelCopyable.writableField);
	}

	[Test]
	public function testCopyClassLevelCopyableCloneable():void {
		const copy:ClassLevelCopyableCloneable = Copier.copy(classLevelCopyableCloneable);
		assertNotNull(copy);
		assertNotNull(copy.property1);
		assertEquals(copy.property1, classLevelCopyable.property1);
		assertNotNull(copy.property2);
		assertEquals(copy.property2, classLevelCopyable.property2);
		assertNotNull(copy.property3);
		assertEquals(copy.property3, classLevelCopyable.property3);
		assertNotNull(copy.writableField);
		assertEquals(copy.writableField, classLevelCopyable.writableField);
	}

	[Test]
	public function testCopyWithPropertyLevelMetadata():void {
		const copy1:PropertyLevelCopyable = Copier.copy(propertyLevelCopyable);
		assertNotNull(copy1);
		assertNull(copy1.property1);
		assertNotNull(copy1.property2);
		assertEquals(copy1.property2, classLevelCopyable.property2);
		assertNotNull(copy1.property3);
		assertEquals(copy1.property3, classLevelCopyable.property3);
		assertNotNull(copy1.writableField);
		assertEquals(copy1.writableField, classLevelCopyable.writableField);

		const copy2:PropertyLevelCopyableCloneable = Copier.copy(propertyLevelCopyableCloneable);
		assertNotNull(copy2);
		assertNull(copy2.property1);
		assertNotNull(copy2.property2);
		assertEquals(copy2.property2, classLevelCopyable.property2);
		assertNotNull(copy2.property3);
		assertEquals(copy2.property3, classLevelCopyable.property3);
		assertNotNull(copy2.writableField);
		assertEquals(copy2.writableField, classLevelCopyable.writableField);
	}
}
}

Split method "testCopyWithClassLevelMetadata" to a few.


package model
{
	import dragonBones.objects.AnimationData;
	import dragonBones.objects.ArmatureData;
	import dragonBones.objects.MovementBoneData;
	import dragonBones.objects.MovementData;
	import dragonBones.objects.XMLDataParser;
	import dragonBones.utils.ConstValues;
	import dragonBones.utils.dragonBones_internal;

	import flash.events.Event;

	import message.MessageDispatcher;

	import mx.collections.XMLListCollection;

	use namespace dragonBones_internal;

	/**
	 * Manage selected animation data
	 */
	public class AnimationDataProxy
	{
		public var movementsMC:XMLListCollection;

		private var _xml:XML;
		private var _movementsXMLList:XMLList;

		private var _movementXML:XML;
		private var _movementBonesXMLList:XMLList;

		private var _movementBoneXML:XML;

		public function get animationName():String
		{
			return _xml?_xml.attribute(ConstValues.A_NAME):"";
		}

		public function get movementName():String
		{
			return _movementXML?_movementXML.attribute(ConstValues.A_NAME):"";
		}

		public function get boneName():String
		{
			return _movementBoneXML?_movementBoneXML.attribute(ConstValues.A_NAME):"";
		}

		public function get durationTo():Number
		{
			if(!_movementXML)
			{
				return -1;
			}
			return int(_movementXML.attribute(ConstValues.A_DURATION_TO)) / ImportDataProxy.getInstance().skeletonData.frameRate;
		}
		public function set durationTo(value:Number):void
		{
			if(_movementXML)
			{
				_movementXML.@[ConstValues.A_DURATION_TO] = Math.round(value * ImportDataProxy.getInstance().skeletonData.frameRate);
				updateMovement();
			}
		}

		public function get durationTween():Number
		{
			if(_movementXML?int(_movementXML.attribute(ConstValues.A_DURATION)) == 1:true)
			{
				return -1;
			}
			return int(_movementXML.attribute(ConstValues.A_DURATION_TWEEN)) / ImportDataProxy.getInstance().skeletonData.frameRate;
		}
		public function set durationTween(value:Number):void
		{
			if(_movementXML)
			{
				_movementXML.@[ConstValues.A_DURATION_TWEEN] = Math.round(value * ImportDataProxy.getInstance().skeletonData.frameRate);
				updateMovement();
			}
		}

		public function get loop():Boolean
		{
			return _movementXML?Boolean(int(_movementXML.attribute(ConstValues.A_LOOP)) == 1):false;
		}
		public function set loop(value:Boolean):void
		{
			if(_movementXML)
			{
				_movementXML.@[ConstValues.A_LOOP] = value?1:0;
				updateMovement();
			}
		}

		public function get tweenEasing():Number
		{
			return _movementXML?Number(_movementXML.attribute(ConstValues.A_TWEEN_EASING)):-1.1;
		}
		public function set tweenEasing(value:Number):void
		{
			if(_movementXML)
			{
				if(value<-1)
				{
					_movementXML.@[ConstValues.A_TWEEN_EASING] = NaN;
				}
				else
				{
					_movementXML.@[ConstValues.A_TWEEN_EASING] = value;
				}
				updateMovement();
			}
		}

		public function get boneScale():Number
		{
			if(!_movementBoneXML || int(_movementXML.attribute(ConstValues.A_DURATION)) < 2)
			{
				return NaN;
			}
			return Number(_movementBoneXML.attribute(ConstValues.A_MOVEMENT_SCALE)) * 100;
		}
		public function set boneScale(value:Number):void
		{
			if(_movementBoneXML)
			{
				_movementBoneXML.@[ConstValues.A_MOVEMENT_SCALE] = value * 0.01;
				updateMovementBone();
			}
		}

		public function get boneDelay():Number
		{
			if(!_movementBoneXML || int(_movementXML.attribute(ConstValues.A_DURATION)) < 2)
			{
				return NaN;
			}
			return Number(_movementBoneXML.attribute(ConstValues.A_MOVEMENT_DELAY))* 100;
		}
		public function set boneDelay(value:Number):void
		{
			if(_movementBoneXML)
			{
				_movementBoneXML.@[ConstValues.A_MOVEMENT_DELAY] = value * 0.01;
				updateMovementBone();
			}
		}

		public function AnimationDataProxy()
		{
			movementsMC = new XMLListCollection();
		}

		internal function setData(xml:XML):void
		{
			_xml = xml;
			if(_xml)
			{
				_movementsXMLList = _xml.elements(ConstValues.MOVEMENT);
			}
			else
			{
				_movementsXMLList = null;
			}

			movementsMC.source = _movementsXMLList;

			MessageDispatcher.dispatchEvent(MessageDispatcher.CHANGE_ANIMATION_DATA, animationName);

			changeMovement();
		}

		public function changeMovement(movementName:String = null, isChangedByArmature:Boolean = false):void
		{
			_movementXML = ImportDataProxy.getElementByName(_movementsXMLList, movementName, true);
			if(_movementXML)
			{
				_movementBonesXMLList = _movementXML.elements(ConstValues.BONE);
			}
			else
			{
				_movementBonesXMLList = null;
			}

			MessageDispatcher.dispatchEvent(MessageDispatcher.CHANGE_MOVEMENT_DATA, this.movementName, isChangedByArmature);

			changeMovementBone(ImportDataProxy.getInstance().armatureDataProxy.boneName);
		}

		public function changeMovementBone(boneName:String = null):void
		{
			var movementBoneXML:XML = ImportDataProxy.getElementByName(_movementBonesXMLList, boneName, true);
			if(movementBoneXML == _movementBoneXML)
			{
				return;
			}
			_movementBoneXML = movementBoneXML;
			MessageDispatcher.dispatchEvent(MessageDispatcher.CHANGE_MOVEMENT_BONE_DATA , this.boneName);
		}

		internal function updateBoneParent(boneName:String):void
		{
			if(_xml)
			{
				var animationData:AnimationData = ImportDataProxy.getInstance().skeletonData.getAnimationData(animationName);
				var armatureData:ArmatureData = ImportDataProxy.getInstance().skeletonData.getArmatureData(animationName);
				XMLDataParser.parseAnimationData(_xml, animationData, armatureData);
			}
		}

		private function updateMovement():void
		{
			var animationData:AnimationData = ImportDataProxy.getInstance().skeletonData.getAnimationData(animationName);
			var movementData:MovementData = animationData.getMovementData(movementName);

			movementData.durationTo = durationTo;
			movementData.durationTween = durationTween;
			movementData.loop = loop;
			movementData.tweenEasing = tweenEasing;

			if(!ImportDataProxy.getInstance().isExportedSource)
			{
				JSFLProxy.getInstance().changeMovement(animationName, movementName, _movementXML);
			}

			MessageDispatcher.dispatchEvent(MessageDispatcher.UPDATE_MOVEMENT_DATA, movementName);
		}

		private function updateMovementBone():void
		{
			var animationData:AnimationData = ImportDataProxy.getInstance().skeletonData.getAnimationData(animationName);
			var movementData:MovementData = animationData.getMovementData(movementName);
			var movementBoneData:MovementBoneData = movementData.getMovementBoneData(boneName);

			movementBoneData.scale = boneScale * 0.01;
			movementBoneData.delay = boneDelay * 0.01;
			if(movementBoneData.delay > 0)
			{
				movementBoneData.delay -= 1;
			}

			if(!ImportDataProxy.getInstance().isExportedSource)
			{
				var movementXMLCopy:XML = _movementXML.copy();
				delete movementXMLCopy.elements(ConstValues.BONE).*;
				delete movementXMLCopy[ConstValues.FRAME];
				JSFLProxy.getInstance().changeMovement(animationName, movementName, movementXMLCopy);
			}

			MessageDispatcher.dispatchEvent(MessageDispatcher.UPDATE_MOVEMENT_BONE_DATA, movementName);
		}
	}
}

package model
{
	import dragonBones.objects.AnimationData;
	import dragonBones.objects.ArmatureData;
	import dragonBones.objects.MovementBoneData;
	import dragonBones.objects.MovementData;
	import dragonBones.objects.XMLDataParser;
	import dragonBones.utils.ConstValues;
	import dragonBones.utils.dragonBones_internal;

	import flash.events.Event;

	import message.MessageDispatcher;

	import mx.collections.XMLListCollection;

	use namespace dragonBones_internal;

	/**
	 * Manage selected animation data
	 */
	public class AnimationDataProxy
	{
		public var movementsMC:XMLListCollection;

		private var _xml:XML;
		private var _movementsXMLList:XMLList;

		private var _movementXML:XML;
		private var _movementBonesXMLList:XMLList;

		private var _movementBoneXML:XML;

		public function get animationName():String
		{
			return _xml?_xml.attribute(ConstValues.A_NAME):"";
		}

		public function get movementName():String
		{
			return _movementXML?_movementXML.attribute(ConstValues.A_NAME):"";
		}

		public function get boneName():String
		{
			return _movementBoneXML?_movementBoneXML.attribute(ConstValues.A_NAME):"";
		}

		public function get durationTo():Number
		{
			if(!_movementXML)
			{
				return -1;
			}
			return int(_movementXML.attribute(ConstValues.A_DURATION_TO)) / ImportDataProxy.getInstance().skeletonData.frameRate;
		}
		public function set durationTo(value:Number):void
		{
			if(_movementXML)
			{
				_movementXML.@[ConstValues.A_DURATION_TO] = Math.round(value * ImportDataProxy.getInstance().skeletonData.frameRate);
				updateMovement();
			}
		}

		public function get durationTween():Number
		{
			if(_movementXML?int(_movementXML.attribute(ConstValues.A_DURATION)) == 1:true)
			{
				return -1;
			}
			return int(_movementXML.attribute(ConstValues.A_DURATION_TWEEN)) / ImportDataProxy.getInstance().skeletonData.frameRate;
		}
		public function set durationTween(value:Number):void
		{
			if(_movementXML)
			{
				_movementXML.@[ConstValues.A_DURATION_TWEEN] = Math.round(value * ImportDataProxy.getInstance().skeletonData.frameRate);
				updateMovement();
			}
		}

		public function get loop():Boolean
		{
			return _movementXML?Boolean(int(_movementXML.attribute(ConstValues.A_LOOP)) == 1):false;
		}
		public function set loop(value:Boolean):void
		{
			if(_movementXML)
			{
				_movementXML.@[ConstValues.A_LOOP] = value?1:0;
				updateMovement();
			}
		}

		public function get tweenEasing():Number
		{
			return _movementXML?Number(_movementXML.attribute(ConstValues.A_TWEEN_EASING)):-1.1;
		}
		public function set tweenEasing(value:Number):void
		{
			if(_movementXML)
			{
				if(value<-1)
				{
					_movementXML.@[ConstValues.A_TWEEN_EASING] = NaN;
				}
				else
				{
					_movementXML.@[ConstValues.A_TWEEN_EASING] = value;
				}
				updateMovement();
			}
		}

		public function get boneScale():Number
		{
			if(!_movementBoneXML || int(_movementXML.attribute(ConstValues.A_DURATION)) < 2)
			{
				return NaN;
			}
			return Number(_movementBoneXML.attribute(ConstValues.A_MOVEMENT_SCALE)) * 100;
		}
		public function set boneScale(value:Number):void
		{
			if(_movementBoneXML)
			{
				_movementBoneXML.@[ConstValues.A_MOVEMENT_SCALE] = value * 0.01;
				updateMovementBone();
			}
		}

		public function get boneDelay():Number
		{
			if(!_movementBoneXML || int(_movementXML.attribute(ConstValues.A_DURATION)) < 2)
			{
				return NaN;
			}
			return Number(_movementBoneXML.attribute(ConstValues.A_MOVEMENT_DELAY))* 100;
		}
		public function set boneDelay(value:Number):void
		{
			if(_movementBoneXML)
			{
				_movementBoneXML.@[ConstValues.A_MOVEMENT_DELAY] = value * 0.01;
				updateMovementBone();
			}
		}

		public function AnimationDataProxy()
		{
			movementsMC = new XMLListCollection();
		}

		internal function setData(xml:XML):void
		{
			_xml = xml;
			if(_xml)
			{
				_movementsXMLList = _xml.elements(ConstValues.MOVEMENT);
			}
			else
			{
				_movementsXMLList = null;
			}

			movementsMC.source = _movementsXMLList;

			MessageDispatcher.dispatchEvent(MessageDispatcher.CHANGE_ANIMATION_DATA, animationName);

			changeMovement();
		}

		public function changeMovement(movementName:String = null, isChangedByArmature:Boolean = false):void
		{
			_movementXML = ImportDataProxy.getElementByName(_movementsXMLList, movementName, true);
			if(_movementXML)
			{
				_movementBonesXMLList = _movementXML.elements(ConstValues.BONE);
			}
			else
			{
				_movementBonesXMLList = null;
			}

			MessageDispatcher.dispatchEvent(MessageDispatcher.CHANGE_MOVEMENT_DATA, this.movementName, isChangedByArmature);

			changeMovementBone(ImportDataProxy.getInstance().armatureDataProxy.boneName);
		}

		public function changeMovementBone(boneName:String = null):void
		{
			var movementBoneXML:XML = ImportDataProxy.getElementByName(_movementBonesXMLList, boneName, true);
			if(movementBoneXML == _movementBoneXML)
			{
				return;
			}
			_movementBoneXML = movementBoneXML;
			MessageDispatcher.dispatchEvent(MessageDispatcher.CHANGE_MOVEMENT_BONE_DATA , this.boneName);
		}

		internal function updateBoneParent(boneName:String):void
		{
			if(_xml)
			{
				var animationData:AnimationData = ImportDataProxy.getInstance().skeletonData.getAnimationData(animationName);
				var armatureData:ArmatureData = ImportDataProxy.getInstance().skeletonData.getArmatureData(animationName);
				XMLDataParser.parseAnimationData(_xml, animationData, armatureData);
			}
		}

		private function updateMovement():void
		{
			var animationData:AnimationData = ImportDataProxy.getInstance().skeletonData.getAnimationData(animationName);
			var movementData:MovementData = animationData.getMovementData(movementName);

			movementData.durationTo = durationTo;
			movementData.durationTween = durationTween;
			movementData.loop = loop;
			movementData.tweenEasing = tweenEasing;

			if(!ImportDataProxy.getInstance().isExportedSource)
			{
				JSFLProxy.getInstance().changeMovement(animationName, movementName, _movementXML);
			}

			MessageDispatcher.dispatchEvent(MessageDispatcher.UPDATE_MOVEMENT_DATA, movementName);
		}

		private function updateMovementBone():void
		{
			var animationData:AnimationData = ImportDataProxy.getInstance().skeletonData.getAnimationData(animationName);
			var movementData:MovementData = animationData.getMovementData(movementName);
			var movementBoneData:MovementBoneData = movementData.getMovementBoneData(boneName);

			movementBoneData.setValues(
				boneScale * 0.01,
				boneDelay * 0.01
			)

			if(!ImportDataProxy.getInstance().isExportedSource)
			{
				var movementXMLCopy:XML = _movementXML.copy();
				delete movementXMLCopy.elements(ConstValues.BONE).*;
				delete movementXMLCopy[ConstValues.FRAME];
				JSFLProxy.getInstance().changeMovement(animationName, movementName, movementXMLCopy);
			}

			MessageDispatcher.dispatchEvent(MessageDispatcher.UPDATE_MOVEMENT_BONE_DATA, movementName);
		}
	}
}

fix bug

fix bug


package fairygui {
	import laya.display.Sprite;
	import laya.events.Event;

    public class Events {
        public static const STATE_CHANGED: String = "fui_state_changed";
        public static const XY_CHANGED: String = "fui_xy_changed";
        public static const SIZE_CHANGED: String = "fui_size_changed";
        public static const SIZE_DELAY_CHANGE: String = "fui_size_delay_change";
        public static const CLICK_ITEM:String = "fui_click_item";
        public static const SCROLL:String = "fui_scroll";
		public static const SCROLL_END:String = "fui_scroll_end";
        public static const DROP:String = "fui_drop";
        public static const FOCUS_CHANGED:String = "fui_focus_changed";
        public static const DRAG_START:String = "fui_drag_start";
        public static const DRAG_MOVE:String = "fui_drag_move";
        public static const DRAG_END:String = "fui_drag_end";
		public static const PULL_DOWN_RELEASE:String = "fui_pull_down_release";
		public static const PULL_UP_RELEASE:String = "fui_pull_up_release";
		public static const GEAR_STOP:String = "fui_gear_stop";

        public static var $event:Event = new Event();

        public static function createEvent(type:String, target:Sprite, source:Event=null):Event {
            Events.$event.setTo(type, target, source?source.target:target);
            if(source)
                Events.$event.touchId = source.touchId;
            Events.$event._stoped = false;
            return Events.$event;
        }

		public static function dispatch(type:String, target:Sprite, source:Event=null):void {
            target.event(type, Events.createEvent(type, target, source));
        }
    }
}

package fairygui {
	import laya.display.Sprite;
	import laya.events.Event;

    public class Events {
        public static const STATE_CHANGED: String = "fui_state_changed";
        public static const XY_CHANGED: String = "fui_xy_changed";
        public static const SIZE_CHANGED: String = "fui_size_changed";
        public static const SIZE_DELAY_CHANGE: String = "fui_size_delay_change";
        public static const CLICK_ITEM:String = "fui_click_item";
        public static const SCROLL:String = "fui_scroll";
		public static const SCROLL_END:String = "fui_scroll_end";
        public static const DROP:String = "fui_drop";
        public static const FOCUS_CHANGED:String = "fui_focus_changed";
        public static const DRAG_START:String = "fui_drag_start";
        public static const DRAG_MOVE:String = "fui_drag_move";
        public static const DRAG_END:String = "fui_drag_end";
		public static const PULL_DOWN_RELEASE:String = "fui_pull_down_release";
		public static const PULL_UP_RELEASE:String = "fui_pull_up_release";
		public static const GEAR_STOP:String = "fui_gear_stop";

        public static var $event:Event = new Event();

        public static function createEvent(type:String, target:Sprite, source:Event=null):Event {
            Events.$event.setTo(type, target, source?source.target:target);
            if(source)
	    {
	    	Events.$event.touchId = source.touchId;
		Events.$event.nativeEvent=source.nativeEvent;
	    }
            else
	    {
	    	Events.$event.nativeEvent=null;
	    }
            Events.$event._stoped = false;
            return Events.$event;
        }

		public static function dispatch(type:String, target:Sprite, source:Event=null):void {
            target.event(type, Events.createEvent(type, target, source));
        }
    }
}

Update Events.as

Update Events.as

修复list点击的item的ctrlKey等属性传递丢失。。


package aerys.minko.type.animation.timeline
{
	import aerys.minko.ns.minko_animation;
	import aerys.minko.scene.node.ISceneNode;

	public class ScalarTimeline extends AbstractTimeline
	{
		use namespace minko_animation;

		minko_animation var _timeTable	    : Vector.<uint>
		minko_animation var _values		    : Vector.<Number>;
        minko_animation var _interpolate    : Boolean;

		public function ScalarTimeline(propertyPath	: String,
									   timeTable 	: Vector.<uint>,
									   values		: Vector.<Number>,
                                       interpolate  : Boolean   = true)
		{
			super(propertyPath, timeTable[int(timeTable.length - 1)]);

			_timeTable = timeTable;
			_values	= values;
            _interpolate = interpolate;
		}

		override public function updateAt(t : int, target : Object):void
		{
			super.updateAt(t, target);

			var time		: int	= t < 0 ? duration + t : t;
			var timeId		: uint 	= getIndexForTime(time);

            if (_interpolate)
            {
    			var timeCount	: uint 	= _timeTable.length;

    			// change value.
    			var previousTime		: Number	= _timeTable[int(timeId - 1)];
    			var nextTime			: Number	= _timeTable[timeId % timeCount];
    			var interpolationRatio	: Number	= (time - previousTime) / (nextTime - previousTime);

    			if (t < 0.)
    				interpolationRatio = 1. - interpolationRatio;

    			currentTarget[propertyName] = (1 - interpolationRatio) * _values[timeId - 1] +
    				interpolationRatio * _values[timeId % timeCount];
            }
            else
            {
                currentTarget[propertyName] = _values[timeId];
            }
		}

		private function getIndexForTime(t : uint) : uint
		{
			// use a dichotomy to find the current frame in the time table.
			var timeCount 		: uint = _timeTable.length;
			var bottomTimeId	: uint = 0;
			var upperTimeId		: uint = timeCount;
			var timeId			: uint;

			while (upperTimeId - bottomTimeId > 1)
			{
				timeId = (bottomTimeId + upperTimeId) >> 1;

				if (_timeTable[timeId] > t)
					upperTimeId = timeId;
				else
					bottomTimeId = timeId;
			}

			return upperTimeId;
		}

		override public function clone() : ITimeline
		{
			return new ScalarTimeline(
				propertyPath,
				_timeTable.slice(),
				_values.slice(),
                _interpolate
			);
		}
	}
}

package aerys.minko.type.animation.timeline
{
	import aerys.minko.ns.minko_animation;
	import aerys.minko.scene.node.ISceneNode;

	public class ScalarTimeline extends AbstractTimeline
	{
		use namespace minko_animation;

		minko_animation var _timeTable	    : Vector.<uint>
		minko_animation var _values		    : Vector.<Number>;
        minko_animation var _interpolate    : Boolean;

		public function ScalarTimeline(propertyPath	: String,
									   timeTable 	: Vector.<uint>,
									   values		: Vector.<Number>,
                                       interpolate  : Boolean   = true)
		{
			super(propertyPath, timeTable[int(timeTable.length - 1)]);

			_timeTable = timeTable;
			_values	= values;
            _interpolate = interpolate;
		}

		override public function updateAt(t : int, target : Object):void
		{
			super.updateAt(t, target);

			var time		: int	= t < 0 ? duration + t : t;
			var timeId		: uint 	= getIndexForTime(time);

            if (_interpolate)
            {
    			var timeCount	: uint 	= _timeTable.length;

    			// change value.
    			var previousTime		: Number	= _timeTable[uint(timeId - 1)];
    			var nextTime			: Number	= _timeTable[uint(timeId % timeCount)];
    			var interpolationRatio	: Number	= (time - previousTime) / (nextTime - previousTime);

    			if (t < 0.)
    				interpolationRatio = 1. - interpolationRatio;

    			currentTarget[propertyName] = (1 - interpolationRatio) * _values[timeId - 1] +
    				interpolationRatio * _values[timeId % timeCount];
            }
            else
            {
                currentTarget[propertyName] = _values[timeId];
            }
		}

		private function getIndexForTime(t : uint) : uint
		{
			// use a dichotomy to find the current frame in the time table.
			var timeCount 		: uint = _timeTable.length;
			var bottomTimeId	: uint = 0;
			var upperTimeId		: uint = timeCount;
			var timeId			: uint;

			while (upperTimeId - bottomTimeId > 1)
			{
				timeId = (bottomTimeId + upperTimeId) >> 1;

				if (_timeTable[timeId] > t)
					upperTimeId = timeId;
				else
					bottomTimeId = timeId;
			}

			return upperTimeId;
		}

		override public function clone() : ITimeline
		{
			return new ScalarTimeline(
				propertyPath,
				_timeTable.slice(),
				_values.slice(),
                _interpolate
			);
		}
	}
}

fix missing cast to uint when accessing array cells


package aerys.minko.scene.controller.camera
{
	import flash.display.BitmapData;
	import flash.events.IEventDispatcher;
	import flash.events.MouseEvent;
	import flash.geom.Point;

	import aerys.minko.render.Viewport;
	import aerys.minko.scene.controller.EnterFrameController;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	public class ArcBallController extends EnterFrameController
	{
		private static const TMP_MATRIX : Matrix4x4 = new Matrix4x4();
		private static const EPSILON	: Number	= 0.001;

		protected var _mousePosition	: Point		= new Point();

		private var _enabled			: Boolean	= true;

		private var _previousTime		: uint		= 0;

		private var _distance			: Number	= 1.;
		private var _yaw				: Number	= 0;
		private var _pitch				: Number	= 0;
		private var _newDistance		: Number	= 1.;
		private var _newYaw				: Number	= 0;
		private var _newPitch			: Number	= 0;
		private var _update				: Boolean	= true;

		private var _position			: Vector4	= new Vector4(0, 0, 0, 1);
		private var _lookAt				: Vector4	= new Vector4(0, 0, 0, 1);
		private var _up					: Vector4	= new Vector4(0, 1, 0, 1);

		private var _minDistance		: Number	= 1.;
		private var _maxDistance		: Number	= 1000;

		private var _distanceStep		: Number	= 1;
		private var _yawStep			: Number	= 0.01;
		private var _pitchStep			: Number	= 0.01;

		private var _inertia			: Number	= 1.0;
		private var _interpolationSpeed	: Number	= 1;
		private var _speed				: Number	= 1;

		public function get speed() : Number
		{
			return _speed;
		}

		public function set speed(value : Number) : void
		{
			_speed = value;
			_update = true;
		}

		public function get inertia() : Number
		{
			return _inertia;
		}

		public function set inertia(value : Number) : void
		{
			_inertia = value;
		}

		public function get interpolationSpeed() : Number
		{
			return _interpolationSpeed;
		}

		public function set interpolationSpeed(value : Number) : void
		{
			_interpolationSpeed = value;
		}

		public function get enabled() : Boolean
		{
			return _enabled;
		}
		public function set enabled(value : Boolean) : void
		{
			_enabled = value;
		}

		protected function set update(value : Boolean) : void
		{
			_update = value;
		}

		/**
		 * Distance between look at point and target
		 */
		public function get distance() : Number
		{
			return _newDistance;
		}
		public function set distance(value : Number) : void
		{
			_newDistance = value;
			_update = true;
		}

		/**
		 * Horizontal rotation angle (in radians)
		 */
		public function get yaw() : Number
		{
			return _newYaw;
		}
		public function set yaw(value : Number) : void
		{
			_newYaw = value;
			_update = true;
		}

		/**
		 * Vertical rotation angle (in radians)
		 */
		public function get pitch() : Number
		{
			return _newPitch;
		}
		public function set pitch(value : Number)	: void
		{
			_newPitch = value;
			_update = true;
		}

		/**
		 * Position the targets will look at
		 */
		public function get lookAt() : Vector4
		{
			return _lookAt;
		}

		/**
		 * Up vector the targets will rotate around
		 */
		public function get up() : Vector4
		{
			return _up;
		}

		/**
		 * Minimum distance constrain between look at point and target.
		 */
		public function get minDistance() : Number
		{
			return _minDistance;
		}
		public function set minDistance(value : Number)	: void
		{
			_minDistance = value;
			_update = true;
		}

		/**
		 * Maximum distance constrain between look at point and target.
		 */
		public function get maxDistance() : Number
		{
			return _maxDistance;
		}
		public function set maxDistance(value : Number)	: void
		{
			_maxDistance = value;
			_update = true;
		}

		/**
		 * Distance step applied to the camera when the mouse wheel is used in meters/wheel rotation unit
		 */
		public function get distanceStep() : Number
		{
			return _distanceStep;
		}
		public function set distanceStep(value : Number) : void
		{
			_distanceStep = value;
		}

		/**
		 * Horizontal angular step applied to the camera when the mouse is moved in radian/pixel
		 */
		public function get yawStep() : Number
		{
			return _yawStep;
		}
		public function set yawStep(value : Number) : void
		{
			_yawStep = value;
		}

		/**
		 * Vertical angular step applied to the camera when the mouse is moved in radian/pixel
		 */
		public function get pitchStep() : Number
		{
			return _pitchStep;
		}

		public function set pitchStep(value : Number)	: void
		{
			_pitchStep = value;
		}

		public function ArcBallController()
		{
			super();

			_newPitch = Math.PI * .5;
			_lookAt.changed.add(updateNextFrameHandler);
			_up.changed.add(updateNextFrameHandler);
		}

		public function bindDefaultControls(dispatcher : IEventDispatcher) : ArcBallController
		{
			dispatcher.addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler);
			dispatcher.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);

			return this;
		}

		public function unbindDefaultControls(dispatcher : IEventDispatcher) : ArcBallController
		{
			dispatcher.removeEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler);
			dispatcher.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);

			return this;
		}

		override protected function targetAddedHandler(ctrl 	: EnterFrameController,
													   target 	: ISceneNode) : void
		{
			super.targetAddedHandler(ctrl, target);

			_update = true;
			updateTargets();
		}

		override protected function sceneEnterFrameHandler(scene		: Scene,
														   viewport		: Viewport,
														   destination	: BitmapData,
														   time			: Number) : void
		{
			updateTargets((time - _previousTime) * .001);
			_previousTime = time;
		}

		private function clampValues() : void
		{
			if (_newDistance < _minDistance)
				_newDistance = _minDistance;
			if (_newDistance > _maxDistance)
				_newDistance = _maxDistance;
			if (_newPitch <= EPSILON)
				_newPitch = EPSILON;
			if (_newPitch > Math.PI - EPSILON)
				_newPitch = Math.PI - EPSILON;
		}

		private function updateTargets(time : Number = 1) : void
		{
			var enableInertia	: Boolean	= (_inertia != 1. && _interpolationSpeed != 1.);
			if (enableInertia || (_update && _enabled))
			{
				clampValues();

				if (enableInertia)
                {
                    var dt      : Number = time * _interpolationSpeed;
                    var factor  : Number = _inertia;

                    _distance   = (((_distance * factor)    + (_newDistance * dt)) / (factor + dt));
                    _pitch      = (((_pitch * factor)       + (_newPitch * dt)) / (factor + dt));
                    _yaw        = (((_yaw * factor)         + (_newYaw * dt)) / (factor + dt));
                }
				else
				{
					_yaw = _newYaw;
					_pitch = _newPitch;
					_distance = _newDistance;
				}

				_position.set(
					_speed * (_lookAt.x + _distance * Math.cos(_yaw) * Math.sin(_pitch)),
					_speed * (_lookAt.y + _distance * Math.cos(_pitch)),
					_speed * (_lookAt.z + _distance * Math.sin(_yaw) * Math.sin(_pitch))
				);
				TMP_MATRIX.lookAt(_lookAt, _position, _up);

				var numTargets : uint = this.numTargets;
				for (var targetId : uint = 0; targetId < numTargets; ++targetId)
					getTarget(targetId).transform.copyFrom(TMP_MATRIX);

				_update = false;
			}
		}

		protected function mouseWheelHandler(e : MouseEvent) : void
		{
			_newDistance	-=	e.delta * _distanceStep;
			_update			=	true;
		}

		protected function mouseMoveHandler(e : MouseEvent) : void
		{
			// compute position
			if (e.buttonDown && _enabled)
			{
				_newYaw		+= (_mousePosition.x - e.stageX) * _yawStep;
				_newPitch	+= (_mousePosition.y - e.stageY) * _pitchStep;

				_update = true;
			}

			_mousePosition.setTo(e.stageX, e.stageY);
		}

		private function updateNextFrameHandler(vector : Vector4) : void
		{
			_update = true;
		}
    }
}

package aerys.minko.scene.controller.camera
{
	import flash.display.BitmapData;
	import flash.events.IEventDispatcher;
	import flash.events.MouseEvent;
	import flash.geom.Point;

	import aerys.minko.render.Viewport;
	import aerys.minko.scene.controller.EnterFrameController;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	public class ArcBallController extends EnterFrameController
	{
		private static const TMP_MATRIX : Matrix4x4 = new Matrix4x4();
		private static const EPSILON	: Number	= 0.001;

		protected var _mousePosition	: Point		= new Point();

		private var _enabled			: Boolean	= true;

		private var _previousTime		: uint		= 0;

		private var _distance			: Number	= 1.;
		private var _yaw				: Number	= 0;
		private var _pitch				: Number	= 0;
		private var _newDistance		: Number	= 1.;
		private var _newYaw				: Number	= 0;
		private var _newPitch			: Number	= 0;
		private var _update				: Boolean	= true;

		private var _position			: Vector4	= new Vector4(0, 0, 0, 1);
		private var _lookAt				: Vector4	= new Vector4(0, 0, 0, 1);
		private var _up					: Vector4	= new Vector4(0, 1, 0, 1);

		private var _minDistance		: Number	= 1.;
		private var _maxDistance		: Number	= 1000;

		private var _distanceStep		: Number	= 1;
		private var _yawStep			: Number	= 0.01;
		private var _pitchStep			: Number	= 0.01;

		private var _inertia			: Number	= 1.0;
		private var _interpolationSpeed	: Number	= 1;
		private var _speed				: Number	= 1;

		public function get speed() : Number
		{
			return _speed;
		}

		public function set speed(value : Number) : void
		{
			_speed = value;
			_update = true;
		}

		public function get inertia() : Number
		{
			return _inertia;
		}

		public function set inertia(value : Number) : void
		{
			_inertia = value;
		}

		public function get interpolationSpeed() : Number
		{
			return _interpolationSpeed;
		}

		public function set interpolationSpeed(value : Number) : void
		{
			_interpolationSpeed = value;
		}

		public function get enabled() : Boolean
		{
			return _enabled;
		}
		public function set enabled(value : Boolean) : void
		{
			_enabled = value;
		}

		protected function set update(value : Boolean) : void
		{
			_update = value;
		}

		/**
		 * Distance between look at point and target
		 */
		public function get distance() : Number
		{
			return _newDistance;
		}
		public function set distance(value : Number) : void
		{
			_newDistance = value;
			_update = true;
		}

		/**
		 * Horizontal rotation angle (in radians)
		 */
		public function get yaw() : Number
		{
			return _newYaw;
		}
		public function set yaw(value : Number) : void
		{
			_newYaw = value;
			_update = true;
		}

		/**
		 * Vertical rotation angle (in radians)
		 */
		public function get pitch() : Number
		{
			return _newPitch;
		}
		public function set pitch(value : Number)	: void
		{
			_newPitch = value;
			_update = true;
		}

		/**
		 * Position the targets will look at
		 */
		public function get lookAt() : Vector4
		{
			return _lookAt;
		}

		/**
		 * Up vector the targets will rotate around
		 */
		public function get up() : Vector4
		{
			return _up;
		}

		/**
		 * Minimum distance constrain between look at point and target.
		 */
		public function get minDistance() : Number
		{
			return _minDistance;
		}
		public function set minDistance(value : Number)	: void
		{
			_minDistance = value;
			_update = true;
		}

		/**
		 * Maximum distance constrain between look at point and target.
		 */
		public function get maxDistance() : Number
		{
			return _maxDistance;
		}
		public function set maxDistance(value : Number)	: void
		{
			_maxDistance = value;
			_update = true;
		}

		/**
		 * Distance step applied to the camera when the mouse wheel is used in meters/wheel rotation unit
		 */
		public function get distanceStep() : Number
		{
			return _distanceStep;
		}
		public function set distanceStep(value : Number) : void
		{
			_distanceStep = value;
		}

		/**
		 * Horizontal angular step applied to the camera when the mouse is moved in radian/pixel
		 */
		public function get yawStep() : Number
		{
			return _yawStep;
		}
		public function set yawStep(value : Number) : void
		{
			_yawStep = value;
		}

		/**
		 * Vertical angular step applied to the camera when the mouse is moved in radian/pixel
		 */
		public function get pitchStep() : Number
		{
			return _pitchStep;
		}

		public function set pitchStep(value : Number)	: void
		{
			_pitchStep = value;
		}

		public function ArcBallController()
		{
			super();

			_newPitch = Math.PI * .5;
			_lookAt.changed.add(updateNextFrameHandler);
			_up.changed.add(updateNextFrameHandler);
		}

		public function bindDefaultControls(dispatcher : IEventDispatcher) : ArcBallController
		{
			dispatcher.addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler);
			dispatcher.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);

			return this;
		}

		public function unbindDefaultControls(dispatcher : IEventDispatcher) : ArcBallController
		{
			dispatcher.removeEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler);
			dispatcher.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);

			return this;
		}

		override protected function targetAddedHandler(ctrl 	: EnterFrameController,
													   target 	: ISceneNode) : void
		{
			super.targetAddedHandler(ctrl, target);

			_update = true;
			updateTargets();
		}

		override protected function sceneEnterFrameHandler(scene		: Scene,
														   viewport		: Viewport,
														   destination	: BitmapData,
														   time			: Number) : void
		{
			updateTargets((time - _previousTime) * .001);
			_previousTime = time;
		}

		private function clampValues() : void
		{
			if (_newDistance < _minDistance)
				_newDistance = _minDistance;
			if (_newDistance > _maxDistance)
				_newDistance = _maxDistance;
			if (_newPitch <= EPSILON)
				_newPitch = EPSILON;
			if (_newPitch > Math.PI - EPSILON)
				_newPitch = Math.PI - EPSILON;
		}

		private function inertiaConverged() : Boolean
		{
			var distanceDiff	: Number = _newDistance - _distance;
			var pitchDiff		: Number = _newPitch - _pitch;
			var yawDiff			: Number = _newYaw - _yaw;

			return distanceDiff > -EPSILON && distanceDiff < EPSILON &&
				pitchDiff > -EPSILON && pitchDiff < EPSILON &&
				yawDiff > -EPSILON && yawDiff < EPSILON;
		}

		private function updateTargets(time : Number = 1) : void
		{
			var enableInertia	: Boolean	= (_inertia != 1. && _interpolationSpeed != 1.);
			if ((enableInertia && !inertiaConverged()) || (_update && _enabled))
			{
				clampValues();

				if (enableInertia)
                {
                    var dt      : Number = time * _interpolationSpeed;
                    var factor  : Number = _inertia;

                    _distance   = (((_distance * factor)    + (_newDistance * dt)) / (factor + dt));
                    _pitch      = (((_pitch * factor)       + (_newPitch * dt)) / (factor + dt));
                    _yaw        = (((_yaw * factor)         + (_newYaw * dt)) / (factor + dt));
                }
				else
				{
					_yaw = _newYaw;
					_pitch = _newPitch;
					_distance = _newDistance;
				}

				_position.set(
					_speed * (_lookAt.x + _distance * Math.cos(_yaw) * Math.sin(_pitch)),
					_speed * (_lookAt.y + _distance * Math.cos(_pitch)),
					_speed * (_lookAt.z + _distance * Math.sin(_yaw) * Math.sin(_pitch))
				);
				TMP_MATRIX.lookAt(_lookAt, _position, _up);

				var numTargets : uint = this.numTargets;
				for (var targetId : uint = 0; targetId < numTargets; ++targetId)
					getTarget(targetId).transform.copyFrom(TMP_MATRIX);

				_update = false;
			}
		}

		protected function mouseWheelHandler(e : MouseEvent) : void
		{
			_newDistance	-=	e.delta * _distanceStep;
			_update			=	true;
		}

		protected function mouseMoveHandler(e : MouseEvent) : void
		{
			// compute position
			if (e.buttonDown && _enabled)
			{
				_newYaw		+= (_mousePosition.x - e.stageX) * _yawStep;
				_newPitch	+= (_mousePosition.y - e.stageY) * _pitchStep;

				_update = true;
			}

			_mousePosition.setTo(e.stageX, e.stageY);
		}

		private function updateNextFrameHandler(vector : Vector4) : void
		{
			_update = true;
		}
    }
}

Stop updating the ArcBallController once inertia has converged.