PageRenderTime 55ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/nickro/greensock/transform/TransformManager.as

http://my-project-nickro.googlecode.com/
ActionScript | 1003 lines | 641 code | 66 blank | 296 comment | 169 complexity | 1de608d6c40a2db7daefe7bd497d6cc3 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. /**
  2. * VERSION: 1.887
  3. * DATE: 2010-04-27
  4. * AS3
  5. * UPDATES AND DOCUMENTATION AT: http://www.greensock.com/transformmanageras3/
  6. **/
  7. package org.nickro.greensock.transform {
  8. import org.nickro.greensock.events.TransformEvent;
  9. import org.nickro.greensock.transform.utils.MatrixTools;
  10. import flash.display.DisplayObject;
  11. import flash.display.DisplayObjectContainer;
  12. import flash.display.Graphics;
  13. import flash.display.Shape;
  14. import flash.display.Sprite;
  15. import flash.display.Stage;
  16. import flash.events.Event;
  17. import flash.events.EventDispatcher;
  18. import flash.events.KeyboardEvent;
  19. import flash.events.MouseEvent;
  20. import flash.geom.Matrix;
  21. import flash.geom.Point;
  22. import flash.geom.Rectangle;
  23. import flash.text.TextField;
  24. import flash.ui.Keyboard;
  25. import flash.ui.Mouse;
  26. import flash.utils.Dictionary;
  27. import flash.utils.getDefinitionByName;
  28. import flash.utils.getTimer;
  29. /**
  30. * TransformManager makes it easy to add interactive scaling/rotating/moving of DisplayObjects to your Flash
  31. * or Flex application. It uses an intuitive interface that's similar to most modern drawing applications.
  32. * When the user clicks on a managed DisplayObject, a selection box will be drawn around it along with 8 handles
  33. * for scaling/rotating. When the mouse is placed just outside of any of the scaling handles, the cursor will
  34. * change to indicate that they're in rotation mode. Just like most other applications, the user can hold down
  35. * the SHIFT key to select multiple items, to constrain scaling proportions, or to limit the rotation to
  36. * 45 degree increments.<br /><br />
  37. *
  38. * <b>FEATURES INCLUDE:</b><br />
  39. * <ul>
  40. * <li> Select multiple items and scale/rotate/move them all simultaneously. </li>
  41. * <li> Includes a <code>FlexTransformManager</code> class that makes it simple to integrate into Flex applications.</li>
  42. * <li> Perform virtually any action (transformations, selections, etc.) through code.</li>
  43. * <li> Depth management which allows you to programmatically push the selected items forward or backward in the stacking order</li>
  44. * <li> Set minScaleX, maxScaleX, minScaleY, and maxScaleY properties on each item </li>
  45. * <li> Arrow keys move the selection </li>
  46. * <li> You can set the scaleMode of any TransformItem to SCALE_WIDTH_AND_HEIGHT so that the width/height properties are altered instead of scaleX/scaleY. This can be helpful for text-related components because altering the width/height changes only the container's dimensions while retaining the text's size.</li>
  47. * <li> There is a 10-pixel wide draggable edge around around the border that users can drag. This is particularly helpful with TextFields/TextAreas.</li>
  48. * <li> Define bounds within which the DisplayObjects must stay, and TransformManager will not let the user scale/rotate/move them beyond those bounds</li>
  49. * <li> Automatically bring the selected item(s) to the front in the stacking order </li>
  50. * <li> The DELETE and BACKSPACE keys can be used to delete the selected DisplayObjects </li>
  51. * <li> Lock certain kinds of transformations like rotation, scale, and/or movement </li>
  52. * <li> Lock the proportions of the DisplayObjects so that users cannot distort them when scaling</li>
  53. * <li> Scale from the DisplayObject's center or from its corners</li>
  54. * <li> Listen for Events like SCALE, MOVE, ROTATE, SELECTION_CHANGE, DEPTH_CHANGE, CLICK_OFF, FINISH_INTERACTIVE_MOVE, FINISH_INTERACTIVE_SCALE, FINISH_INTERACTIVE_ROTATE, DOUBLE_CLICK, and DESTROY </li>
  55. * <li> Set the selection box line color and handle thickness</li>
  56. * <li> Cursor will automatically change to indicate scale or rotation mode</li>
  57. * <li> Optionally hide the center handle</li>
  58. * <li> Export transformational data for each item's scale, rotation, and position as well as the TranformManager's settings in XML format so that you can easily save it to a database (or wherever). Then apply it anytime to revert objects to a particular state.</li>
  59. * <li> VERY easy to use. In fact, all it takes is one line of code to get it up and running with the default settings. </li>
  60. * </ul>
  61. *
  62. * <b>NOTES / LIMITATIONS</b>:<br />
  63. * <ul>
  64. * <li> All DisplayObjects that are managed by a particular TransformManager instance must have the same parent (you can create multiple TransformManager instances if you want)</li>
  65. * <li> TextFields cannot be flipped (have negative scales).</li>
  66. * <li> TextFields cannot be skewed. Therefore, when a TextField is part of a multi-selection, scaling will be disabled because it could skew the TextField (imagine if a TextField is at a 45 degree angle, and then you selected another item and scaled vertically - your TextField would end up getting skewed).</li>
  67. * <li> Due to several bugs in the Flex framework (acknowledged by Adobe), TransformManager doesn't work quite as expected inside Flex containers, but I created a FlexTransformManager class that helps avoid the limitations. However, you still cannot scale TextFields disproportionately.</li>
  68. * <li> Due to a limitation in the way Flash reports bounds, items that are extremely close or exactly on top of a boundary (if you define bounds) will be moved about 0.1 pixel away from the boundary when you select them. If an item fills the width and/or height of the bounds, it will be scaled down very slightly (about 0.2 pixels total) to move it away from the bounds and allow accurate collision detection.</li>
  69. * </ul>
  70. *
  71. * <b>USAGE</b><br /><br />
  72. *
  73. * The first (and only) parameter in the constructor should be an object with any number of properties.
  74. * This makes it easier to set only the properties that shouldn't use their default values (you'll
  75. * probably find that most of the time the default values work well for you). It also makes the code
  76. * easier to read. The properties can be in any order, like so:<br /><br /><code>
  77. *
  78. * var manager:TransformManager = new TransformManager({targetObjects:[mc1, mc2], forceSelectionToFront:true, bounds:new Rectangle(0, 0, 550, 450), allowDelete:true});<br /><br /></code>
  79. *
  80. * All TransformEvents have an "items" property which is an Array populated by the affected TransformItem instances. TransformEvents also have a "mouseEvent" property that will be populated if there was an associted MouseEvent (like CLICK_OFF) <br /><br />
  81. *
  82. * <b>EXAMPLES:</b><br /><br />
  83. *
  84. * To make two MovieClips (mc1 and mc2) transformable using the default settings:<br /><br /><code>
  85. *
  86. * import org.nickro.greensock.transform.TransformManager; <br /><br />
  87. *
  88. * var manager:TransformManager = new TransformManager({targetObjects:[mc1, mc2]});<br /><br /></code>
  89. *
  90. * To make the two MovieClips transformable, constrain their scaling to be proportional (even if the user is not holding
  91. * down the shift key), call the onScale function everytime one of the objects is scaled, lock the rotation value of each
  92. * MovieClip (preventing rotation), and allow the delete key to appear to delete the selected MovieClip from the stage:<br /><br /><code>
  93. *
  94. * import org.nickro.greensock.transform.TransformManager;<br />
  95. * import org.nickro.greensock.events.TransformEvent;<br /><br />
  96. *
  97. * var manager:TransformManager = new TransformManager({targetObjects:[mc1, mc2], constrainScale:true, lockRotation:true, allowDelete:true, autoDeselect:true});<br />
  98. * manager.addEventListener(TransformEvent.SCALE, onScale);<br />
  99. * function onScale($e:TransformEvent):void {<br />
  100. * trace("Scaled " + $e.items.length + " items");<br />
  101. * }<br /><br /></code>
  102. *
  103. * To add mc1 and mc2 and myText after a TransformManager has been created, and then listen for when only mc1 is selected:<br /><br /><code>
  104. *
  105. * import org.nickro.greensock.transform.TransformManager;<br />
  106. * import org.nickro.greensock.events.TransformEvent;<br /><br />
  107. *
  108. * var manager:TransformManager = new TransformManager();<br /><br />
  109. *
  110. * var mc1Item:TransformItem = manager.addItem(mc1);<br />
  111. * var mc2Item:TransformItem = manager.addItem(mc2);<br />
  112. * var myTextItem:TransformItem = manager.addItem(myText, TransformManager.SCALE_WIDTH_AND_HEIGHT, true);<br /><br />
  113. *
  114. * mc1Item.addEventListener(TransformEvent.SELECT, onSelectClip1);<br /><br />
  115. *
  116. * function onSelectClip1($e:TransformEvent):void {<br />
  117. * trace("selected mc1");<br />
  118. * }<br /><br /></code>
  119. *
  120. *
  121. * <b>Copyright 2010, GreenSock. All rights reserved.</b> This work is subject to the terms in <a href="http://www.greensock.com/eula.html">http://www.greensock.com/eula.html</a> or for corporate Club GreenSock members, the software agreement that was issued with the corporate membership.
  122. *
  123. * @author Jack Doyle, jack@greensock.com
  124. */
  125. public class TransformManager extends EventDispatcher {
  126. public static const VERSION:Number = 1.887;
  127. public static const SCALE_NORMAL:String = "scaleNormal";
  128. public static const SCALE_WIDTH_AND_HEIGHT:String = "scaleWidthAndHeight";
  129. /** @private precomputation for speed **/
  130. private static const _DEG2RAD:Number = Math.PI / 180;
  131. /** @private precomputation for speed **/
  132. private static const _RAD2DEG:Number = 180 / Math.PI;
  133. /** @private can be scaleCursor or rotationCursor **/
  134. private static var _currentCursor:Shape;
  135. /** @private the TransformManager instance that called swapCursorIn() most recently (we need to track this for cases where multiple instances exist - it's possible for a swapCursorOut() to be called by the one releasing control after the swapCursorIn() is called by the one gaining control. **/
  136. private static var _cursorManager:TransformManager;
  137. /** @private stores key codes of pressed keys **/
  138. private static var _keysDown:Object;
  139. /** @private a Dictionary that gives us a way to look up the stages that have had listeners added to them (to accommodate multi-window AIR apps) **/
  140. private static var _keyListenerInits:Dictionary = new Dictionary();
  141. /** @private **/
  142. private static var _keyDispatcher:EventDispatcher = new EventDispatcher();
  143. /** @private **/
  144. private static var _tempDeselectedItem:TransformItem;
  145. /** @private **/
  146. public static var scaleCursor:Shape;
  147. /** @private **/
  148. public static var rotationCursor:Shape;
  149. /** @private **/
  150. public static var moveCursor:Shape;
  151. /** @private If true, we'll delete a TransformItem's MovieClip when it's selected and the user hits the Delete key. **/
  152. private var _allowDelete:Boolean;
  153. /** @private **/
  154. private var _allowMultiSelect:Boolean;
  155. /** @private **/
  156. private var _hideCenterHandle:Boolean;
  157. /** @private if true, whenever you select an item, it will be ADDED to the selection instead of replacing it.**/
  158. private var _multiSelectMode:Boolean;
  159. /** @private used to prevent unnecessary actions when this TransformManager is causing TransformItem events to get dispatched, like when selecting/deselecting multiple items or rotating/scaling/moving **/
  160. private var _ignoreEvents:Boolean;
  161. /** @private If true (and it's true by default), TransformItems will be deselected when the user clicks off of them. Disabling this is sometimes necessary in cases where you want the user to be able to select a MovieClip and then select/edit separate form fields without deselecting the MovieClip. In that case, you'll need to handle things in a custom way through your _eventHandler (look for action_str == "deselect" which will still get fired when the user clicks off of it)**/
  162. private var _autoDeselect:Boolean;
  163. /** @private If true, only proportional scaling is allowed (even if the SHIFT key isn't held down).**/
  164. private var _constrainScale:Boolean;
  165. /** @private **/
  166. private var _lockScale:Boolean;
  167. /** @private **/
  168. private var _scaleFromCenter:Boolean;
  169. /** @private **/
  170. private var _lockRotation:Boolean;
  171. /** @private **/
  172. private var _lockPosition:Boolean;
  173. /** @private **/
  174. private var _arrowKeysMove:Boolean;
  175. /** @private reflects info about the selection. If any selected items have contrainScale set to true, this will be true.**/
  176. private var _selConstrainScale:Boolean;
  177. /** @private reflects info about the selection. If any selected items have lockScale set to true, this will be true.**/
  178. private var _selLockScale:Boolean;
  179. /** @private reflects info about the selection. If any selected items have loclRotation set to true, this will be true.**/
  180. private var _selLockRotation:Boolean;
  181. /** @private reflects info about the selection. If any selected items have lockPosition set to true, this will be true.**/
  182. private var _selLockPosition:Boolean;
  183. /** @private **/
  184. private var _selHasTextFields:Boolean;
  185. /** @private if any of the items in the selection have scale limits (minScaleX, maxScaleX, minScaleY, maxScaleY), this should be true.**/
  186. private var _selHasScaleLimits:Boolean;
  187. /** @private **/
  188. private var _lineColor:uint;
  189. /** @private **/
  190. private var _handleColor:uint;
  191. /** @private **/
  192. private var _handleSize:Number;
  193. /** @private **/
  194. private var _paddingForRotation:Number;
  195. /** @private **/
  196. private var _selectedItems:Array;
  197. /** @private **/
  198. private var _forceSelectionToFront:Boolean;
  199. /** @private Holds references to all TransformItems that this TransformManager can control (use addItem() to add DisplayObjects)**/
  200. private var _items:Array;
  201. /** @private ignore clicks on the DisplayObjects in this list (like form elements, etc.)**/
  202. private var _ignoredObjects:Array;
  203. /** @private Set this value to false if you want to disable all TransformItems. Setting it to true will enable them all.**/
  204. private var _enabled:Boolean;
  205. /** @private Defines an area that the items are restrained to (according to their parent coordinate system)**/
  206. private var _bounds:Rectangle;
  207. /** @private **/
  208. private var _selection:Sprite;
  209. /** @private Invisible - mirrors the transformations of the overall selection. We use this to pin the handles in the right spots.**/
  210. private var _dummyBox:Sprite;
  211. /** @private **/
  212. private var _handles:Array;
  213. /** @private To make lookups faster on cursor rollovers. **/
  214. private var _handlesDict:Dictionary;
  215. /** @private the parent of the items (they all must share the same parent!)**/
  216. private var _parent:DisplayObjectContainer;
  217. /** @private **/
  218. private var _stage:Stage;
  219. /** @private acts like a registration point for transformations**/
  220. private var _origin:Point;
  221. /** @private stores various data about the selection during transformations**/
  222. private var _trackingInfo:Object;
  223. /** @private Only true after at least one item is added (which gives us a way to get to the stage and set up listeners)**/
  224. private var _initted:Boolean;
  225. /** @private **/
  226. private var _isFlex:Boolean;
  227. /** @private **/
  228. private var _edges:Sprite;
  229. /** @private **/
  230. private var _lockCursor:Boolean;
  231. /** @private **/
  232. private var _onUnlock:Function;
  233. /** @private **/
  234. private var _onUnlockParam:Event;
  235. /** @private **/
  236. private var _dispatchScaleEvents:Boolean;
  237. /** @private **/
  238. private var _dispatchMoveEvents:Boolean;
  239. /** @private **/
  240. private var _dispatchRotateEvents:Boolean;
  241. /** @private while an interactive scale/move/rotation is in progress, this is true.**/
  242. private var _isTransforming:Boolean;
  243. /** @private **/
  244. private var _prevScaleX:Number = 0;
  245. /** @private **/
  246. private var _prevScaleY:Number = 0;
  247. /** @private **/
  248. private var _lockOrigin:Boolean;
  249. /** @private **/
  250. private var _lastClickTime:uint = 0;
  251. /**
  252. * Constructor
  253. *
  254. * @param $vars An object specifying any properties that should be set upon instantiation, like <code>{items:[mc1, mc2], lockRotation:true, bounds:new Rectangle(0, 0, 500, 300)}</code>.
  255. */
  256. public function TransformManager($vars:Object = null) {
  257. if (TransformItem.VERSION < 1.87) {
  258. throw new Error("TransformManager Error: You have an outdated TransformManager-related class file. You may need to clear your ASO files. Please make sure you're using the latest version of TransformManager and TransformItem, available from www.greensock.com.");
  259. }
  260. if ($vars == null) {
  261. $vars = {};
  262. }
  263. init($vars);
  264. }
  265. /** @private **/
  266. protected function init($vars:Object):void {
  267. _allowDelete = setDefault($vars.allowDelete, false);
  268. _allowMultiSelect = setDefault($vars.allowMultiSelect, true);
  269. _autoDeselect = setDefault($vars.autoDeselect, true);
  270. _constrainScale = setDefault($vars.constrainScale, false);
  271. _lockScale = setDefault($vars.lockScale, false);
  272. _scaleFromCenter = setDefault($vars.scaleFromCenter, false);
  273. _lockRotation = setDefault($vars.lockRotation, false);
  274. _lockPosition = setDefault($vars.lockPosition, false);
  275. _arrowKeysMove = setDefault($vars.arrowKeysMove, false);
  276. _forceSelectionToFront = setDefault($vars.forceSelectionToFront, true);
  277. _lineColor = setDefault($vars.lineColor, 0x3399FF); //Line color (including handles and selection around MovieClip)
  278. _handleColor = setDefault($vars.handleFillColor, 0xFFFFFF); //Handle fill color
  279. _handleSize = setDefault($vars.handleSize, 8); //Number of pixels the handles should be (square)
  280. _paddingForRotation = setDefault($vars.paddingForRotation, 12); //Number of pixels beyond the handles that should be sensitive for rotating.
  281. _hideCenterHandle = setDefault($vars.hideCenterHandle, false);
  282. _multiSelectMode = _ignoreEvents = false;
  283. _bounds = $vars.bounds;
  284. _enabled = true;
  285. _keyDispatcher.addEventListener("pressDelete", onPressDelete, false, 0, true);
  286. _keyDispatcher.addEventListener("pressArrowKey", onPressArrowKey, false, 0, true);
  287. _keyDispatcher.addEventListener("pressMultiSelectKey", onPressMultiSelectKey, false, 0, true);
  288. _keyDispatcher.addEventListener("releaseMultiSelectKey", onReleaseMultiSelectKey, false, 0, true);
  289. _items = $vars.items || [];
  290. _selectedItems = [];
  291. this.ignoredObjects = $vars.ignoredObjects || [];
  292. _handles = [];
  293. _handlesDict = new Dictionary();
  294. if ($vars.targetObjects != undefined) {
  295. addItems($vars.targetObjects);
  296. }
  297. }
  298. /** @private **/
  299. protected function initParent($parent:DisplayObjectContainer):void {
  300. if (!_initted && _parent == null) {
  301. try {
  302. _isFlex = Boolean(getDefinitionByName("mx.managers.SystemManager")); // SystemManager is the first display class created within a Flex application
  303. } catch ($e:Error) {
  304. _isFlex = false;
  305. }
  306. _parent = $parent;
  307. for (var i:int = _items.length - 1; i > -1; i--) {
  308. _items[i].targetObject.removeEventListener(Event.ADDED_TO_STAGE, onTargetAddedToStage);
  309. }
  310. if (_parent.stage == null) {
  311. _parent.addEventListener(Event.ADDED_TO_STAGE, initStage, false, 0, true); //Sometimes in Flex, the parent hasn't been added to the stage yet, so we need to wait so that we can access the stage.
  312. } else {
  313. initStage();
  314. }
  315. }
  316. }
  317. /** @private **/
  318. protected function onTargetAddedToStage($e:Event):void {
  319. initParent($e.target.parent);
  320. }
  321. /** @private **/
  322. protected function initStage($e:Event=null):void {
  323. _parent.removeEventListener(Event.ADDED_TO_STAGE, initStage);
  324. _stage = _parent.stage;
  325. initKeyListeners(_stage);
  326. _stage.addEventListener(MouseEvent.MOUSE_DOWN, checkForDeselect, false, 0, true);
  327. _stage.addEventListener(Event.DEACTIVATE, onReleaseMultiSelectKey, false, 0, true); //otherwise, if the user has the SHIFT key down and they click on another window and then release the key and then click back on this window, it could get stuck in multiselect mode.
  328. initSelection();
  329. initScaleCursor();
  330. initMoveCursor();
  331. initRotationCursor();
  332. _initted = true;
  333. if (_selectedItems.length != 0) {
  334. if (_forceSelectionToFront) {
  335. for (var i:int = _selectedItems.length - 1; i > -1; i--) {
  336. bringToFront(_selectedItems[i].targetObject);
  337. }
  338. }
  339. calibrateConstraints();
  340. updateSelection();
  341. }
  342. }
  343. /**
  344. * In order for a DisplayObject to be managed by TransformManger, it must first be added via <code>addItem()</code>. When the
  345. * DisplayObject is added, a TransformItem instance is automatically created and associated with the DisplayObject.
  346. * If you need to set item-specific settings like minScaleX, maxScaleX, etc., you would set those via the TransformItem
  347. * instance. <code>addItem()</code> returns a TransformItem instance, but you can retrieve it anytime with the <code>getItem()</code>
  348. * method (just pass it your DisplayObject, like <code>var myItem:TransformItem = myManager.getItem(myDisplayObject)</code>.<br /><br />
  349. *
  350. * NOTE: If your targetObject isn't an InteractiveObject, it will not receive MouseEvents like clicks and rollovers that trigger
  351. * selection, dragging, cursor changes, etc. so user interaction-based actions won't be possible. That may be perfectly
  352. * acceptable, though, if you're going to do transformations directly via code.
  353. *
  354. * @param $targetObject The DisplayObject to be managed
  355. * @param $scaleMode Either <code>TransformManager.SCALE_NORMAL</code> for normal scaleX/scaleY scaling or <code>TransformManager.SCALE_WIDTH_AND_HEIGHT</code> if you prefer that TransformManager alters the <code>width</code>/<code>height</code> properties instead.
  356. * @param $hasSelectableText If true, this prevents dragging of the object unless clicking on the edges/border or center handle, and allows the DELETE key to be pressed without deleting the object itself. It will also force the scaleMode to <code>TransformManager.SCALE_WIDTH_AND_HEIGHT</code>.
  357. * @return TransformItem instance
  358. */
  359. public function addItem($targetObject:DisplayObject, $scaleMode:String="scaleNormal", $hasSelectableText:Boolean=false):TransformItem {
  360. if ($targetObject == _dummyBox || $targetObject == _selection) {
  361. return null;
  362. }
  363. var props:Array = ["constrainScale", "scaleFromCenter", "lockScale", "lockRotation", "lockPosition", "autoDeselect", "allowDelete", "bounds", "enabled", "forceSelectionToFront"];
  364. var newVars:Object = {manager:this};
  365. for (var i:uint = 0; i < props.length; i++) {
  366. newVars[props[i]] = this[props[i]];
  367. }
  368. var existingItem:TransformItem = getItem($targetObject); //Just in case it's already in the _items Array
  369. if (existingItem != null) {
  370. existingItem.update(null);
  371. return existingItem;
  372. }
  373. newVars.scaleMode = ($targetObject is TextField) ? SCALE_WIDTH_AND_HEIGHT : $scaleMode;
  374. newVars.hasSelectableText = ($targetObject is TextField) ? true : $hasSelectableText;
  375. var newItem:TransformItem = newItem = new TransformItem($targetObject, newVars);
  376. newItem.addEventListener(TransformEvent.SELECT, onSelectItem);
  377. newItem.addEventListener(TransformEvent.DESELECT, onDeselectItem);
  378. newItem.addEventListener(TransformEvent.MOUSE_DOWN, onMouseDownItem);
  379. newItem.addEventListener(TransformEvent.SELECT_MOUSE_DOWN, onPressMove);
  380. newItem.addEventListener(TransformEvent.SELECT_MOUSE_UP, onReleaseMove);
  381. newItem.addEventListener(TransformEvent.UPDATE, onUpdateItem);
  382. newItem.addEventListener(TransformEvent.SCALE, onUpdateItem);
  383. newItem.addEventListener(TransformEvent.ROTATE, onUpdateItem);
  384. newItem.addEventListener(TransformEvent.MOVE, onUpdateItem);
  385. newItem.addEventListener(TransformEvent.ROLL_OVER_SELECTED, onRollOverSelectedItem);
  386. newItem.addEventListener(TransformEvent.ROLL_OUT_SELECTED, onRollOutSelectedItem);
  387. newItem.addEventListener(TransformEvent.DESTROY, onDestroyItem);
  388. _items.push(newItem);
  389. if (!_initted) {
  390. if ($targetObject.parent == null) {
  391. $targetObject.addEventListener(Event.ADDED_TO_STAGE, onTargetAddedToStage, false, 0, true);
  392. } else {
  393. initParent($targetObject.parent);
  394. }
  395. }
  396. return newItem;
  397. }
  398. /**
  399. * Same as addItem() but accepts an Array containing multiple DisplayObjects.
  400. *
  401. * @param $targetObjects An Array of DisplayObject to be managed
  402. * @param $scaleMode Either <code>TransformManager.SCALE_NORMAL</code> for normal scaleX/scaleY scaling or <code>TransformManager.SCALE_WIDTH_AND_HEIGHT</code> if you prefer that TransformManager alters the <code>width</code>/<code>height</code> properties instead.
  403. * @param $hasSelectableText If true, this prevents dragging of the objects unless clicking on the edges/border or center handle, and allows the DELETE key to be pressed without deleting the object itself. It will also force the scaleMode to <code>TransformManager.SCALE_WIDTH_AND_HEIGHT</code>.
  404. * @return An Array of corresponding TransformItems that are created
  405. */
  406. public function addItems($targetObjects:Array, $scaleMode:String="scaleNormal", $hasSelectableText:Boolean=false):Array {
  407. var a:Array = [];
  408. for (var i:uint = 0; i < $targetObjects.length; i++) {
  409. a.push(addItem($targetObjects[i], $scaleMode, $hasSelectableText));
  410. }
  411. return a;
  412. }
  413. /**
  414. * Removes an item. Calling this on an item will NOT delete the DisplayObject - it just prevents it from being affected by this TransformManager anymore.
  415. *
  416. * @param $item Either the DisplayObject or the associated TransformItem that should be removed
  417. */
  418. public function removeItem($item:*):void {
  419. var item:TransformItem = findObject($item);
  420. if (item != null) {
  421. item.selected = false;
  422. item.removeEventListener(TransformEvent.SELECT, onSelectItem);
  423. item.removeEventListener(TransformEvent.DESELECT, onDeselectItem);
  424. item.removeEventListener(TransformEvent.MOUSE_DOWN, onMouseDownItem);
  425. item.removeEventListener(TransformEvent.SELECT_MOUSE_DOWN, onPressMove);
  426. item.removeEventListener(TransformEvent.SELECT_MOUSE_UP, onReleaseMove);
  427. item.removeEventListener(TransformEvent.UPDATE, onUpdateItem);
  428. item.removeEventListener(TransformEvent.SCALE, onUpdateItem);
  429. item.removeEventListener(TransformEvent.ROTATE, onUpdateItem);
  430. item.removeEventListener(TransformEvent.MOVE, onUpdateItem);
  431. item.removeEventListener(TransformEvent.ROLL_OVER_SELECTED, onRollOverSelectedItem);
  432. item.removeEventListener(TransformEvent.ROLL_OUT_SELECTED, onRollOutSelectedItem);
  433. item.removeEventListener(TransformEvent.DESTROY, onDestroyItem);
  434. for (var i:int = _items.length - 1; i > -1; i--) {
  435. if (item == _items[i]) {
  436. _items.splice(i, 1);
  437. item.destroy();
  438. break;
  439. }
  440. }
  441. }
  442. }
  443. /** Removes all items from the TransformManager instance. This does NOT delete the items - it just prevents them from being affected by this TransformManager anymore. **/
  444. public function removeAllItems():void {
  445. var item:TransformItem;
  446. for (var i:int = _items.length - 1; i > -1; i--) {
  447. item = _items[i];
  448. item.selected = false;
  449. item.removeEventListener(TransformEvent.SELECT, onSelectItem);
  450. item.removeEventListener(TransformEvent.DESELECT, onDeselectItem);
  451. item.removeEventListener(TransformEvent.MOUSE_DOWN, onMouseDownItem);
  452. item.removeEventListener(TransformEvent.SELECT_MOUSE_DOWN, onPressMove);
  453. item.removeEventListener(TransformEvent.SELECT_MOUSE_UP, onReleaseMove);
  454. item.removeEventListener(TransformEvent.UPDATE, onUpdateItem);
  455. item.removeEventListener(TransformEvent.SCALE, onUpdateItem);
  456. item.removeEventListener(TransformEvent.ROTATE, onUpdateItem);
  457. item.removeEventListener(TransformEvent.MOVE, onUpdateItem);
  458. item.removeEventListener(TransformEvent.ROLL_OVER_SELECTED, onRollOverSelectedItem);
  459. item.removeEventListener(TransformEvent.ROLL_OUT_SELECTED, onRollOutSelectedItem);
  460. item.removeEventListener(TransformEvent.DESTROY, onDestroyItem);
  461. _items.splice(i, 1);
  462. item.destroy();
  463. }
  464. }
  465. /**
  466. * Allows you to have TransformManager ignore clicks on a particular DisplayObject (handy for buttons, color pickers, etc.). The DisplayObject CANNOT be a child of a targetObject
  467. *
  468. * @param $object DisplayObject that should be ignored
  469. */
  470. public function addIgnoredObject($object:DisplayObject):void {
  471. for (var i:uint = 0; i < _ignoredObjects.length; i++) { //first make sure it's not already in the Array
  472. if (_ignoredObjects[i] == $object) {
  473. return;
  474. }
  475. }
  476. removeItem($object);
  477. _ignoredObjects.push($object);
  478. }
  479. /**
  480. * Removes an ignored DisplayObject so that its clicks are no longer ignored.
  481. *
  482. * @param $object DisplayObject that should not be ignored anymore
  483. */
  484. public function removeIgnoredObject($object:DisplayObject):void {
  485. for (var i:uint = 0; i < _ignoredObjects.length; i++) {
  486. if (_ignoredObjects[i] == $object) {
  487. _ignoredObjects.splice(i, 1);
  488. }
  489. }
  490. }
  491. /** @private **/
  492. private function onDestroyItem($e:TransformEvent):void {
  493. removeItem($e.target);
  494. }
  495. //---- GENERAL -------------------------------------------------------------------------------------------------------------------------
  496. /** @private **/
  497. private function setOrigin($p:Point):void { //Repositions the registration point of the _dummyBox and then calls plotHandles() to redraw the handles
  498. if (!_lockOrigin) {
  499. _lockOrigin = true; //prevents problems with recursion, particularly in updateSelection() when enforcing the saftey zone
  500. _origin = $p;
  501. var local:Point = _dummyBox.globalToLocal(_parent.localToGlobal($p));
  502. var bounds:Rectangle = _dummyBox.getBounds(_dummyBox);
  503. _dummyBox.graphics.clear();
  504. _dummyBox.graphics.beginFill(0x0066FF, 1);
  505. _dummyBox.graphics.drawRect(bounds.x - local.x, bounds.y - local.y, bounds.width, bounds.height);
  506. _dummyBox.graphics.endFill();
  507. _dummyBox.x = _origin.x;
  508. _dummyBox.y = _origin.y;
  509. enforceSafetyZone();
  510. for (var i:int = _selectedItems.length - 1; i > -1; i--) {
  511. _selectedItems[i].origin = _origin;
  512. }
  513. plotHandles();
  514. renderSelection();
  515. _lockOrigin = false;
  516. }
  517. }
  518. /** @private **/
  519. private function enforceSafetyZone():void { //Due to rounding issues in extremely small decimals, a selection can creep slightly over the bounds when the origin and/or selection bounding box is directly on top of one of the edges of the boundaries. This function enforces a 0.1 pixel "safety zone" to avoid that issue.
  520. if (_bounds != null) {
  521. var locks:Array;
  522. var prevLockPosition:Boolean = _selLockPosition;
  523. var prevLockScale:Boolean = _selLockScale;
  524. _selLockPosition = false;
  525. _selLockScale = false;
  526. if (!_bounds.containsPoint(_origin)) {
  527. locks = recordLocks();
  528. if (_bounds.left > _origin.x) {
  529. shiftSelection(_bounds.left - _origin.x, 0);
  530. } else if (_bounds.right < _origin.x) {
  531. shiftSelection(_bounds.right - _origin.x, 0);
  532. }
  533. if (_bounds.top > _origin.y) {
  534. shiftSelection(0, _bounds.top - _origin.y);
  535. } else if (_bounds.bottom < _origin.y) {
  536. shiftSelection(0, _bounds.bottom - _origin.y);
  537. }
  538. }
  539. if (_selectedItems.length != 0) {
  540. if (locks == null) {
  541. locks = recordLocks();
  542. }
  543. if (_handles[0].point == null) {
  544. plotHandles();
  545. }
  546. var b:Rectangle = getSelectionRect();
  547. if (_bounds.width - b.width < 0.2) {
  548. shiftSelectionScale(1 - (0.22 / b.width));
  549. }
  550. b = getSelectionRect();
  551. if (_bounds.height - b.height < 0.2) {
  552. shiftSelectionScale(1 - (0.22 / b.height));
  553. }
  554. if (Math.abs(b.top - _bounds.top) < 0.1) {
  555. shiftSelection(0, 0.1);
  556. }
  557. if (Math.abs(b.bottom - _bounds.bottom) < 0.1) {
  558. shiftSelection(0, -0.1);
  559. }
  560. if (Math.abs(b.left - _bounds.left) < 0.1) {
  561. shiftSelection(0.1, 0);
  562. }
  563. if (Math.abs(b.right - _bounds.right) < 0.1) {
  564. shiftSelection(-0.1, 0);
  565. }
  566. }
  567. if (locks != null) {
  568. restoreLocks(locks);
  569. }
  570. _selLockPosition = prevLockPosition;
  571. _selLockScale = prevLockScale;
  572. }
  573. //In order to impose the safe area, we need to make sure the lockPosition and lockScale properties are false. This function records the current values so we can restore them later.
  574. function recordLocks():Array {
  575. var a:Array = [];
  576. for (var i:int = _selectedItems.length - 1; i > -1; i--) {
  577. a[i] = {position:_selectedItems[i].lockPosition, scale:_selectedItems[i].lockScale};
  578. }
  579. return a;
  580. }
  581. function restoreLocks($a:Array):void {
  582. for (var i:int = $a.length - 1; i > -1; i--) {
  583. _selectedItems[i].lockPosition = $a[i].position;
  584. _selectedItems[i].lockScale = $a[i].scale;
  585. }
  586. }
  587. function shiftSelection($x:Number, $y:Number):void {
  588. _dummyBox.x += $x;
  589. _dummyBox.y += $y
  590. for (var i:int = _selectedItems.length - 1; i > -1; i--) {
  591. _selectedItems[i].move($x, $y, false, false);
  592. }
  593. _origin.x += $x;
  594. _origin.y += $y;
  595. }
  596. function shiftSelectionScale($scale:Number):void {
  597. var o:Point = _origin.clone();
  598. _origin.x = _bounds.x + (_bounds.width / 2);
  599. _origin.y = _bounds.y + (_bounds.height / 2);
  600. var i:int;
  601. for (i = _selectedItems.length - 1; i > -1; i--) {
  602. _selectedItems[i].origin = _origin;
  603. }
  604. scaleSelection($scale, $scale, false);
  605. _origin.x = o.x;
  606. _origin.y = o.y;
  607. for (i = _selectedItems.length - 1; i > -1; i--) {
  608. _selectedItems[i].origin = _origin;
  609. }
  610. updateSelection();
  611. }
  612. }
  613. /** @private **/
  614. protected function onPressDelete($e:Event = null):void {
  615. if (_enabled && _allowDelete) {
  616. var deletedItems:Array = [];
  617. var item:TransformItem;
  618. var multiple:Boolean = Boolean(_selectedItems.length > 1);
  619. var i:int = _selectedItems.length;
  620. while (i--) {
  621. item = _selectedItems[i];
  622. if (item.onPressDelete($e, multiple)) {
  623. deletedItems[deletedItems.length] = item;
  624. }
  625. }
  626. if (deletedItems.length > 0) {
  627. dispatchEvent(new TransformEvent(TransformEvent.DELETE, deletedItems));
  628. }
  629. }
  630. }
  631. /**
  632. * Deletes all selected items.
  633. *
  634. * @param $e Accepts an optional Event in case you want to use this as an event handler
  635. */
  636. public function deleteSelection($e:Event = null):void {
  637. var deletedItems:Array = [];
  638. var item:TransformItem;
  639. for (var i:int = _selectedItems.length - 1; i > -1; i--) {
  640. item = _selectedItems[i];
  641. item.deleteObject();
  642. deletedItems.push(item);
  643. }
  644. if (deletedItems.length != 0) {
  645. dispatchEvent(new TransformEvent(TransformEvent.DELETE, deletedItems));
  646. }
  647. }
  648. /** @private **/
  649. private function onPressArrowKey($e:KeyboardEvent = null):void {
  650. if (_arrowKeysMove && _enabled && _selectedItems.length != 0 && !(_stage.focus is TextField)){
  651. var moveAmount:int = 1;
  652. // Move faster if the shift key is down.
  653. if(isKeyDown(Keyboard.SHIFT)){
  654. moveAmount = 10;
  655. }
  656. switch($e.keyCode) {
  657. case Keyboard.UP:
  658. moveSelection(0, -moveAmount);
  659. dispatchEvent(new TransformEvent(TransformEvent.FINISH_INTERACTIVE_MOVE, _selectedItems.slice()));
  660. break;
  661. case Keyboard.DOWN:
  662. moveSelection(0, moveAmount);
  663. dispatchEvent(new TransformEvent(TransformEvent.FINISH_INTERACTIVE_MOVE, _selectedItems.slice()));
  664. break;
  665. case Keyboard.LEFT:
  666. moveSelection(-moveAmount, 0);
  667. dispatchEvent(new TransformEvent(TransformEvent.FINISH_INTERACTIVE_MOVE, _selectedItems.slice()));
  668. break;
  669. case Keyboard.RIGHT:
  670. moveSelection(moveAmount, 0);
  671. dispatchEvent(new TransformEvent(TransformEvent.FINISH_INTERACTIVE_MOVE, _selectedItems.slice()));
  672. break;
  673. }
  674. }
  675. }
  676. /** @private **/
  677. public function centerOrigin():void {
  678. setOrigin(getSelectionCenter());
  679. }
  680. /**
  681. * Gets the center point of the current selection
  682. *
  683. * @return Center Point of the current selection
  684. */
  685. public function getSelectionCenter():Point {
  686. var bounds:Rectangle = _dummyBox.getBounds(_dummyBox);
  687. return _parent.globalToLocal(_dummyBox.localToGlobal(new Point(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2)));
  688. }
  689. /**
  690. * Gets the bounding Rectangle of the current selection (not including handles)
  691. *
  692. * @param targetCoordinateSpace The display object that defines the coordinate system to use.
  693. * @return Bounding Rectangle of the current selection (not including handles).
  694. */
  695. public function getSelectionBounds(targetCoordinateSpace:DisplayObject=null):Rectangle {
  696. if (_parent.contains(_dummyBox) && _selectedItems.length != 0) {
  697. if (targetCoordinateSpace) {
  698. return _dummyBox.get

Large files files are truncated, but you can click here to view the full file