/**
* @author Fabien BIZOT
* http://lafabrick.com/blog
* http://twitter.com/fabienbizot
*/
package com.lafabrick.components
{
import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.ui.Keyboard;
import mx.events.FlexEvent;
import mx.events.SandboxMouseEvent;
import mx.managers.IFocusManagerComponent;
import spark.components.supportClasses.Range;
import spark.effects.animation.Animation;
import spark.effects.animation.MotionPath;
import spark.effects.animation.SimpleMotionPath;
import spark.effects.easing.Sine;
import spark.events.TrackBaseEvent;
[Event("change", type="flash.events.Event")]
[Event("changeEnd", type="mx.events.FlexEvent")]
[Event("changeStart", type="mx.events.FlexEvent")]
[Event("thumbDrag", type="spark.events.TrackBaseEvent")]
[Event("thumbPress", type="spark.events.TrackBaseEvent")]
[Event("thumbRelease", type="spark.events.TrackBaseEvent")]
[DefaultTriggerEvent("change")]
[Style("slideDuration", type="Number", format="Time", inherit="no")]
[Exclude("color", kind="style")]
[Exclude("fontSize", kind="style")]
[Exclude("fontWeight", kind="style")]
[Exclude("textAlign", kind="style")]
[AccessibilityClass("spark.accessibility.SliderBaseAccImpl")]
[SkinState("normal")]
[SkinState("disabled")]
public class CircularSlider extends Range implements IFocusManagerComponent
{
public function CircularSlider()
{
super();
addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
}
public static const CLOCKWISE:String = "clockwise";
public static const COUNTERCLOCKWISE:String = "counterclockwise";
[SkinPart("false")]
public var thumb : PieButton;
[SkinPart("false")]
public var track : PieButton;
private var _angle : Number;
private var _startAngle : Number;
private var _clickOffset:Point;
private var _animator:Animation = null;
private var _slideToValue:Number;
private var _rotationDirection : String;
private var _isKeyDown:Boolean = false;
public function get rotationDirection():String
{
return _rotationDirection;
}
[Inspectable("clockwise,counterclockwise",defaultValue="counterclockwise",type="String")]
public function set rotationDirection(value:String):void
{
_rotationDirection = value;
invalidateDisplayList();
}
public function get angle():Number
{
return _angle;
}
public function set angle(value:Number):void
{
_angle = value;
invalidateDisplayList();
}
public function get startAngle():Number
{
return _startAngle;
}
public function set startAngle(value:Number):void
{
_startAngle = value;
invalidateDisplayList();
}
override public function set maximum(value:Number):void
{
if (value == super.maximum)
return;
super.maximum = value;
invalidateSkinState();
invalidateDisplayList();
}
override public function set minimum(value:Number):void
{
if (value == super.minimum)
return;
super.minimum = value;
invalidateSkinState();
invalidateDisplayList();
}
[Bindable("valueCommit")]
override public function get value():Number
{
return super.value;
}
override public function set value(newValue:Number):void
{
if (newValue == super.value)
return;
super.value = newValue;
invalidateDisplayList();
}
override protected function setValue(value:Number):void
{
super.setValue(value);
invalidateDisplayList();
}
override public function changeValueByStep(increase:Boolean = true):void
{
var prevValue:Number = this.value;
super.changeValueByStep(increase);
if (value != prevValue)
dispatchEvent(new Event(Event.CHANGE));
}
override protected function getCurrentSkinState():String
{
return enabled ? "normal" : "disabled";
}
private function addedToStageHandler(event:Event):void
{
updateSkinDisplayList();
}
override protected function updateDisplayList(w:Number, h:Number):void
{
super.updateDisplayList(w, h);
updateSkinDisplayList();
}
protected function updateSkinDisplayList():void
{
if( track ) {
track.angle = angle;
track.startAngle = startAngle;
}
if( thumb ) {
thumb.startAngle = valueToAngle();
}
}
protected function valueToAngle() : Number
{
var nvalue : Number = value * ( angle-thumb.angle ) / maximum;
if( rotationDirection == CLOCKWISE ) {
return angle-thumb.angle-nvalue+startAngle;
}
return nvalue+startAngle;
}
protected function pointToValue(x:Number, y:Number):Number
{
if (!thumb || !track)
return 0;
var centerX : Number = ( getLayoutBoundsWidth() ) /2;
var centerY : Number = ( getLayoutBoundsHeight() ) /2;
var xoffset : Number = 0;
var yoffset : Number = 0;
if( width > height ) {
yoffset = height/width;
}
if( width < height ) {
xoffset = width/height;
}
var clickWidth : Number = x - centerX;
var clickHeight : Number = -y + centerY;
clickWidth *= 2-yoffset;
clickHeight *= 2-xoffset;
var radStartAngle : Number = startAngle/180*Math.PI;
var newRadAngle : Number = Math.atan2( clickHeight, clickWidth );
newRadAngle -= radStartAngle;
if( newRadAngle < 0 ) {
newRadAngle += (2*Math.PI);
}
var newAngle : Number = ( (newRadAngle) * 180 / Math.PI);
if( newAngle < 0 ) {
newAngle = 360+newAngle;
}
var val : Number = (newAngle*maximum)/angle;
if( rotationDirection == CLOCKWISE ) {
val = maximum-val;
}
if( newAngle > angle || newAngle < 0 ) {
if( newAngle > angle+(360-angle)/2 ) {
return rotationDirection == CLOCKWISE ? maximum : minimum;
}
else {
return rotationDirection == CLOCKWISE ? minimum : maximum;
}
}
return val;
}
override protected function partAdded(partName:String, instance:Object):void
{
super.partAdded(partName, instance);
if (instance == thumb)
{
thumb.focusEnabled = false;
thumb.addEventListener(MouseEvent.MOUSE_DOWN, thumb_mouseDownHandler);
thumb.addEventListener(FlexEvent.UPDATE_COMPLETE, thumb_updateCompleteHandler);
thumb.stickyHighlighting = true;
}
else if (instance == track)
{
track.focusEnabled = false;
track.addEventListener(MouseEvent.MOUSE_DOWN, track_mouseDownHandler);
}
}
override protected function partRemoved(partName:String, instance:Object):void
{
super.partRemoved(partName, instance);
if (instance == thumb) {
thumb.removeEventListener(MouseEvent.MOUSE_DOWN, thumb_mouseDownHandler);
thumb.removeEventListener(FlexEvent.UPDATE_COMPLETE, thumb_updateCompleteHandler);
}
else if (instance == track) {
track.removeEventListener(MouseEvent.MOUSE_DOWN, track_mouseDownHandler);
}
}
override protected function focusInHandler(event:FocusEvent):void
{
super.focusInHandler(event);
systemManager.getSandboxRoot().addEventListener(MouseEvent.MOUSE_WHEEL, system_mouseWheelHandler, true);
}
override protected function focusOutHandler(event:FocusEvent):void
{
super.focusOutHandler(event);
systemManager.getSandboxRoot().removeEventListener(MouseEvent.MOUSE_WHEEL, system_mouseWheelHandler, true);
}
protected function system_mouseWheelHandler(event:MouseEvent):void
{
if (!event.isDefaultPrevented())
{
var newValue:Number = nearestValidValue(value + event.delta * stepSize, stepSize);
setValue(newValue);
dispatchEvent(new Event(Event.CHANGE));
event.preventDefault();
}
}
protected function thumb_mouseDownHandler(event:MouseEvent):void
{
systemManager.getSandboxRoot().addEventListener(MouseEvent.MOUSE_MOVE, system_mouseMoveHandler, true);
systemManager.getSandboxRoot().addEventListener(MouseEvent.MOUSE_UP, system_mouseUpHandler, true);
systemManager.getSandboxRoot().addEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, system_mouseUpHandler);
_clickOffset = thumb.globalToLocal(new Point(event.stageX, event.stageY));
dispatchEvent(new TrackBaseEvent(TrackBaseEvent.THUMB_PRESS));
dispatchEvent(new FlexEvent(FlexEvent.CHANGE_START));
}
protected function system_mouseMoveHandler(event:MouseEvent):void
{
if (!track)
return;
var point:Point = track.globalToLocal(new Point(event.stageX, event.stageY));
var newValue:Number = pointToValue(point.x, point.y);
newValue = nearestValidValue(newValue, snapInterval);
if (newValue != value)
{
setValue(newValue);
dispatchEvent(new TrackBaseEvent(TrackBaseEvent.THUMB_DRAG));
dispatchEvent(new Event(Event.CHANGE));
}
event.updateAfterEvent();
}
protected function system_mouseUpHandler(event:Event):void
{
systemManager.getSandboxRoot().removeEventListener(MouseEvent.MOUSE_MOVE, system_mouseMoveHandler, true);
systemManager.getSandboxRoot().removeEventListener(MouseEvent.MOUSE_UP, system_mouseUpHandler, true);
systemManager.getSandboxRoot().removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, system_mouseUpHandler);
dispatchEvent(new TrackBaseEvent(TrackBaseEvent.THUMB_RELEASE));
dispatchEvent(new FlexEvent(FlexEvent.CHANGE_END));
}
protected function track_mouseDownHandler(event:MouseEvent):void
{
if (!enabled)
return;
var point:Point = track.globalToLocal(new Point(event.stageX, event.stageY));
var newValue:Number = pointToValue(point.x, point.y);
newValue = nearestValidValue(newValue, snapInterval);
if (newValue != value)
{
var slideDuration:Number = getStyle("slideDuration");
if (slideDuration != 0) {
if (!_animator)
{
_animator = new Animation();
var animTarget:AnimationTarget = new AnimationTarget(animationUpdateHandler);
animTarget.endFunction = animationEndHandler;
_animator.animationTarget = animTarget;
_animator.easer = new Sine(0);
}
if (_animator.isPlaying)
stopAnimation();
_slideToValue = newValue;
_animator.duration = slideDuration * (Math.abs(value - _slideToValue) / (maximum - minimum));
_animator.motionPaths = new <MotionPath>[new SimpleMotionPath("value", value, _slideToValue)];
dispatchEvent(new FlexEvent(FlexEvent.CHANGE_START));
_animator.play();
}
else {
setValue(newValue);
dispatchEvent(new Event(Event.CHANGE));
}
}
event.updateAfterEvent();
}
private function animationUpdateHandler(animation:Animation):void
{
value = animation.currentValue["value"];
}
private function animationEndHandler(animation:Animation):void
{
setValue(_slideToValue);
dispatchEvent(new Event(Event.CHANGE));
dispatchEvent(new FlexEvent(FlexEvent.CHANGE_END));
}
private function stopAnimation():void
{
_animator.stop();
setValue(nearestValidValue(value, snapInterval));
dispatchEvent(new Event(Event.CHANGE));
dispatchEvent(new FlexEvent(FlexEvent.CHANGE_END));
}
private function thumb_updateCompleteHandler(event:Event):void
{
updateSkinDisplayList();
thumb.removeEventListener(FlexEvent.UPDATE_COMPLETE, thumb_updateCompleteHandler);
}
override protected function keyUpHandler(event:KeyboardEvent) : void
{
switch (event.keyCode)
{
case Keyboard.DOWN:
case Keyboard.LEFT:
case Keyboard.UP:
case Keyboard.RIGHT:
{
if (_isKeyDown)
{
dispatchEvent(new FlexEvent(FlexEvent.CHANGE_END));
_isKeyDown = false;
}
event.preventDefault();
break;
}
}
}
override protected function keyDownHandler(event:KeyboardEvent):void
{
super.keyDownHandler(event);
if (event.isDefaultPrevented())
return;
if (_animator && _animator.isPlaying)
stopAnimation();
var prevValue:Number = this.value;
var newValue:Number;
switch (event.keyCode)
{
case Keyboard.DOWN:
case Keyboard.LEFT:
{
newValue = nearestValidValue(value - stepSize, snapInterval);
if (prevValue != newValue)
{
if (!_isKeyDown)
{
dispatchEvent(new FlexEvent(FlexEvent.CHANGE_START));
_isKeyDown = true;
}
setValue(newValue);
dispatchEvent(new Event(Event.CHANGE));
}
event.preventDefault();
break;
}
case Keyboard.UP:
case Keyboard.RIGHT:
{
newValue = nearestValidValue(value + stepSize, snapInterval);
if (prevValue != newValue)
{
if (!_isKeyDown)
{
dispatchEvent(new FlexEvent(FlexEvent.CHANGE_START));
_isKeyDown = true;
}
setValue(newValue);
dispatchEvent(new Event(Event.CHANGE));
}
event.preventDefault();
break;
}
case Keyboard.HOME:
{
value = minimum;
if (value != prevValue)
dispatchEvent(new Event(Event.CHANGE));
event.preventDefault();
break;
}
case Keyboard.END:
{
value = maximum;
if (value != prevValue)
dispatchEvent(new Event(Event.CHANGE));
event.preventDefault();
break;
}
}
}
}
}
import spark.effects.animation.Animation;
import spark.effects.animation.IAnimationTarget;
internal class AnimationTarget implements IAnimationTarget
{
public var updateFunction:Function;
public var startFunction:Function;
public var stopFunction:Function;
public var endFunction:Function;
public var repeatFunction:Function;
public function AnimationTarget(updateFunction:Function = null)
{
this.updateFunction = updateFunction;
}
public function animationStart(animation:Animation):void
{
if (startFunction != null)
startFunction(animation);
}
public function animationEnd(animation:Animation):void
{
if (endFunction != null)
endFunction(animation);
}
public function animationStop(animation:Animation):void
{
if (stopFunction != null)
stopFunction(animation);
}
public function animationRepeat(animation:Animation):void
{
if (repeatFunction != null)
repeatFunction(animation);
}
public function animationUpdate(animation:Animation):void
{
if (updateFunction != null)
updateFunction(animation);
}
}