/** senocular.com display package */ package com.senocular.display { import flash.display.DisplayObject; import flash.display.Shape; import flash.display.Sprite; import flash.display.Stage; import flash.events.Event; import flash.events.EventPhase; import flash.events.MouseEvent; import flash.geom.Matrix; import flash.geom.Point; import flash.geom.Rectangle; import flash.geom.Transform; import flash.utils.Dictionary; // TODO: Documentation /** * Creates a transform tool that allows uaers to modify display objects on the screen * * @usage *
* var tool:TransformTool = new TransformTool(); * addChild(tool); * tool.target = targetDisplayObject; ** * @version 0.9.10 * @author Trevor McCauley * @author http://www.senocular.com */ public class TransformTool extends Sprite { // Variables private var toolInvertedMatrix:Matrix = new Matrix(); private var innerRegistration:Point = new Point(); private var registrationLog:Dictionary = new Dictionary(true); private var targetBounds:Rectangle = new Rectangle(); private var mouseLoc:Point = new Point(); private var mouseOffset:Point = new Point(); private var innerMouseLoc:Point = new Point(); private var interactionStart:Point = new Point(); private var innerInteractionStart:Point = new Point(); private var interactionStartAngle:Number = 0; private var interactionStartMatrix:Matrix = new Matrix(); private var toolSprites:Sprite = new Sprite(); private var lines:Sprite = new Sprite(); private var moveControls:Sprite = new Sprite(); private var registrationControls:Sprite = new Sprite(); private var rotateControls:Sprite = new Sprite(); private var scaleControls:Sprite = new Sprite(); private var skewControls:Sprite = new Sprite(); private var cursors:Sprite = new Sprite(); private var customControls:Sprite = new Sprite(); private var customCursors:Sprite = new Sprite(); // With getter/setters private var _target:DisplayObject; private var _toolMatrix:Matrix = new Matrix(); private var _globalMatrix:Matrix = new Matrix(); private var _registration:Point = new Point(); private var _livePreview:Boolean = true; private var _raiseNewTargets:Boolean = true; private var _moveNewTargets:Boolean = true; private var _moveEnabled:Boolean = true; private var _registrationEnabled:Boolean = true; private var _rotationEnabled:Boolean = true; private var _scaleEnabled:Boolean = true; private var _skewEnabled:Boolean = true; private var _outlineEnabled:Boolean = true; private var _customControlsEnabled:Boolean = true; private var _customCursorsEnabled:Boolean = true; private var _cursorsEnabled:Boolean = true; private var _rememberRegistration:Boolean = true; private var _constrainScale:Boolean = false; private var _constrainRotationAngle:Number = Math.PI/4; // default at 45 degrees private var _constrainRotation:Boolean = false; private var _moveUnderObjects:Boolean = true; private var _maintainControlForm:Boolean = true; private var _controlSize:Number = 8; private var _maxScaleX:Number = Infinity; private var _maxScaleY:Number = Infinity; private var _boundsTopLeft:Point = new Point(); private var _boundsTop:Point = new Point(); private var _boundsTopRight:Point = new Point(); private var _boundsRight:Point = new Point(); private var _boundsBottomRight:Point = new Point(); private var _boundsBottom:Point = new Point(); private var _boundsBottomLeft:Point = new Point(); private var _boundsLeft:Point = new Point(); private var _boundsCenter:Point = new Point(); private var _currentControl:TransformToolControl; private var _moveControl:TransformToolControl; private var _registrationControl:TransformToolControl; private var _outlineControl:TransformToolControl; private var _scaleTopLeftControl:TransformToolControl; private var _scaleTopControl:TransformToolControl; private var _scaleTopRightControl:TransformToolControl; private var _scaleRightControl:TransformToolControl; private var _scaleBottomRightControl:TransformToolControl; private var _scaleBottomControl:TransformToolControl; private var _scaleBottomLeftControl:TransformToolControl; private var _scaleLeftControl:TransformToolControl; private var _rotationTopLeftControl:TransformToolControl; private var _rotationTopRightControl:TransformToolControl; private var _rotationBottomRightControl:TransformToolControl; private var _rotationBottomLeftControl:TransformToolControl; private var _skewTopControl:TransformToolControl; private var _skewRightControl:TransformToolControl; private var _skewBottomControl:TransformToolControl; private var _skewLeftControl:TransformToolControl; private var _moveCursor:TransformToolCursor; private var _registrationCursor:TransformToolCursor; private var _rotationCursor:TransformToolCursor; private var _scaleCursor:TransformToolCursor; private var _skewCursor:TransformToolCursor; // Event constants public static const NEW_TARGET:String = "newTarget"; public static const TRANSFORM_TARGET:String = "transformTarget"; public static const TRANSFORM_TOOL:String = "transformTool"; public static const CONTROL_INIT:String = "controlInit"; public static const CONTROL_TRANSFORM_TOOL:String = "controlTransformTool"; public static const CONTROL_DOWN:String = "controlDown"; public static const CONTROL_MOVE:String = "controlMove"; public static const CONTROL_UP:String = "controlUp"; public static const CONTROL_PREFERENCE:String = "controlPreference"; // Skin constants public static const REGISTRATION:String = "registration"; public static const SCALE_TOP_LEFT:String = "scaleTopLeft"; public static const SCALE_TOP:String = "scaleTop"; public static const SCALE_TOP_RIGHT:String = "scaleTopRight"; public static const SCALE_RIGHT:String = "scaleRight"; public static const SCALE_BOTTOM_RIGHT:String = "scaleBottomRight"; public static const SCALE_BOTTOM:String = "scaleBottom"; public static const SCALE_BOTTOM_LEFT:String = "scaleBottomLeft"; public static const SCALE_LEFT:String = "scaleLeft"; public static const ROTATION_TOP_LEFT:String = "rotationTopLeft"; public static const ROTATION_TOP_RIGHT:String = "rotationTopRight"; public static const ROTATION_BOTTOM_RIGHT:String = "rotationBottomRight"; public static const ROTATION_BOTTOM_LEFT:String = "rotationBottomLeft"; public static const SKEW_TOP:String = "skewTop"; public static const SKEW_RIGHT:String = "skewRight"; public static const SKEW_BOTTOM:String = "skewBottom"; public static const SKEW_LEFT:String = "skewLeft"; public static const CURSOR_REGISTRATION:String = "cursorRegistration"; public static const CURSOR_MOVE:String = "cursorMove"; public static const CURSOR_SCALE:String = "cursorScale"; public static const CURSOR_ROTATION:String = "cursorRotate"; public static const CURSOR_SKEW:String = "cursorSkew"; // Properties /** * The display object the transform tool affects */ public function get target():DisplayObject { return _target; } public function set target(d:DisplayObject):void { // null target, set target as null if (!d) { if (_target) { _target = null; updateControlsVisible(); dispatchEvent(new Event(NEW_TARGET)); } return; }else{ // invalid target, do nothing if (d == _target || d == this || contains(d) || d.contains(this)) { return; } // valid target, set and update _target = d; updateMatrix(); setNewRegistation(); updateControlsVisible(); // raise to top of display list if applies if (_raiseNewTargets) { raiseTarget(); } } // if not moving new targets, apply transforms if (!_moveNewTargets) { apply(); } // send event; updates control points dispatchEvent(new Event(NEW_TARGET)); // initiate move interaction if applies after controls updated if (_moveNewTargets && _moveEnabled && _moveControl) { _currentControl = _moveControl; _currentControl.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_DOWN)); } } /** * When true, new targets are placed at the top of their display list * @see target */ public function get raiseNewTargets():Boolean { return _raiseNewTargets; } public function set raiseNewTargets(b:Boolean):void { _raiseNewTargets = b; } /** * When true, new targets are immediately given a move interaction and can be dragged * @see target * @see moveEnabled */ public function get moveNewTargets():Boolean { return _moveNewTargets; } public function set moveNewTargets(b:Boolean):void { _moveNewTargets = b; } /** * When true, the target instance scales with the tool as it is transformed. * When false, transforms in the tool are only reflected when transforms are completed. */ public function get livePreview():Boolean { return _livePreview; } public function set livePreview(b:Boolean):void { _livePreview = b; } /** * Controls the default Control sizes of controls used by the tool */ public function get controlSize():Number { return _controlSize; } public function set controlSize(n:Number):void { if (_controlSize != n) { _controlSize = n; dispatchEvent(new Event(CONTROL_PREFERENCE)); } } /** * When true, counters transformations applied to controls by their parent containers */ public function get maintainControlForm():Boolean { return _maintainControlForm; } public function set maintainControlForm(b:Boolean):void { if (_maintainControlForm != b) { _maintainControlForm = b; dispatchEvent(new Event(CONTROL_PREFERENCE)); } } /** * When true (default), the transform tool uses an invisible control using the shape of the current * target to allow movement. This means any objects above the target but below the * tool cannot be clicked on since this hidden control will be clicked on first * (allowing you to move objects below others without selecting the objects on top). * When false, the target itself is used for movement and any objects above the target * become clickable preventing tool movement if the target itself is not clicked directly. */ public function get moveUnderObjects():Boolean { return _moveUnderObjects; } public function set moveUnderObjects(b:Boolean):void { if (_moveUnderObjects != b) { _moveUnderObjects = b; dispatchEvent(new Event(CONTROL_PREFERENCE)); } } /** * The transform matrix of the tool * as it exists in its on coordinate space * @see globalMatrix */ public function get toolMatrix():Matrix { return _toolMatrix.clone(); } public function set toolMatrix(m:Matrix):void { updateMatrix(m, false); updateRegistration(); dispatchEvent(new Event(TRANSFORM_TOOL)); } /** * The transform matrix of the tool * as it appears in global space * @see toolMatrix */ public function get globalMatrix():Matrix { var _globalMatrix:Matrix = _toolMatrix.clone(); _globalMatrix.concat(transform.concatenatedMatrix); return _globalMatrix; } public function set globalMatrix(m:Matrix):void { updateMatrix(m); updateRegistration(); dispatchEvent(new Event(TRANSFORM_TOOL)); } /** * The location of the registration point in the tool. Note: registration * points are tool-specific. If you change the registration point of a * target, the new registration will only be reflected in the tool used * to change that point. * @see registrationEnabled * @see rememberRegistration */ public function get registration():Point { return _registration.clone(); } public function set registration(p:Point):void { _registration = p.clone(); innerRegistration = toolInvertedMatrix.transformPoint(_registration); if (_rememberRegistration) { // log new registration point for the next // time this target is selected registrationLog[_target] = innerRegistration; } dispatchEvent(new Event(TRANSFORM_TOOL)); } /** * The current control being used in the tool if being manipulated. * This value is null if the user is not transforming the tool. */ public function get currentControl():TransformToolControl { return _currentControl; } /** * Allows or disallows users to move the tool */ public function get moveEnabled():Boolean { return _moveEnabled; } public function set moveEnabled(b:Boolean):void { if (_moveEnabled != b) { _moveEnabled = b; updateControlsEnabled(); } } /** * Allows or disallows users to see and move the registration point * @see registration * @see rememberRegistration */ public function get registrationEnabled():Boolean { return _registrationEnabled; } public function set registrationEnabled(b:Boolean):void { if (_registrationEnabled != b) { _registrationEnabled = b; updateControlsEnabled(); } } /** * Allows or disallows users to see and adjust rotation controls */ public function get rotationEnabled():Boolean { return _rotationEnabled; } public function set rotationEnabled(b:Boolean):void { if (_rotationEnabled != b) { _rotationEnabled = b; updateControlsEnabled(); } } /** * Allows or disallows users to see and adjust scale controls */ public function get scaleEnabled():Boolean { return _scaleEnabled; } public function set scaleEnabled(b:Boolean):void { if (_scaleEnabled != b) { _scaleEnabled = b; updateControlsEnabled(); } } /** * Allows or disallows users to see and adjust skew controls */ public function get skewEnabled():Boolean { return _skewEnabled; } public function set skewEnabled(b:Boolean):void { if (_skewEnabled != b) { _skewEnabled = b; updateControlsEnabled(); } } /** * Allows or disallows users to see tool boundry outlines */ public function get outlineEnabled():Boolean { return _outlineEnabled; } public function set outlineEnabled(b:Boolean):void { if (_outlineEnabled != b) { _outlineEnabled = b; updateControlsEnabled(); } } /** * Allows or disallows users to see native cursors * @see addCursor * @see removeCursor * @see customCursorsEnabled */ public function get cursorsEnabled():Boolean { return _cursorsEnabled; } public function set cursorsEnabled(b:Boolean):void { if (_cursorsEnabled != b) { _cursorsEnabled = b; updateControlsEnabled(); } } /** * Allows or disallows users to see and use custom controls * @see addControl * @see removeControl * @see customCursorsEnabled */ public function get customControlsEnabled():Boolean { return _customControlsEnabled; } public function set customControlsEnabled(b:Boolean):void { if (_customControlsEnabled != b) { _customControlsEnabled = b; updateControlsEnabled(); dispatchEvent(new Event(CONTROL_PREFERENCE)); } } /** * Allows or disallows users to see custom cursors * @see addCursor * @see removeCursor * @see cursorsEnabled * @see customControlsEnabled */ public function get customCursorsEnabled():Boolean { return _customCursorsEnabled; } public function set customCursorsEnabled(b:Boolean):void { if (_customCursorsEnabled != b) { _customCursorsEnabled = b; updateControlsEnabled(); dispatchEvent(new Event(CONTROL_PREFERENCE)); } } /** * Allows or disallows users to see custom cursors * @see registration */ public function get rememberRegistration():Boolean { return _rememberRegistration; } public function set rememberRegistration(b:Boolean):void { _rememberRegistration = b; if (!_rememberRegistration) { registrationLog = new Dictionary(true); } } /** * Allows constraining of scale transformations that scale along both X and Y. * @see constrainRotation */ public function get constrainScale():Boolean { return _constrainScale; } public function set constrainScale(b:Boolean):void { if (_constrainScale != b) { _constrainScale = b; dispatchEvent(new Event(CONTROL_PREFERENCE)); } } /** * Allows constraining of rotation transformations by an angle * @see constrainRotationAngle * @see constrainScale */ public function get constrainRotation():Boolean { return _constrainRotation; } public function set constrainRotation(b:Boolean):void { if (_constrainRotation != b) { _constrainRotation = b; dispatchEvent(new Event(CONTROL_PREFERENCE)); } } /** * The angle at which rotation is constrainged when constrainRotation is true * @see constrainRotation */ public function get constrainRotationAngle():Number { return _constrainRotationAngle * 180/Math.PI; } public function set constrainRotationAngle(n:Number):void { var angleInRadians:Number = n * Math.PI/180; if (_constrainRotationAngle != angleInRadians) { _constrainRotationAngle = angleInRadians; dispatchEvent(new Event(CONTROL_PREFERENCE)); } } /** * The maximum scaleX allowed to be applied to a target */ public function get maxScaleX():Number { return _maxScaleX; } public function set maxScaleX(n:Number):void { _maxScaleX = n; } /** * The maximum scaleY allowed to be applied to a target */ public function get maxScaleY():Number { return _maxScaleY; } public function set maxScaleY(n:Number):void { _maxScaleY = n; } public function get boundsTopLeft():Point { return _boundsTopLeft.clone(); } public function get boundsTop():Point { return _boundsTop.clone(); } public function get boundsTopRight():Point { return _boundsTopRight.clone(); } public function get boundsRight():Point { return _boundsRight.clone(); } public function get boundsBottomRight():Point { return _boundsBottomRight.clone(); } public function get boundsBottom():Point { return _boundsBottom.clone(); } public function get boundsBottomLeft():Point { return _boundsBottomLeft.clone(); } public function get boundsLeft():Point { return _boundsLeft.clone(); } public function get boundsCenter():Point { return _boundsCenter.clone(); } public function get mouse():Point { return new Point(mouseX, mouseY); } public function get moveControl():TransformToolControl { return _moveControl; } public function get registrationControl():TransformToolControl { return _registrationControl; } public function get outlineControl():TransformToolControl { return _outlineControl; } public function get scaleTopLeftControl():TransformToolControl { return _scaleTopLeftControl; } public function get scaleTopControl():TransformToolControl { return _scaleTopControl; } public function get scaleTopRightControl():TransformToolControl { return _scaleTopRightControl; } public function get scaleRightControl():TransformToolControl { return _scaleRightControl; } public function get scaleBottomRightControl():TransformToolControl { return _scaleBottomRightControl; } public function get scaleBottomControl():TransformToolControl { return _scaleBottomControl; } public function get scaleBottomLeftControl():TransformToolControl { return _scaleBottomLeftControl; } public function get scaleLeftControl():TransformToolControl { return _scaleLeftControl; } public function get rotationTopLeftControl():TransformToolControl { return _rotationTopLeftControl; } public function get rotationTopRightControl():TransformToolControl { return _rotationTopRightControl; } public function get rotationBottomRightControl():TransformToolControl { return _rotationBottomRightControl; } public function get rotationBottomLeftControl():TransformToolControl { return _rotationBottomLeftControl; } public function get skewTopControl():TransformToolControl { return _skewTopControl; } public function get skewRightControl():TransformToolControl { return _skewRightControl; } public function get skewBottomControl():TransformToolControl { return _skewBottomControl; } public function get skewLeftControl():TransformToolControl { return _skewLeftControl; } public function get moveCursor():TransformToolCursor { return _moveCursor; } public function get registrationCursor():TransformToolCursor { return _registrationCursor; } public function get rotationCursor():TransformToolCursor { return _rotationCursor; } public function get scaleCursor():TransformToolCursor { return _scaleCursor; } public function get skewCursor():TransformToolCursor { return _skewCursor; } /** * TransformTool Constructor. * Creates new instances of the transform tool */ public function TransformTool() { createControls(); } /** * Provides a string representation of the transform instance */ override public function toString():String { return "[Transform Tool: target=" + String(_target) + "]" ; } // Setup private function createControls():void { // defining controls _moveControl = new TransformToolMoveShape("move", moveInteraction); _registrationControl = new TransformToolRegistrationControl(REGISTRATION, registrationInteraction, "registration"); _rotationTopLeftControl = new TransformToolRotateControl(ROTATION_TOP_LEFT, rotationInteraction, "boundsTopLeft"); _rotationTopRightControl = new TransformToolRotateControl(ROTATION_TOP_RIGHT, rotationInteraction, "boundsTopRight"); _rotationBottomRightControl = new TransformToolRotateControl(ROTATION_BOTTOM_RIGHT, rotationInteraction, "boundsBottomRight"); _rotationBottomLeftControl = new TransformToolRotateControl(ROTATION_BOTTOM_LEFT, rotationInteraction, "boundsBottomLeft"); _scaleTopLeftControl = new TransformToolScaleControl(SCALE_TOP_LEFT, scaleBothInteraction, "boundsTopLeft"); _scaleTopControl = new TransformToolScaleControl(SCALE_TOP, scaleYInteraction, "boundsTop"); _scaleTopRightControl = new TransformToolScaleControl(SCALE_TOP_RIGHT, scaleBothInteraction, "boundsTopRight"); _scaleRightControl = new TransformToolScaleControl(SCALE_RIGHT, scaleXInteraction, "boundsRight"); _scaleBottomRightControl = new TransformToolScaleControl(SCALE_BOTTOM_RIGHT, scaleBothInteraction, "boundsBottomRight"); _scaleBottomControl = new TransformToolScaleControl(SCALE_BOTTOM, scaleYInteraction, "boundsBottom"); _scaleBottomLeftControl = new TransformToolScaleControl(SCALE_BOTTOM_LEFT, scaleBothInteraction, "boundsBottomLeft"); _scaleLeftControl = new TransformToolScaleControl(SCALE_LEFT, scaleXInteraction, "boundsLeft"); _skewTopControl = new TransformToolSkewBar(SKEW_TOP, skewXInteraction, "boundsTopRight", "boundsTopLeft", "boundsTopRight"); _skewRightControl = new TransformToolSkewBar(SKEW_RIGHT, skewYInteraction, "boundsBottomRight", "boundsTopRight", "boundsBottomRight"); _skewBottomControl = new TransformToolSkewBar(SKEW_BOTTOM, skewXInteraction, "boundsBottomLeft", "boundsBottomRight", "boundsBottomLeft"); _skewLeftControl = new TransformToolSkewBar(SKEW_LEFT, skewYInteraction, "boundsTopLeft", "boundsBottomLeft", "boundsTopLeft"); // defining cursors _moveCursor = new TransformToolMoveCursor(); _moveCursor.addReference(_moveControl); _registrationCursor = new TransformToolRegistrationCursor(); _registrationCursor.addReference(_registrationControl); _rotationCursor = new TransformToolRotateCursor(); _rotationCursor.addReference(_rotationTopLeftControl); _rotationCursor.addReference(_rotationTopRightControl); _rotationCursor.addReference(_rotationBottomRightControl); _rotationCursor.addReference(_rotationBottomLeftControl); _scaleCursor = new TransformToolScaleCursor(); _scaleCursor.addReference(_scaleTopLeftControl); _scaleCursor.addReference(_scaleTopControl); _scaleCursor.addReference(_scaleTopRightControl); _scaleCursor.addReference(_scaleRightControl); _scaleCursor.addReference(_scaleBottomRightControl); _scaleCursor.addReference(_scaleBottomControl); _scaleCursor.addReference(_scaleBottomLeftControl); _scaleCursor.addReference(_scaleLeftControl); _skewCursor = new TransformToolSkewCursor(); _skewCursor.addReference(_skewTopControl); _skewCursor.addReference(_skewRightControl); _skewCursor.addReference(_skewBottomControl); _skewCursor.addReference(_skewLeftControl); // adding controls addToolControl(moveControls, _moveControl); addToolControl(registrationControls, _registrationControl); addToolControl(rotateControls, _rotationTopLeftControl); addToolControl(rotateControls, _rotationTopRightControl); addToolControl(rotateControls, _rotationBottomRightControl); addToolControl(rotateControls, _rotationBottomLeftControl); addToolControl(scaleControls, _scaleTopControl); addToolControl(scaleControls, _scaleRightControl); addToolControl(scaleControls, _scaleBottomControl); addToolControl(scaleControls, _scaleLeftControl); addToolControl(scaleControls, _scaleTopLeftControl); addToolControl(scaleControls, _scaleTopRightControl); addToolControl(scaleControls, _scaleBottomRightControl); addToolControl(scaleControls, _scaleBottomLeftControl); addToolControl(skewControls, _skewTopControl); addToolControl(skewControls, _skewRightControl); addToolControl(skewControls, _skewBottomControl); addToolControl(skewControls, _skewLeftControl); addToolControl(lines, new TransformToolOutline("outline"), false); // adding cursors addToolControl(cursors, _moveCursor, false); addToolControl(cursors, _registrationCursor, false); addToolControl(cursors, _rotationCursor, false); addToolControl(cursors, _scaleCursor, false); addToolControl(cursors, _skewCursor, false); updateControlsEnabled(); } private function addToolControl(container:Sprite, control:TransformToolControl, interactive:Boolean = true):void { control.transformTool = this; if (interactive) { control.addEventListener(MouseEvent.MOUSE_DOWN, startInteractionHandler); } container.addChild(control); control.dispatchEvent(new Event(CONTROL_INIT)); } /** * Allows you to add a custom control to the tool * @see removeControl * @see addCursor * @see removeCursor */ public function addControl(control:TransformToolControl):void { addToolControl(customControls, control); } /** * Allows you to remove a custom control to the tool * @see addControl * @see addCursor * @see removeCursor */ public function removeControl(control:TransformToolControl):TransformToolControl { if (customControls.contains(control)) { customControls.removeChild(control); return control; } return null; } /** * Allows you to add a custom cursor to the tool * @see removeCursor * @see addControl * @see removeControl */ public function addCursor(cursor:TransformToolCursor):void { addToolControl(customCursors, cursor); } /** * Allows you to remove a custom cursor to the tool * @see addCursor * @see addControl * @see removeControl */ public function removeCursor(cursor:TransformToolCursor):TransformToolCursor { if (customCursors.contains(cursor)) { customCursors.removeChild(cursor); return cursor; } return null; } /** * Allows you to change the appearance of default controls * @see addControl * @see removeControl */ public function setSkin(controlName:String, skin:DisplayObject):void { var control:TransformToolInternalControl = getControlByName(controlName); if (control) { control.skin = skin; } } /** * Allows you to get the skin of an existing control. * If one was not set, null is returned * @see addControl * @see removeControl */ public function getSkin(controlName:String):DisplayObject { var control:TransformToolInternalControl = getControlByName(controlName); return control.skin; } private function getControlByName(controlName:String):TransformToolControl { var control:TransformToolControl; var containers:Array = new Array(skewControls, registrationControls, cursors, rotateControls, scaleControls); var i:int = containers.length; while (i-- && control == null) { control = containers[i].getChildByName(controlName) as TransformToolControl; } return control; } // Interaction Handlers private function startInteractionHandler(event:MouseEvent):void { _currentControl = event.currentTarget as TransformToolControl; if (_currentControl) { setupInteraction(); } } private function setupInteraction():void { updateMatrix(); apply(); dispatchEvent(new Event(CONTROL_DOWN)); // mouse offset to allow interaction from desired point mouseOffset = (_currentControl && _currentControl.referencePoint) ? _currentControl.referencePoint.subtract(new Point(mouseX, mouseY)) : new Point(0, 0); updateMouse(); // set variables for interaction reference interactionStart = mouseLoc.clone(); innerInteractionStart = innerMouseLoc.clone(); interactionStartMatrix = _toolMatrix.clone(); interactionStartAngle = distortAngle(); if (stage) { // setup stage events to manage control interaction stage.addEventListener(MouseEvent.MOUSE_MOVE, interactionHandler); stage.addEventListener(MouseEvent.MOUSE_UP, endInteractionHandler, false); stage.addEventListener(MouseEvent.MOUSE_UP, endInteractionHandler, true); } } private function interactionHandler(event:MouseEvent):void { // define mouse position for interaction updateMouse(); // use original toolMatrix for reference of interaction _toolMatrix = interactionStartMatrix.clone(); // dispatch events that let controls do their thing dispatchEvent(new Event(CONTROL_MOVE)); dispatchEvent(new Event(CONTROL_TRANSFORM_TOOL)); if (_livePreview) { // update target if applicable apply(); } // smooth sailing event.updateAfterEvent(); } private function endInteractionHandler(event:MouseEvent):void { if (event.eventPhase == EventPhase.BUBBLING_PHASE || !(event.currentTarget is Stage)) { // ignore unrelated events received by stage return; } if (!_livePreview) { // update target if applicable apply(); } // get stage reference from event in case // stage is no longer accessible from this instance var stageRef:Stage = event.currentTarget as Stage; stageRef.removeEventListener(MouseEvent.MOUSE_MOVE, interactionHandler); stageRef.removeEventListener(MouseEvent.MOUSE_UP, endInteractionHandler, false); stageRef.removeEventListener(MouseEvent.MOUSE_UP, endInteractionHandler, true); dispatchEvent(new Event(CONTROL_UP)); _currentControl = null; } // Interaction Transformations /** * Control Interaction. Moves the tool */ public function moveInteraction():void { var moveLoc:Point = mouseLoc.subtract(interactionStart); _toolMatrix.tx += moveLoc.x; _toolMatrix.ty += moveLoc.y; updateRegistration(); completeInteraction(); } /** * Control Interaction. Moves the registration point */ public function registrationInteraction():void { // move registration point _registration.x = mouseLoc.x; _registration.y = mouseLoc.y; innerRegistration = toolInvertedMatrix.transformPoint(_registration); if (_rememberRegistration) { // log new registration point for the next // time this target is selected registrationLog[_target] = innerRegistration; } completeInteraction(); } /** * Control Interaction. Rotates the tool */ public function rotationInteraction():void { // rotate in global transform var globalMatrix:Matrix = transform.concatenatedMatrix; var globalInvertedMatrix:Matrix = globalMatrix.clone(); globalInvertedMatrix.invert(); _toolMatrix.concat(globalMatrix); // get change in rotation var angle:Number = distortAngle() - interactionStartAngle; if (_constrainRotation) { // constrain rotation based on constrainRotationAngle if (angle > Math.PI) { angle -= Math.PI*2; }else if (angle < -Math.PI) { angle += Math.PI*2; } angle = Math.round(angle/_constrainRotationAngle)*_constrainRotationAngle; } // apply rotation to toolMatrix _toolMatrix.rotate(angle); _toolMatrix.concat(globalInvertedMatrix); completeInteraction(true); } /** * Control Interaction. Scales the tool along the X axis */ public function scaleXInteraction():void { // get distortion offset vertical movement var distortH:Point = distortOffset(new Point(innerMouseLoc.x, innerInteractionStart.y), innerInteractionStart.x - innerRegistration.x); // update the matrix for vertical scale _toolMatrix.a += distortH.x; _toolMatrix.b += distortH.y; completeInteraction(true); } /** * Control Interaction. Scales the tool along the Y axis */ public function scaleYInteraction():void { // get distortion offset vertical movement var distortV:Point = distortOffset(new Point(innerInteractionStart.x, innerMouseLoc.y), innerInteractionStart.y - innerRegistration.y); // update the matrix for vertical scale _toolMatrix.c += distortV.x; _toolMatrix.d += distortV.y; completeInteraction(true); } /** * Control Interaction. Scales the tool along both the X and Y axes */ public function scaleBothInteraction():void { // mouse reference, may change from innerMouseLoc if constraining var innerMouseRef:Point = innerMouseLoc.clone(); if (_constrainScale) { // how much the mouse has moved from starting the interaction var moved:Point = innerMouseLoc.subtract(innerInteractionStart); // the relationship of the start location to the registration point var regOffset:Point = innerInteractionStart.subtract(innerRegistration); // find the ratios between movement and the registration offset var ratioH = regOffset.x ? moved.x/regOffset.x : 0; var ratioV = regOffset.y ? moved.y/regOffset.y : 0; // have the larger of the movement distances brought down // based on the lowest ratio to fit the registration offset if (ratioH > ratioV) { innerMouseRef.x = innerInteractionStart.x + regOffset.x * ratioV; }else{ innerMouseRef.y = innerInteractionStart.y + regOffset.y * ratioH; } } // get distortion offsets for both vertical and horizontal movements var distortH:Point = distortOffset(new Point(innerMouseRef.x, innerInteractionStart.y), innerInteractionStart.x - innerRegistration.x); var distortV:Point = distortOffset(new Point(innerInteractionStart.x, innerMouseRef.y), innerInteractionStart.y - innerRegistration.y); // update the matrix for both scales _toolMatrix.a += distortH.x; _toolMatrix.b += distortH.y; _toolMatrix.c += distortV.x; _toolMatrix.d += distortV.y; completeInteraction(true); } /** * Control Interaction. Skews the tool along the X axis */ public function skewXInteraction():void { var distortH:Point = distortOffset(new Point(innerMouseLoc.x, innerInteractionStart.y), innerInteractionStart.y - innerRegistration.y); _toolMatrix.c += distortH.x; _toolMatrix.d += distortH.y; completeInteraction(true); } /** * Control Interaction. Skews the tool along the Y axis */ public function skewYInteraction():void { var distortV:Point = distortOffset(new Point(innerInteractionStart.x, innerMouseLoc.y), innerInteractionStart.x - innerRegistration.x); _toolMatrix.a += distortV.x; _toolMatrix.b += distortV.y; completeInteraction(true); } private function distortOffset(offset:Point, regDiff:Number):Point { // get changes in matrix combinations based on targetBounds var ratioH:Number = regDiff ? targetBounds.width/regDiff : 0; var ratioV:Number = regDiff ? targetBounds.height/regDiff : 0; offset = interactionStartMatrix.transformPoint(offset).subtract(interactionStart); offset.x *= targetBounds.width ? ratioH/targetBounds.width : 0; offset.y *= targetBounds.height ? ratioV/targetBounds.height : 0; return offset; } private function completeInteraction(offsetReg:Boolean = false):void { enforceLimits(); if (offsetReg) { // offset of registration to have transformations based around // custom registration point var offset:Point = _registration.subtract(_toolMatrix.transformPoint(innerRegistration)); _toolMatrix.tx += offset.x; _toolMatrix.ty += offset.y; } updateBounds(); } // Information private function distortAngle():Number { // use global mouse and registration var globalMatrix:Matrix = transform.concatenatedMatrix; var gMouseLoc:Point = globalMatrix.transformPoint(mouseLoc); var gRegistration:Point = globalMatrix.transformPoint(_registration); // distance and angle of mouse from registration var offset:Point = gMouseLoc.subtract(gRegistration); return Math.atan2(offset.y, offset.x); } // Updates private function updateMouse():void { mouseLoc = new Point(mouseX, mouseY).add(mouseOffset); innerMouseLoc = toolInvertedMatrix.transformPoint(mouseLoc); } private function updateMatrix(useMatrix:Matrix = null, counterTransform:Boolean = true):void { if (_target) { _toolMatrix = useMatrix ? useMatrix.clone() : _target.transform.concatenatedMatrix.clone(); if (counterTransform) { // counter transform of the parents of the tool var current:Matrix = transform.concatenatedMatrix; current.invert(); _toolMatrix.concat(current); } enforceLimits(); toolInvertedMatrix = _toolMatrix.clone(); toolInvertedMatrix.invert(); updateBounds(); } } private function updateBounds():void { if (_target) { // update tool bounds based on target bounds targetBounds = _target.getBounds(_target); _boundsTopLeft = _toolMatrix.transformPoint(new Point(targetBounds.left, targetBounds.top)); _boundsTopRight = _toolMatrix.transformPoint(new Point(targetBounds.right, targetBounds.top)); _boundsBottomRight = _toolMatrix.transformPoint(new Point(targetBounds.right, targetBounds.bottom)); _boundsBottomLeft = _toolMatrix.transformPoint(new Point(targetBounds.left, targetBounds.bottom)); _boundsTop = Point.interpolate(_boundsTopLeft, _boundsTopRight, .5); _boundsRight = Point.interpolate(_boundsTopRight, _boundsBottomRight, .5); _boundsBottom = Point.interpolate(_boundsBottomRight, _boundsBottomLeft, .5); _boundsLeft = Point.interpolate(_boundsBottomLeft, _boundsTopLeft, .5); _boundsCenter = Point.interpolate(_boundsTopLeft, _boundsBottomRight, .5); } } private function updateControlsVisible():void { // show toolSprites only if there is a valid target var isChild:Boolean = contains(toolSprites); if (_target) { if (!isChild) { addChild(toolSprites); } }else if (isChild) { removeChild(toolSprites); } } private function updateControlsEnabled():void { // highest arrangement updateControlContainer(customCursors, _customCursorsEnabled); updateControlContainer(cursors, _cursorsEnabled); updateControlContainer(customControls, _customControlsEnabled); updateControlContainer(registrationControls, _registrationEnabled); updateControlContainer(scaleControls, _scaleEnabled); updateControlContainer(skewControls, _skewEnabled); updateControlContainer(moveControls, _moveEnabled); updateControlContainer(rotateControls, _rotationEnabled); updateControlContainer(lines, _outlineEnabled); // lowest arrangement } private function updateControlContainer(container:Sprite, enabled:Boolean):void { var isChild:Boolean = toolSprites.contains(container); if (enabled) { // add child or sent to bottom if enabled if (isChild) { toolSprites.setChildIndex(container, 0); }else{ toolSprites.addChildAt(container, 0); } }else if (isChild) { // removed if disabled toolSprites.removeChild(container); } } private function updateRegistration():void { _registration = _toolMatrix.transformPoint(innerRegistration); } private function enforceLimits():void { var currScale:Number; var angle:Number; var enforced:Boolean = false; // use global matrix var _globalMatrix:Matrix = _toolMatrix.clone(); _globalMatrix.concat(transform.concatenatedMatrix); // check current scale in X currScale = Math.sqrt(_globalMatrix.a * _globalMatrix.a + _globalMatrix.b * _globalMatrix.b); if (currScale > _maxScaleX) { // set scaleX to no greater than _maxScaleX angle = Math.atan2(_globalMatrix.b, _globalMatrix.a); _globalMatrix.a = Math.cos(angle) * _maxScaleX; _globalMatrix.b = Math.sin(angle) * _maxScaleX; enforced = true; } // check current scale in Y currScale = Math.sqrt(_globalMatrix.c * _globalMatrix.c + _globalMatrix.d * _globalMatrix.d); if (currScale > _maxScaleY) { // set scaleY to no greater than _maxScaleY angle= Math.atan2(_globalMatrix.c, _globalMatrix.d); _globalMatrix.d = Math.cos(angle) * _maxScaleY; _globalMatrix.c = Math.sin(angle) * _maxScaleY; enforced = true; } // if scale was enforced, apply to _toolMatrix if (enforced) { _toolMatrix = _globalMatrix; var current:Matrix = transform.concatenatedMatrix; current.invert(); _toolMatrix.concat(current); } } // Render private function setNewRegistation():void { if (_rememberRegistration && _target in registrationLog) { // retrieved saved reg point in log var savedReg:Point = registrationLog[_target]; innerRegistration = registrationLog[_target]; }else{ // use internal own point innerRegistration = new Point(0, 0); } updateRegistration(); } private function raiseTarget():void { // set target to last object in display list var index:int = _target.parent.numChildren - 1; _target.parent.setChildIndex(_target, index); // if this tool is in the same display list // raise it to the top above target if (_target.parent == parent) { parent.setChildIndex(this, index); } } /** * Draws the transform tool over its target instance */ public function draw():void { // update the matrix and draw controls updateMatrix(); dispatchEvent(new Event(TRANSFORM_TOOL)); } /** * Applies the current tool transformation to its target instance */ public function apply():void { if (_target) { // get matrix to apply to target var applyMatrix:Matrix = _toolMatrix.clone(); applyMatrix.concat(transform.concatenatedMatrix); // if target has a parent, counter parent transformations if (_target.parent) { var invertMatrix:Matrix = target.parent.transform.concatenatedMatrix; invertMatrix.invert(); applyMatrix.concat(invertMatrix); } // set target's matrix _target.transform.matrix = applyMatrix; dispatchEvent(new Event(TRANSFORM_TARGET)); } } } } import flash.display.DisplayObject; import flash.display.Shape; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Matrix; import flash.geom.Point; import com.senocular.display.TransformTool; import com.senocular.display.TransformToolControl; import com.senocular.display.TransformToolCursor; // Controls class TransformToolInternalControl extends TransformToolControl { public var interactionMethod:Function; public var referenceName:String; public var _skin:DisplayObject; public function set skin(skin:DisplayObject):void { if (_skin && contains(_skin)) { removeChild(_skin); } _skin = skin; if (_skin) { addChild(_skin); } draw(); } public function get skin():DisplayObject { return _skin; } override public function get referencePoint():Point { if (referenceName in _transformTool) { return _transformTool[referenceName]; } return null; } /* * Constructor */ public function TransformToolInternalControl(name:String, interactionMethod:Function = null, referenceName:String = null) { this.name = name; this.interactionMethod = interactionMethod; this.referenceName = referenceName; addEventListener(TransformTool.CONTROL_INIT, init); } protected function init(event:Event):void { _transformTool.addEventListener(TransformTool.NEW_TARGET, draw); _transformTool.addEventListener(TransformTool.TRANSFORM_TOOL, draw); _transformTool.addEventListener(TransformTool.CONTROL_TRANSFORM_TOOL, position); _transformTool.addEventListener(TransformTool.CONTROL_PREFERENCE, draw); _transformTool.addEventListener(TransformTool.CONTROL_MOVE, controlMove); draw(); } public function draw(event:Event = null):void { if (_transformTool.maintainControlForm) { counterTransform(); } position(); } public function position(event:Event = null):void { var reference:Point = referencePoint; if (reference) { x = reference.x; y = reference.y; } } private function controlMove(event:Event):void { if (interactionMethod && _transformTool.currentControl == this) { interactionMethod(); } } } class TransformToolMoveShape extends TransformToolInternalControl { private var lastTarget:DisplayObject; function TransformToolMoveShape(name:String, interactionMethod:Function) { super(name, interactionMethod); } override public function draw(event:Event = null):void { var currTarget:DisplayObject; var moveUnderObjects:Boolean = _transformTool.moveUnderObjects; // use hitArea if moving under objects // then movement would have the same depth as the tool if (moveUnderObjects) { hitArea = _transformTool.target; currTarget = null; relatedObject = this; }else{ // when not moving under objects // use the tool target to handle movement allowing // objects above it to be selectable hitArea = null; currTarget = _transformTool.target; relatedObject = currTarget; } if (lastTarget != currTarget) { // set up/remove listeners for target being clicked if (lastTarget) { lastTarget.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDown, false); } if (currTarget) { currTarget.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown, false, 0, true); } // register/unregister cursor with the object var cursor:TransformToolCursor = _transformTool.moveCursor; cursor.removeReference(lastTarget); cursor.addReference(currTarget); lastTarget = currTarget; } } private function mouseDown(event:MouseEvent):void { dispatchEvent(new MouseEvent(MouseEvent.MOUSE_DOWN)); } } class TransformToolRegistrationControl extends TransformToolInternalControl { function TransformToolRegistrationControl(name:String, interactionMethod:Function, referenceName:String) { super(name, interactionMethod, referenceName); } override public function draw(event:Event = null):void { graphics.clear(); if (!_skin) { graphics.lineStyle(1, 0); graphics.beginFill(0xFFFFFF); graphics.drawCircle(0, 0, _transformTool.controlSize/2); graphics.endFill(); } super.draw(); } } class TransformToolScaleControl extends TransformToolInternalControl { function TransformToolScaleControl(name:String, interactionMethod:Function, referenceName:String) { super(name, interactionMethod, referenceName); } override public function draw(event:Event = null):void { graphics.clear(); if (!_skin) { graphics.lineStyle(2, 0xFFFFFF); graphics.beginFill(0); var size = _transformTool.controlSize; var size2:Number = size/2; graphics.drawRect(-size2, -size2, size, size); graphics.endFill(); } super.draw(); } } class TransformToolRotateControl extends TransformToolInternalControl { private var locationName:String; function TransformToolRotateControl(name:String, interactionMethod:Function, locationName:String) { super(name, interactionMethod); this.locationName = locationName; } override public function draw(event:Event = null):void { graphics.clear(); if (!_skin) { graphics.beginFill(0xFF, 0); graphics.drawCircle(0, 0, _transformTool.controlSize*2); graphics.endFill(); } super.draw(); } override public function position(event:Event = null):void { if (locationName in _transformTool) { var location:Point = _transformTool[locationName]; x = location.x; y = location.y; } } } class TransformToolSkewBar extends TransformToolInternalControl { private var locationStart:String; private var locationEnd:String; function TransformToolSkewBar(name:String, interactionMethod:Function, referenceName:String, locationStart:String, locationEnd:String) { super(name, interactionMethod, referenceName); this.locationStart = locationStart; this.locationEnd = locationEnd; } override public function draw(event:Event = null):void { graphics.clear(); if (_skin) { super.draw(event); return; } // derive point locations for bar var locStart:Point = _transformTool[locationStart]; var locEnd:Point = _transformTool[locationEnd]; // counter transform var toolTrans:Matrix; var toolTransInverted:Matrix; var maintainControlForm:Boolean = _transformTool.maintainControlForm; if (maintainControlForm) { toolTrans = transform.concatenatedMatrix; toolTransInverted = toolTrans.clone(); toolTransInverted.invert(); locStart = toolTrans.transformPoint(locStart); locEnd = toolTrans.transformPoint(locEnd); } var size:Number = _transformTool.controlSize/2; var diff:Point = locEnd.subtract(locStart); var angle:Number = Math.atan2(diff.y, diff.x) - Math.PI/2; var offset:Point = Point.polar(size, angle); var corner1:Point = locStart.add(offset); var corner2:Point = locEnd.add(offset); var corner3:Point = locEnd.subtract(offset); var corner4:Point = locStart.subtract(offset); if (maintainControlForm) { corner1 = toolTransInverted.transformPoint(corner1); corner2 = toolTransInverted.transformPoint(corner2); corner3 = toolTransInverted.transformPoint(corner3); corner4 = toolTransInverted.transformPoint(corner4); } // draw bar graphics.beginFill(0xFF0000, 0); graphics.moveTo(corner1.x, corner1.y); graphics.lineTo(corner2.x, corner2.y); graphics.lineTo(corner3.x, corner3.y); graphics.lineTo(corner4.x, corner4.y); graphics.lineTo(corner1.x, corner1.y); graphics.endFill(); } override public function position(event:Event = null):void { if (_skin) { var locStart:Point = _transformTool[locationStart]; var locEnd:Point = _transformTool[locationEnd]; var location:Point = Point.interpolate(locStart, locEnd, .5); x = location.x; y = location.y; }else{ x = 0; y = 0; draw(event); } } } class TransformToolOutline extends TransformToolInternalControl { function TransformToolOutline(name:String) { super(name); } override public function draw(event:Event = null):void { var topLeft:Point = _transformTool.boundsTopLeft; var topRight:Point = _transformTool.boundsTopRight; var bottomRight:Point = _transformTool.boundsBottomRight; var bottomLeft:Point = _transformTool.boundsBottomLeft; graphics.clear(); graphics.lineStyle(0, 0); graphics.moveTo(topLeft.x, topLeft.y); graphics.lineTo(topRight.x, topRight.y); graphics.lineTo(bottomRight.x, bottomRight.y); graphics.lineTo(bottomLeft.x, bottomLeft.y); graphics.lineTo(topLeft.x, topLeft.y); } override public function position(event:Event = null):void { draw(event); } } // Cursors class TransformToolInternalCursor extends TransformToolCursor { public var offset:Point = new Point(); public var icon:Shape = new Shape(); public function TransformToolInternalCursor() { addChild(icon); offset = _mouseOffset; addEventListener(TransformTool.CONTROL_INIT, init); } private function init(event:Event):void { _transformTool.addEventListener(TransformTool.NEW_TARGET, maintainTransform); _transformTool.addEventListener(TransformTool.CONTROL_PREFERENCE, maintainTransform); draw(); } protected function maintainTransform(event:Event):void { offset = _mouseOffset; if (_transformTool.maintainControlForm) { transform.matrix = new Matrix(); var concatMatrix:Matrix = transform.concatenatedMatrix; concatMatrix.invert(); transform.matrix = concatMatrix; offset = concatMatrix.deltaTransformPoint(offset); } updateVisible(event); } protected function drawArc(originX:Number, originY:Number, radius:Number, angle1:Number, angle2:Number, useMove:Boolean = true):void { var diff:Number = angle2 - angle1; var divs:Number = 1 + Math.floor(Math.abs(diff)/(Math.PI/4)); var span:Number = diff/(2*divs); var cosSpan:Number = Math.cos(span); var radiusc:Number = cosSpan ? radius/cosSpan : 0; var i:int; if (useMove) { icon.graphics.moveTo(originX + Math.cos(angle1)*radius, originY - Math.sin(angle1)*radius); }else{ icon.graphics.lineTo(originX + Math.cos(angle1)*radius, originY - Math.sin(angle1)*radius); } for (i=0; i