 }
 
 }
+
+import flash.display.DisplayObject;
+import flash.events.Event;
+import flash.events.MouseEvent;
+import flash.events.TouchEvent;
+import flash.geom.Point;
+import flash.utils.getTimer;
+
+import mx.events.SandboxMouseEvent;
+
+import spark.components.Scroller;
+import spark.events.TouchScrollEvent;
+
+class TouchScrollHelper
+{
+    private static const HISTORY:int = 5;
+    private static const MIN_START_VELOCITY:Number = 0.05;
+    
+    private static function calculateVelocity(clickHistory:Vector.<Point>, timeHistory:Vector.<int>):Vector.<Point>
+    {
+        var len:int = clickHistory.length - 1;
+        if (len <= 0)
+            
+            return null;
+        
+        var velocities:Vector.<Point> = new Vector.<Point>(len);
+        
+        for (var i:int = 0; i < len; i++)
+        {
+            var point1:Point = clickHistory[i];
+            var point2:Point = clickHistory[i+1];
+            var distance:Point = point2.subtract(point1);
+            var time:Number = timeHistory[i+1] - timeHistory[i];
+            velocities[i] = new Point(distance.x/time, distance.y/time);
+        }
+        
+        return velocities;
+    }
+    
+    private static function averagePoints(points:Vector.<Point>, weighted:Vector.<Number>):Point
+    {
+        var len:int = points.length;
+        
+        var currentPoint:Point = new Point(0, 0);
+        var totalWeight:Number = 0;
+        
+        for (var i:int = 0; i < len; i++)
+        {
+            var point:Point = new Point(points[i].x, points[i].y);
+            totalWeight += weighted[i];
+            point.x *= weighted[i];
+            point.y *= weighted[i];
+            currentPoint.x += point.x;
+            currentPoint.y += point.y;
+        }
+        
+        currentPoint.x = currentPoint.x/totalWeight;
+        currentPoint.y = currentPoint.y/totalWeight;
+        
+        return currentPoint;
+    }
+    
+    public function TouchScrollHelper(scroller:Scroller)
+    {
+        super();
+        
+        clickHistory = new Vector.<Point>();
+        timeHistory = new Vector.<int>();
+        
+        this.scroller = scroller;
+    }
+    
+    public var horizontalSlop:Number;
+    public var verticalSlop:Number;
+    public var diagonalSlop:Number;
+    public var scroller:Scroller;
+    
+    // each of these per mouseEvent (or per touchPoint ID)
+    private var mouseDownedPoint:Point;
+    private var scrollStartPoint:Point;
+    private var startTime:Number;
+    private var mouseDownedDisplayObject:DisplayObject;
+    private var clickHistory:Vector.<Point>;
+    private var timeHistory:Vector.<int>;
+    private var isScrolling:Boolean;
+    
+    public function startScrollWatch(event:Event):void
+    {
+        startTime = flash.utils.getTimer();
+        
+        if (event is MouseEvent && event.type == MouseEvent.MOUSE_DOWN)
+        {
+            var mouseEvent:MouseEvent = event as MouseEvent;
+            
+            if (!isScrolling)
+            {
+                this.mouseDownedDisplayObject = mouseEvent.target as DisplayObject;
+                
+                mouseDownedPoint = new Point(mouseEvent.stageX, mouseEvent.stageY);
+            }
+            
+            installMouseListeners();
+            
+            // if we were already scrolling, continue scrolling
+            if (isScrolling)
+            {
+                // FIXME (rfrishbe): below is same as in mouseMove...should it be?
+//                var scrollDragStartEvent:TouchScrollEvent = new TouchScrollEvent(TouchScrollEvent.TOUCH_SCROLL_START);
+//                scrollDragStartEvent.scrollingObject = scroller;
+//                scroller.dispatchEvent(scrollDragStartEvent);
+                scrollStartPoint = new Point(mouseEvent.stageX, mouseEvent.stageY);
+            }
+            
+            // only need to do this stuff once per target
+            // store last 5 values
+            clickHistory.length = 0;
+            timeHistory.length = 0;
+            
+            addMouseEventHistory(mouseEvent);
+        }
+        else if (event is TouchEvent && event.type == TouchEvent.TOUCH_BEGIN)
+        {
+            // TouchEvent case
+        }            
+    }
+    
+    public function stopScrollWatch():void
+    {
+        uninstallMouseListeners();
+    }
+    
+    private function addMouseEventHistory(event:MouseEvent):Point
+    {
+        var p:Point = new Point(event.stageX, event.stageY);
+        var differencePoint:Point = p.subtract(mouseDownedPoint);
+        
+        if (clickHistory.length >= HISTORY)
+        {
+            clickHistory.shift();
+            timeHistory.shift();
+        }
+        
+        clickHistory.push(differencePoint);
+        timeHistory.push(flash.utils.getTimer() - startTime);
+        
+        return differencePoint;
+    }
+
+    // event listener helpers
+    private function installMouseListeners():void
+    {
+        var sbRoot:DisplayObject = scroller.systemManager.getSandboxRoot();
+        
+        sbRoot.addEventListener(MouseEvent.MOUSE_MOVE, sbRoot_mouseMoveHandler, true);
+        sbRoot.addEventListener(MouseEvent.MOUSE_UP, sbRoot_mouseUpHandler, true);
+        sbRoot.addEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, sbRoot_mouseUpHandler);
+        
+        scroller.systemManager.deployMouseShields(true);
+    }
+    
+    private function uninstallMouseListeners():void
+    {
+        var sbRoot:DisplayObject = scroller.systemManager.getSandboxRoot();
+        
+        // mouse events added in installMouseListeners()
+        sbRoot.removeEventListener(MouseEvent.MOUSE_MOVE, sbRoot_mouseMoveHandler, true);
+        sbRoot.removeEventListener(MouseEvent.MOUSE_UP, sbRoot_mouseUpHandler, true);
+        sbRoot.removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, sbRoot_mouseUpHandler);
+        
+        scroller.systemManager.deployMouseShields(false);
+    }
+    
+    // event listeners
+    /**
+     *  @private
+     * 
+     *  @langversion 3.0
+     *  @playerversion Flash 10
+     *  @playerversion AIR 1.5
+     *  @productversion Flex 4
+     */
+    protected function sbRoot_mouseMoveHandler(event:MouseEvent):void
+    {
+        var mouseDownedDifference:Point = addMouseEventHistory(event);
+        
+        if (!isScrolling)
+        {
+            var shouldBeScrolling:Boolean = false;
+            
+            switch (scroller.viewport.scrollAxis)
+            {
+                case "vertical":
+                    shouldBeScrolling = Math.abs(mouseDownedDifference.y) >= verticalSlop;
+                    break;
+                case "horizontal":
+                    shouldBeScrolling = Math.abs(mouseDownedDifference.x) >= horizontalSlop;
+                    break;
+                case "diagonal":
+                    shouldBeScrolling = Math.abs(mouseDownedDifference.length) >= diagonalSlop;
+                    break;
+            }
+            
+            // Check to see if we should be scrolling
+            if (shouldBeScrolling)
+            {
+                // cancellable and bubbling event
+                var scrollDragStartingEvent:TouchScrollEvent = new TouchScrollEvent(TouchScrollEvent.TOUCH_SCROLL_STARTING, true, true);
+                scrollDragStartingEvent.scrollingObject = scroller;
+                var eventAccepted:Boolean = mouseDownedDisplayObject.dispatchEvent(scrollDragStartingEvent);
+                
+                if (!eventAccepted)
+                {                    
+                    // TODO (rfrishbe): do we need to call updateAfterEvent() here and below?
+                    event.updateAfterEvent();
+                    
+                    // if our mouse target has been rejected, let's give up and remove all the listeners
+                    uninstallMouseListeners();
+                    
+                    return;
+                }
+                
+                var scrollDragStartEvent:TouchScrollEvent = new TouchScrollEvent(TouchScrollEvent.TOUCH_SCROLL_START);
+                scrollDragStartEvent.scrollingObject = scroller;
+                scroller.dispatchEvent(scrollDragStartEvent);
+                
+                // FIXME (rfrishbe): the difference should not be from the original point but from the slop.
+                // otherwise we "jump" on the first move.
+                // we should reset startPoint here to be this point minus slop (in the direction that caused the scroll)
+                scrollStartPoint = new Point(event.stageX, event.stageY);
+                isScrolling = true;
+                
+                // velocity calculations come from mouseDownedPoint.  The drag ones com from scrollStartPoint.
+            }
+            
+            
+        }
+        
+        // if we are scrolling
+        if (isScrolling)
+        {
+            var p:Point = new Point(event.stageX, event.stageY);
+            var scrollDifferencePoint:Point = p.subtract(scrollStartPoint);
+            
+            var scrollDragEvent:TouchScrollEvent = new TouchScrollEvent(TouchScrollEvent.TOUCH_SCROLL_DRAG, false, false);
+            scrollDragEvent.scrollingObject = scroller;
+            scrollDragEvent.dragX = scrollDifferencePoint.x;
+            scrollDragEvent.dragY = scrollDifferencePoint.y;
+            
+            scroller.dispatchEvent(scrollDragEvent);
+            event.updateAfterEvent();
+        }
+    }
+    
+    /**
+     *  @private
+     *  Called when the user releases the TitleWindow.
+     * 
+     *  @langversion 3.0
+     *  @playerversion Flash 10
+     *  @playerversion AIR 1.5
+     *  @productversion Flex 4
+     */
+    protected function sbRoot_mouseUpHandler(event:Event):void
+    {
+        uninstallMouseListeners();
+        
+        // FIXME (rfrishbe): can we scroll by velocity even tho no drag took place?
+        if (!isScrolling)
+            return;
+        
+        if (event is MouseEvent)
+            addMouseEventHistory(event as MouseEvent);
+        
+        // decide about throw
+        
+        // pad click and timeHistory if needed
+        var currentTime:Number = getTimer();
+        
+        // calculate average time b/w events and see if the last two (mouseMove and this mouseUp) 
+        // were far apart.  If they were, then don't do anything if the velocity of them is small.
+        var averageTime:Number = 0;
+        var len:int = timeHistory.length;
+        
+        // FIXME (rfrishbe): this isn't true sine we could get a mouseUpSomehwere event
+        // gauranteed to have 2 mouse events b/c atleast a mousedown and a mouse up
+        
+        for (var i:int = 0; i < len - 2; i++)
+        {
+            averageTime += timeHistory[i+1] - timeHistory[i];
+        }
+        averageTime /= len-2;
+        
+        var lastTime:Number = timeHistory[len-1] - timeHistory[len-2];
+        var lastVelocity:Point = clickHistory[len-1].subtract(clickHistory[len-2]);
+        lastVelocity.x /= lastTime;
+        lastVelocity.y /= lastTime;
+        
+        // FIXME (rfrishbe): should be minXVelocity, minYVelocity, minDiagonalVelocity
+        // FIXME (rfrishbe): this should be parameterized better
+        if ( (lastTime >= 3*averageTime) &&
+            (lastVelocity.length <= MIN_START_VELOCITY))
+        {
+            isScrolling = false;
+            
+            // don't do anything
+            var scrollEndEvent:TouchScrollEvent = new TouchScrollEvent(TouchScrollEvent.TOUCH_SCROLL_END);
+            scrollEndEvent.scrollingObject = scroller;
+            scroller.dispatchEvent(scrollEndEvent);
+            return;
+        }
+        
+        
+        var velocities:Vector.<Point> = calculateVelocity(clickHistory, timeHistory);
+        
+        var velocityWeights:Vector.<Number> = Vector.<Number>([1,1.33,1.66,2]);
+        var throwVelocity:Point = averagePoints(velocities, velocityWeights);
+        
+        //            trace("velocity: ", initialVelocity.y);
+        if (throwVelocity.length > MIN_START_VELOCITY)
+        {
+            var scrollEndAndThrowEvent:TouchScrollEvent = new TouchScrollEvent(TouchScrollEvent.TOUCH_SCROLL_THROW, false, false);
+            scrollEndAndThrowEvent.scrollingObject = scroller;
+            scrollEndAndThrowEvent.throwVelocity = throwVelocity;
+            
+            scroller.addEventListener(TouchScrollEvent.TOUCH_SCROLL_THROW_ANIMATION_END, scroller_touchScrollThrowAnimationEnd);
+            scroller.dispatchEvent(scrollEndAndThrowEvent);
+        }
+        
+        // if we own this user gesture, don't let others see this event
+//        if (isScrolling)
+//            event.stopImmediatePropagation();
+    }
+    
+    private function scroller_touchScrollThrowAnimationEnd(event:TouchScrollEvent):void
+    {
+        scroller.removeEventListener(TouchScrollEvent.TOUCH_SCROLL_THROW_ANIMATION_END, scroller_touchScrollThrowAnimationEnd);
+        
+        isScrolling = false;
+        
+        var scrollEndEvent:TouchScrollEvent = new TouchScrollEvent(TouchScrollEvent.TOUCH_SCROLL_END);
+        scrollEndEvent.scrollingObject = scroller;
+        scroller.dispatchEvent(scrollEndEvent);
+    }
+    
+    
+}

     */
    override public function styleChanged(styleProp:String):void
    {
        var allStyles:Boolean = (styleProp == null || styleProp == "styleName");
        super.styleChanged(styleProp);
        if (allStyles || styleProp == "horizontalScrollPolicy" || 
            styleProp == "verticalScrollPolicy")
        {
            invalidateSkin();
        }
    }

    /**


package spark.components
{
import flash.display.InteractiveObject;
import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.system.ApplicationDomain;
import flash.text.TextField;
import flash.ui.Keyboard;

import mx.core.IInvalidating;
import mx.core.IVisualElement;
import mx.core.IVisualElementContainer;
import mx.core.LayoutDirection;
import mx.core.ScrollPolicy;
import mx.events.EffectEvent;
import mx.events.PropertyChangeEvent;
import mx.managers.IFocusManagerComponent;

import spark.components.Group;
import spark.components.supportClasses.ScrollerLayout;
import spark.components.supportClasses.SkinnableComponent;
import spark.components.supportClasses.TouchScrollingEasing;
import spark.core.IViewport;
import spark.core.NavigationUnit;
import spark.effects.Animate;
import spark.effects.animation.MotionPath;
import spark.effects.animation.SimpleMotionPath;
import spark.events.TouchScrollEvent;

include "../styles/metadata/BasicInheritingTextStyles.as"
include "../styles/metadata/AdvancedInheritingTextStyles.as"
