package flare.vis.controls {
import flare.util.Displays;
import flash.display.InteractiveObject;
import flash.display.Stage;
import flash.events.Event;
import flash.events.MouseEvent;
/**
* Interactive control for panning and zooming a "camera". Any sprite can
* be treated as a camera onto its drawing content and display list
* children. The PanZoomControl allows you to manipulate a sprite's
* transformation matrix (the transform.matrix
property) to
* simulate camera movements such as panning and zooming. To pan and
* zoom over a collection of objects, simply add a PanZoomControl for
* the sprite holding the collection.
*
*
* var s:Sprite; // a sprite holding a collection of items
* new PanZoomControl().attach(s); // attach pan and zoom controls to the sprite
*
* Once a PanZoomControl has been created, panning is performed by
* clicking and dragging. Zooming is performed either by scrolling the
* mouse wheel or by clicking and dragging vertically while the control key
* is pressed.
*
* By default, the PanZoomControl attaches itself to the
* stage
to listen for mouse events. This works fine if there
* is only one collection of objects in the display list, but can cause
* trouble if you want to have multiple collections that can be separately
* panned and zoomed. The PanZoomControl constructor takes a second
* argument that specifies a "hit area", a shape in the display list that
* should be used to listen to the mouse events for panning and zooming.
* For example, this could be a background sprite behind the zoomable
* content, to which the "camera" sprite could be added. One can then set
* the scrollRect
property to add clipping bounds to the
* panning and zooming region.
*/
public class PanZoomControl extends Control
{
private var px:Number, py:Number;
private var dx:Number, dy:Number;
private var mx:Number, my:Number;
private var _drag:Boolean = false;
private var _hit:InteractiveObject;
private var _stage:Stage;
/** The active hit area over which pan/zoom interactions can be performed. */
public function get hitArea():InteractiveObject { return _hit; }
public function set hitArea(hitArea:InteractiveObject):void {
if (_hit != null) onRemove();
_hit = hitArea;
if (_object && _object.stage != null) onAdd();
}
/**
* Creates a new PanZoomControl.
* @param hitArea a display object to use as the hit area for mouse
* events. For example, this could be a background region over which
* the panning and zooming should be done. If this argument is null,
* the stage will be used.
*/
public function PanZoomControl(hitArea:InteractiveObject=null):void
{
_hit = hitArea;
}
/** @inheritDoc */
public override function attach(obj:InteractiveObject):void
{
super.attach(obj);
if (obj != null) {
obj.addEventListener(Event.ADDED_TO_STAGE, onAdd);
obj.addEventListener(Event.REMOVED_FROM_STAGE, onRemove);
if (obj.stage != null) onAdd();
}
}
/** @inheritDoc */
public override function detach():InteractiveObject
{
onRemove();
_object.removeEventListener(Event.ADDED_TO_STAGE, onAdd);
_object.removeEventListener(Event.REMOVED_FROM_STAGE, onRemove);
return super.detach();
}
private function onAdd(evt:Event=null):void
{
_stage = _object.stage;
if (_hit == null) {
_hit = _stage;
}
_hit.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
_hit.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
}
private function onRemove(evt:Event=null):void
{
_hit.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
_hit.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
}
private function onMouseDown(event:MouseEvent) : void
{
if (_stage == null) return;
if (_hit == _object && event.target != _hit) return;
_stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
_stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
px = mx = event.stageX;
py = my = event.stageY;
_drag = true;
}
private function onMouseMove(event:MouseEvent) : void
{
if (!_drag) return;
var x:Number = event.stageX;
var y:Number = event.stageY;
if (!event.ctrlKey) {
dx = dy = NaN;
Displays.panBy(_object, x-mx, y-my);
} else {
if (isNaN(dx)) {
dx = event.stageX;
dy = event.stageY;
}
var dz:Number = 1 + (y-my)/100;
Displays.zoomBy(_object, dz, dx, dy);
}
mx = x;
my = y;
}
private function onMouseUp(event:MouseEvent) : void
{
dx = dy = NaN;
_drag = false;
_stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
_stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
}
private function onMouseWheel(event:MouseEvent) : void
{
var dw:Number = 1.1 * event.delta;
var dz:Number = dw < 0 ? 1/Math.abs(dw) : dw;
Displays.zoomBy(_object, dz);
}
} // end of class PanZoomControl
}