PageRenderTime 57ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/flex-object-handles-2/src/com/roguedevelopment/objecthandles/ObjectHandles.as

https://github.com/natami/ObjectHandles
ActionScript | 1220 lines | 686 code | 250 blank | 284 comment | 85 complexity | 90ca2ad687731e3a24ae66dbe0abaee6 MD5 | raw file
  1. /**
  2. * Latest information on this project can be found at http://www.rogue-development.com/objectHandles.html
  3. *
  4. * Copyright (c) 2010 Marc Hughes
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a
  7. * copy of this software and associated documentation files (the "Software"),
  8. * to deal in the Software without restriction, including without limitation
  9. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  10. * and/or sell copies of the Software, and to permit persons to whom the Software
  11. * is furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in all
  14. * copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  17. * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  18. * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  19. * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20. * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  21. * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. *
  23. * -------------------------------------------------------------------------------------------
  24. *
  25. * Contributions by:
  26. *
  27. * Alexander Kludt
  28. * Thomas Jakobi
  29. * Mario Ernst
  30. * Aaron Winkler
  31. * Gregory Tappero
  32. * Andrew Westberg
  33. * Erik Hoffmann
  34. *
  35. * -------------------------------------------------------------------------------------------
  36. *
  37. * Description:
  38. * ObjectHandles gives the user the ability to move and resize a component with the mouse.
  39. *
  40. *
  41. *
  42. **/
  43. package com.roguedevelopment.objecthandles
  44. {
  45. import flash.display.DisplayObject;
  46. import flash.display.Sprite;
  47. import flash.events.EventDispatcher;
  48. import flash.events.IEventDispatcher;
  49. import flash.events.KeyboardEvent;
  50. import flash.events.MouseEvent;
  51. import flash.geom.Matrix;
  52. import flash.geom.Point;
  53. import flash.ui.Keyboard;
  54. import flash.utils.Dictionary;
  55. import mx.containers.Canvas;
  56. import mx.core.ClassFactory;
  57. import mx.core.Container;
  58. import mx.core.IFactory;
  59. import mx.events.PropertyChangeEvent;
  60. import mx.events.ScrollEvent;
  61. [Event(name="handleClicked",type="com.roguedevelopment.objecthandles.HandleClickedEvent")]
  62. [Event(name="objectMoved",type="com.roguedevelopment.objecthandles.ObjectChangedEvent")]
  63. [Event(name="objectResized",type="com.roguedevelopment.objecthandles.ObjectChangedEvent")]
  64. [Event(name="objectRotated",type="com.roguedevelopment.objecthandles.ObjectChangedEvent")]
  65. [Event(name="objectMoving",type="com.roguedevelopment.objecthandles.ObjectChangedEvent")]
  66. [Event(name="objectResizing",type="com.roguedevelopment.objecthandles.ObjectChangedEvent")]
  67. [Event(name="objectRotating",type="com.roguedevelopment.objecthandles.ObjectChangedEvent")]
  68. [Event(name="objectMoveStart",type="com.roguedevelopment.objecthandles.ObjectChangedEvent")]
  69. [Event(name="objectResizeStart",type="com.roguedevelopment.objecthandles.ObjectChangedEvent")]
  70. [Event(name="objectRotateStart",type="com.roguedevelopment.objecthandles.ObjectChangedEvent")]
  71. public class ObjectHandles extends EventDispatcher
  72. {
  73. /**
  74. * The default handle class to use.
  75. *
  76. * SpriteHandle is good for Flex 3 based applications, or Flex 4 applications where the object handles live
  77. * inside a Canvas.
  78. *
  79. * Switch it over to a VisualElementHandle for Flex 4 based applications. Do this before creating ObjectHandle instances,
  80. * it's merely here to provide a convienent way to set the default globally.
  81. **/
  82. public static var defaultHandleClass:Class = SpriteHandle;
  83. // We need a zero point a lot, so lets not re-create it all the time.
  84. protected const zero:Point = new Point(0,0);
  85. // The container that the object handles all live inside.
  86. protected var container:Sprite;
  87. /**
  88. * Should the user be allowed to select multiple items?
  89. **/
  90. public var enableMultiSelect:Boolean=true;
  91. [Bindable] public var selectionManager:ObjectHandlesSelectionManager;
  92. protected var handleFactory:IFactory;
  93. /**
  94. * When a single object is selected, these are the default handles that will appear.
  95. *
  96. * You can modify it on an object by object basis by setting handleDescriptions in the
  97. * registerComponent method.
  98. **/
  99. public var defaultHandles:Array = [];
  100. /**
  101. * These are the handles that appear around the bounding box when multiple objects are selected.
  102. **/
  103. private var _multiSelectHandles:Array = [];
  104. // Key = a Model, value = an Array of handles
  105. protected var handles:Dictionary = new Dictionary();
  106. // Key = a visual, value = the model
  107. protected var models:Dictionary = new Dictionary();
  108. // Key = a model, value = an array of constraints for that model.
  109. protected var constraints:Dictionary = new Dictionary();
  110. // A dictionary of the geometry of the models before the current drag operation started.
  111. // This is set at the beginning of the user gesture.
  112. // Key = a visual, value = the model
  113. protected var originalModelGeometry:Dictionary = new Dictionary();
  114. // Key = a model, value = the visual
  115. protected var visuals:Dictionary = new Dictionary();
  116. // Key = a model, value = an array of HandleDescription objects;
  117. protected var handleDefinitions:Dictionary = new Dictionary();
  118. // Array of unused, visible=false handles
  119. protected var handleCache:Array = [];
  120. protected var temp:Point = new Point(0,0);
  121. protected var tempMatrix:Matrix = new Matrix();
  122. protected var isDragging:Boolean = false;
  123. protected var currentDragRole:uint = 0;
  124. protected var mouseDownPoint:Point;
  125. protected var containerMouseDownPoint:Point;
  126. protected var mouseDownRotation:Number;
  127. protected var originalGeometry:DragGeometry;
  128. /**
  129. * An array of IConstraint objects that influence how the objects are allowed to be
  130. * moved or resized.
  131. *
  132. * For instance, put in a SizeConstraint to set the max or minimum sizes.
  133. **/
  134. protected var defaultConstraints:Array = [];
  135. /**
  136. * An array of IConstraint objects that influence how a group of objects are allowed to be
  137. * moved or resized.
  138. **/
  139. protected var multiSelectConstraints:Array = [];
  140. protected var currentHandleConstraint:IFactory;
  141. /**
  142. * Flex 3 and Flex 4 applications manage children addition/removal differently (addChild vs. addElement) so I've abstracted
  143. * out that functionality into an IChildManager interface.
  144. **/
  145. protected var _childManager:IChildManager;
  146. public var modelList:Array=[];
  147. //used to remember object changes so
  148. //events can be fired when the changes are complete
  149. private var isMoved:Boolean = false;
  150. private var isResized:Boolean = false;
  151. private var isRotated:Boolean = false;
  152. protected var multiSelectModel:DragGeometry=new DragGeometry();
  153. /**
  154. * Many times below we have the need to create lots of temporary DragGeometry objects,
  155. * we can use this one instead for times when we only need one at a time so we're not
  156. * allocating and deallocating tons of objects causing the garbage collector to go crazy.
  157. *
  158. * Be very careful not to use this in a way that multiple places are depending on it at once!
  159. *
  160. **/
  161. private var tempGeometry:DragGeometry = new DragGeometry();
  162. /**
  163. * @param container The base container that all of the objects and the handles will be added to.
  164. *
  165. * @param selectionManager A manager class that deals with which object(s) are currently selected. If you
  166. * pass null, a new one will be created for you. It's often times useful to share
  167. * a single selection manage across an entire app so that "selection" is global.
  168. *
  169. * @param handleFactory A factory capable of creating IHandle objects.
  170. * If you pass null, a generic Flex 3 based factory will be created.
  171. * Use the Flex4HandleFactory to make Group compatible handles.
  172. *
  173. * @param childManager Flex 3 and Flex 4 applications manage children addition/removal differently (addChild vs. addElement) so I've abstracted
  174. * out that functionality into an IChildManager interface that understands those differences. In general, use Flex3ChildManager for
  175. * Flex3 applications and use Flex4ChildManager for Flex 4 based applications.
  176. *
  177. **/
  178. public function ObjectHandles( container:Sprite ,
  179. selectionManager:ObjectHandlesSelectionManager = null,
  180. handleFactory:IFactory = null,
  181. childManager:IChildManager = null
  182. )
  183. {
  184. this.container = container;
  185. //container.addEventListener(MouseEvent.ROLL_OUT, onContainerRollOut );
  186. container.addEventListener( ScrollEvent.SCROLL, onContainerScroll );
  187. if( selectionManager )
  188. this.selectionManager = selectionManager;
  189. else
  190. this.selectionManager = new ObjectHandlesSelectionManager();
  191. if( handleFactory )
  192. this.handleFactory = handleFactory;
  193. else
  194. this.handleFactory = new ClassFactory( defaultHandleClass );
  195. this.selectionManager.addEventListener(SelectionEvent.ADDED_TO_SELECTION, onSelectionAdded );
  196. this.selectionManager.addEventListener(SelectionEvent.REMOVED_FROM_SELECTION, onSelectionRemoved );
  197. this.selectionManager.addEventListener(SelectionEvent.SELECTION_CLEARED, onSelectionCleared );
  198. multiSelectHandles.push( new HandleDescription( HandleRoles.RESIZE_UP + HandleRoles.RESIZE_LEFT,
  199. zero ,
  200. zero ) );
  201. multiSelectHandles.push( new HandleDescription( HandleRoles.RESIZE_UP ,
  202. new Point(50,0) ,
  203. zero ) );
  204. multiSelectHandles.push( new HandleDescription( HandleRoles.RESIZE_UP + HandleRoles.RESIZE_RIGHT,
  205. new Point(100,0) ,
  206. zero ) );
  207. multiSelectHandles.push( new HandleDescription( HandleRoles.RESIZE_RIGHT,
  208. new Point(100,50) ,
  209. zero ) );
  210. multiSelectHandles.push( new HandleDescription( HandleRoles.RESIZE_DOWN + HandleRoles.RESIZE_RIGHT,
  211. new Point(100,100) ,
  212. zero ) );
  213. multiSelectHandles.push( new HandleDescription( HandleRoles.RESIZE_DOWN ,
  214. new Point(50,100) ,
  215. zero ) );
  216. multiSelectHandles.push( new HandleDescription( HandleRoles.RESIZE_DOWN + HandleRoles.RESIZE_LEFT,
  217. new Point(0,100) ,
  218. zero ) );
  219. multiSelectHandles.push( new HandleDescription( HandleRoles.RESIZE_LEFT,
  220. new Point(0,50) ,
  221. zero ) );
  222. multiSelectHandles.push( new HandleDescription( HandleRoles.ROTATE,
  223. new Point(100,50) ,
  224. new Point(20,0) ) );
  225. defaultHandles.push( new HandleDescription( HandleRoles.RESIZE_UP + HandleRoles.RESIZE_LEFT,
  226. zero ,
  227. zero ) );
  228. defaultHandles.push( new HandleDescription( HandleRoles.RESIZE_UP ,
  229. new Point(50,0) ,
  230. zero ) );
  231. defaultHandles.push( new HandleDescription( HandleRoles.RESIZE_UP + HandleRoles.RESIZE_RIGHT,
  232. new Point(100,0) ,
  233. zero ) );
  234. defaultHandles.push( new HandleDescription( HandleRoles.RESIZE_RIGHT,
  235. new Point(100,50) ,
  236. zero ) );
  237. defaultHandles.push( new HandleDescription( HandleRoles.RESIZE_DOWN + HandleRoles.RESIZE_RIGHT,
  238. new Point(100,100) ,
  239. zero ) );
  240. defaultHandles.push( new HandleDescription( HandleRoles.RESIZE_DOWN ,
  241. new Point(50,100) ,
  242. zero ) );
  243. defaultHandles.push( new HandleDescription( HandleRoles.RESIZE_DOWN + HandleRoles.RESIZE_LEFT,
  244. new Point(0,100) ,
  245. zero ) );
  246. defaultHandles.push( new HandleDescription( HandleRoles.RESIZE_LEFT,
  247. new Point(0,50) ,
  248. zero ) );
  249. defaultHandles.push( new HandleDescription( HandleRoles.ROTATE,
  250. new Point(100,50) ,
  251. new Point(20,0) ) );
  252. if( childManager == null )
  253. {
  254. _childManager = new Flex3ChildManager();
  255. }
  256. else
  257. {
  258. _childManager = childManager
  259. }
  260. registerComponent(multiSelectModel,null,multiSelectHandles,false);
  261. }
  262. public function get multiSelectHandles():Array
  263. {
  264. return _multiSelectHandles;
  265. }
  266. public function set multiSelectHandles(value:Array):void
  267. {
  268. var ind:int = modelList.indexOf( multiSelectModel );
  269. if( ind != -1 ) modelList.splice(ind,1);
  270. multiSelectModel.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onModelChange );
  271. _multiSelectHandles = value;
  272. registerComponent(multiSelectModel,null,_multiSelectHandles,false);
  273. }
  274. /**
  275. * Registers a component with the ObjectHandle manager.
  276. *
  277. * @param dataModel The data model that represents this object. This is where values will be commited to. It should have a getter/setter for x,y,width, height, and optionally
  278. * rotation.
  279. *
  280. * @param visualDisplay This is the actual on-screen display of the object. We never set the coordinates of this object explictly, it should be bound
  281. * to your data model.
  282. *
  283. * @param handleDescriptions If you want non-standard handles, create a list of HandleDescription objects and pass them in.
  284. *
  285. * @param captureKeyEvents Should we add event listeners to support keyboard navigation?
  286. **/
  287. public function registerComponent( dataModel:Object,
  288. visualDisplay:IEventDispatcher ,
  289. handleDescriptions:Array = null,
  290. captureKeyEvents:Boolean = true,
  291. customConstraints:Array = null ) : void
  292. {
  293. modelList.push(dataModel);
  294. if( visualDisplay )
  295. {
  296. visualDisplay.addEventListener( MouseEvent.MOUSE_DOWN, onComponentMouseDown, false, 0, true );
  297. visualDisplay.addEventListener( SelectionEvent.SELECTED, handleSelection );
  298. if(captureKeyEvents)
  299. {
  300. visualDisplay.addEventListener( KeyboardEvent.KEY_DOWN, onKeyDown );
  301. }
  302. models[visualDisplay] = dataModel;
  303. }
  304. dataModel.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onModelChange );
  305. visuals[dataModel] = visualDisplay;
  306. if( handleDescriptions )
  307. {
  308. handleDefinitions[ dataModel ] = handleDescriptions;
  309. }
  310. if( customConstraints )
  311. {
  312. constraints[dataModel] = customConstraints;
  313. }
  314. }
  315. /**
  316. * A constraint limits the way something could be sized or moved. For instance, you could have a minimum size.
  317. *
  318. * There are 3 types of constraints.
  319. *
  320. * A constraint that applies to a single component (set in the registerComponent call)
  321. * A constraint that applies to groups of components during multi selection (set in addMultiSelectConstraint)
  322. * Constraints that apply to all individual components
  323. *
  324. * This method adds constraints to that last one.
  325. **/
  326. public function addDefaultConstraint( constraint:IConstraint ) : void
  327. {
  328. defaultConstraints.push( constraint );
  329. }
  330. /**
  331. * A constraint limits the way something could be sized or moved. For instance, you could have a minimum size.
  332. *
  333. * There are 3 types of constraints.
  334. *
  335. * A constraint that applies to a single component (set in the registerComponent call)
  336. * Constraints that apply to all individual components (set in addDefaultConstraint)
  337. * A constraint that applies to groups of components during multi selection
  338. *
  339. * This method adds constraints to that last one.
  340. **/
  341. public function addMultiSelectConstraint( constraint:IConstraint ) : void
  342. {
  343. multiSelectConstraints.push( constraint );
  344. }
  345. public function getDisplayForModel( model:Object ) : IEventDispatcher
  346. {
  347. return visuals[model];
  348. }
  349. protected function onKeyDown(event:KeyboardEvent):void
  350. {
  351. var t:DragGeometry = new DragGeometry();
  352. switch(event.keyCode )
  353. {
  354. case Keyboard.UP : t.y --; break;
  355. case Keyboard.DOWN : t.y ++; break;
  356. case Keyboard.RIGHT : t.x ++; break;
  357. case Keyboard.LEFT : t.x --; break;
  358. default:return;
  359. }
  360. //Respect locked
  361. var locked:Boolean = false;
  362. if( selectionManager.currentlySelected.length > 0)
  363. {
  364. for each ( var obj:Object in selectionManager.currentlySelected )
  365. {
  366. if( obj.hasOwnProperty("isLocked") && obj["isLocked"] )
  367. {
  368. locked = true;
  369. }
  370. }
  371. }
  372. if( locked) { return; }
  373. // saving old coordinates
  374. originalModelGeometry = new Dictionary();
  375. for each(var current:Object in selectionManager.currentlySelected)
  376. {
  377. originalModelGeometry[current] = selectionManager.getGeometryForObject(current);
  378. }
  379. originalGeometry = selectionManager.getGeometry();
  380. applyConstraints( t, HandleRoles.MOVE );
  381. applyTranslation( t );
  382. dispatchMoving();
  383. }
  384. //
  385. // protected function onKeyDown(event:KeyboardEvent):void
  386. // {
  387. // var t:DragGeometry = new DragGeometry();
  388. // switch(event.keyCode )
  389. // {
  390. // case Keyboard.UP : t.y --; break;
  391. // case Keyboard.DOWN : t.y ++; break;
  392. // case Keyboard.RIGHT : t.x ++; break;
  393. // case Keyboard.LEFT : t.x --; break;
  394. // default:return;
  395. // }
  396. //
  397. // originalGeometry = selectionManager.getGeometry();
  398. //
  399. //
  400. // applyConstraints( t, HandleRoles.MOVE );
  401. //
  402. // applyTranslation( t );
  403. // }
  404. /**
  405. * Returns true if the given model should have a movement handle.
  406. **/
  407. protected function hasMovementHandle( model:Object ) : Boolean
  408. {
  409. var desiredHandles:Array = getHandleDefinitions(model);
  410. for each ( var handle:HandleDescription in desiredHandles )
  411. {
  412. if( HandleRoles.isMove( handle.role ) ) return true;
  413. }
  414. return false;
  415. }
  416. public function unregisterModel( model:Object ) : void
  417. {
  418. var display:IEventDispatcher = visuals[model];
  419. unregisterComponent( display );
  420. }
  421. public function unregisterComponent( visualDisplay:IEventDispatcher ) : void
  422. {
  423. visualDisplay.removeEventListener( MouseEvent.MOUSE_DOWN, onComponentMouseDown);
  424. visualDisplay.removeEventListener( SelectionEvent.SELECTED, handleSelection );
  425. visualDisplay.removeEventListener( KeyboardEvent.KEY_DOWN, onKeyDown );
  426. var dataModel:Object = findModel(visualDisplay as DisplayObject);
  427. if( dataModel )
  428. {
  429. dataModel.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onModelChange );
  430. }
  431. modelList.splice( modelList.indexOf(dataModel), 1 );
  432. delete visuals[dataModel];
  433. delete models[visualDisplay];
  434. if( selectionManager.currentlySelected.indexOf(dataModel) != -1 )
  435. {
  436. selectionManager.clearSelection();
  437. }
  438. }
  439. protected function onModelChange(event:PropertyChangeEvent):void
  440. {
  441. switch( event.property )
  442. {
  443. case "x":
  444. case "y":
  445. case "width":
  446. case "height":
  447. case "rotation": updateHandlePositions(event.target);
  448. break;
  449. case "isLocked": redrawHandle(event.target);
  450. }
  451. }
  452. protected function onSelectionAdded( event:SelectionEvent ) : void
  453. {
  454. setupHandles();
  455. }
  456. protected function onSelectionRemoved( event:SelectionEvent ) : void
  457. {
  458. setupHandles();
  459. }
  460. protected function onSelectionCleared( event:SelectionEvent ) : void
  461. {
  462. setupHandles();
  463. lastSelectedModel=null;
  464. }
  465. protected function onComponentMouseDown(event:MouseEvent):void
  466. {
  467. handleSelection( event );
  468. container.stage.addEventListener(MouseEvent.MOUSE_MOVE, onContainerMouseMove );
  469. container.stage.addEventListener( MouseEvent.MOUSE_UP, onContainerMouseUp );
  470. try
  471. {
  472. event.target.setFocus();
  473. }catch(e:Error){}
  474. var model:Object = findModel( event.target as DisplayObject);
  475. if( ! hasMovementHandle(model) )
  476. {
  477. currentDragRole = HandleRoles.MOVE; // a mouse down on the component itself as opposed to a handle is a move operation.
  478. currentHandleConstraint = null;
  479. handleBeginDrag( event );
  480. dispatchEvent( new ObjectChangedEvent(selectionManager.currentlySelected,ObjectChangedEvent.OBJECT_MOVE_START) );
  481. }
  482. }
  483. protected function onContainerRollOut(event:MouseEvent) : void
  484. {
  485. isDragging = false;
  486. }
  487. protected function onContainerMouseUp( event:MouseEvent ) : void
  488. {
  489. if (isMoved)
  490. {
  491. dispatchEvent(new ObjectChangedEvent(selectionManager.currentlySelected, ObjectChangedEvent.OBJECT_MOVED, true));
  492. }
  493. else if (isResized)
  494. {
  495. dispatchEvent(new ObjectChangedEvent(selectionManager.currentlySelected, ObjectChangedEvent.OBJECT_RESIZED, true));
  496. }
  497. else if (isRotated)
  498. {
  499. dispatchEvent(new ObjectChangedEvent(selectionManager.currentlySelected, ObjectChangedEvent.OBJECT_ROTATED, true));
  500. }
  501. isMoved = false;
  502. isResized = false;
  503. isRotated = false;
  504. container.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onContainerMouseMove );
  505. container.stage.removeEventListener( MouseEvent.MOUSE_UP, onContainerMouseUp );
  506. if( selectionManager.currentlySelected.length > 1 )
  507. {
  508. multiSelectModel.copyFrom( selectionManager.getGeometry() );
  509. updateHandlePositions(multiSelectModel);
  510. }
  511. isDragging = false;
  512. }
  513. protected function onContainerScroll(event:ScrollEvent):void
  514. {
  515. for each (var model:Object in models )
  516. {
  517. updateHandlePositions(model);
  518. }
  519. }
  520. // Pulling these 4 variables to a member level var so we allocate less of them, this method gets called a lot
  521. // so a ton of objects were being created and eventually destroyed leading to some choppiness.
  522. protected var translation:DragGeometry = new DragGeometry();
  523. protected function onContainerMouseMove( event:MouseEvent ) : void
  524. {
  525. var locked:Boolean = false;
  526. if( selectionManager.currentlySelected.length > 0)
  527. {
  528. for each ( var obj:Object in selectionManager.currentlySelected )
  529. {
  530. if( obj.hasOwnProperty("isLocked") && obj["isLocked"] )
  531. {
  532. locked = true;
  533. }
  534. }
  535. }
  536. if( (! isDragging) || locked) { return; }
  537. //var translation:DragGeometry = new DragGeometry();
  538. translation.height=0;
  539. translation.width=0;
  540. translation.x=0;
  541. translation.y=0;
  542. translation.rotation=0;
  543. if( HandleRoles.isMove( currentDragRole ) )
  544. {
  545. isMoved = true;
  546. applyMovement( event, translation );
  547. applyConstraints(translation, currentDragRole );
  548. }
  549. if( HandleRoles.isResizeLeft( currentDragRole ) )
  550. {
  551. isResized = true;
  552. applyResizeLeft( event, translation );
  553. }
  554. if( HandleRoles.isResizeUp( currentDragRole) )
  555. {
  556. isResized = true;
  557. applyResizeUp( event, translation );
  558. }
  559. if( HandleRoles.isResizeRight( currentDragRole ) )
  560. {
  561. isResized = true;
  562. applyResizeRight( event, translation );
  563. }
  564. if( HandleRoles.isResizeDown( currentDragRole ) )
  565. {
  566. isResized = true;
  567. applyResizeDown( event, translation );
  568. }
  569. if( HandleRoles.isRotate( currentDragRole ) )
  570. {
  571. isRotated = true;
  572. applyRotate( event, translation );
  573. }
  574. applyConstraints(translation, currentDragRole );
  575. applyAnchorPoint(originalGeometry, translation, currentDragRole );
  576. applyTranslation( translation );
  577. event.updateAfterEvent();
  578. if (isMoved)
  579. {
  580. dispatchMoving();
  581. }
  582. else if (isResized)
  583. {
  584. dispatchResizing();
  585. }
  586. else if (isRotated)
  587. {
  588. dispatchRotating();
  589. }
  590. }
  591. private function dispatchRotating():void
  592. {
  593. if( willTrigger( ObjectChangedEvent.OBJECT_ROTATING ) ) // Don't create an event object unless it's actually needed. Should help avoid GC and improve performance.
  594. {
  595. dispatchEvent(new ObjectChangedEvent(selectionManager.currentlySelected,ObjectChangedEvent.OBJECT_ROTATING,true) );
  596. }
  597. }
  598. private function dispatchResizing():void
  599. {
  600. if( willTrigger( ObjectChangedEvent.OBJECT_RESIZING ) ) // Don't create an event object unless it's actually needed. Should help avoid GC and improve performance.
  601. {
  602. dispatchEvent(new ObjectChangedEvent(selectionManager.currentlySelected,ObjectChangedEvent.OBJECT_RESIZING,true) );
  603. }
  604. }
  605. protected function dispatchMoving():void
  606. {
  607. if( willTrigger( ObjectChangedEvent.OBJECT_MOVING ) ) // Don't create an event object unless it's actually needed. Should help avoid GC and improve performance.
  608. {
  609. dispatchEvent(new ObjectChangedEvent(selectionManager.currentlySelected,ObjectChangedEvent.OBJECT_MOVING,true) );
  610. }
  611. }
  612. /**
  613. * When resizing, there should be an "anchor point" that doesn't move. Sometimes, we need to move the entire object around so
  614. * that anchor point doesn't move.
  615. *
  616. * Example: Resizing a rectangle larger to the left. The width should increase and the whole thing should move to the left so the
  617. * right side stays stationary.
  618. *
  619. * This method applies an x/y translation based upon a width/height translation and drag role
  620. **/
  621. protected function applyAnchorPoint( original:DragGeometry, translation:DragGeometry, currentDragRole:uint ) : void
  622. {
  623. if( HandleRoles.isRotate( currentDragRole ) )
  624. {
  625. var mid:Point = new Point(original.width/2, original.height/2) ;
  626. // We want to rotate around the center instead of around the upper left corner.
  627. tempMatrix.identity();
  628. tempMatrix.rotate( toRadians(original.rotation) );
  629. temp = tempMatrix.transformPoint( mid ) // this is where the center was.
  630. tempMatrix.identity();
  631. tempMatrix.rotate( toRadians(original.rotation + translation.rotation) );
  632. mid = tempMatrix.transformPoint( mid ); // This is where the new center should be.
  633. translation.x = temp.x - mid.x;
  634. translation.y = temp.y - mid.y;
  635. }
  636. if( HandleRoles.isResize(currentDragRole) )
  637. {
  638. var proportion:Point = getAnchorProportion( currentDragRole );
  639. tempMatrix.identity();
  640. tempMatrix.rotate(toRadians(original.rotation));
  641. temp.x = (proportion.x * (translation.width + originalGeometry.width)) - proportion.x * originalGeometry.width;
  642. temp.y = (proportion.y * (translation.height + originalGeometry.height)) - proportion.y * originalGeometry.height;
  643. temp = tempMatrix.transformPoint( temp );
  644. translation.x += temp.x;
  645. translation.y += temp.y;
  646. // More readable version of the optimized code above:
  647. // var proportion:Point = getAnchorProportion( currentDragRole );
  648. // var m:Matrix = new Matrix();
  649. // m.rotate(toRadians(rotation));
  650. // var anchorPoint:Point = new Point( proportion.x * originalGeometry.width, proportion.y * originalGeometry.height );
  651. // var destAnchorPoint:Point = new Point( proportion.x * (translation.width + originalGeometry.width), proportion.y * (translation.height + originalGeometry.height) );
  652. //
  653. // var offset:Point = new Point(destAnchorPoint.x - anchorPoint.x, destAnchorPoint.y - anchorPoint.y);
  654. //
  655. // offset = m.transformPoint( offset );
  656. // translation.x += offset.x;
  657. // translation.y += offset.y;
  658. }
  659. }
  660. /**
  661. *
  662. * Figure out which point is the anchor, and then return what the width/height proportion of
  663. * the translation applies to it.
  664. *
  665. * Anchor = This is the point that shouldn't move.
  666. * I'm only going to worry about the 8 main places a handle could be, so this will get
  667. * a bit weird if you have custom handles in odd places. If that's the case for you,
  668. * subclass this class and override getAnchorProportion
  669. **/
  670. protected function getAnchorProportion( resizeHandleRole:uint) : Point
  671. {
  672. var anchorPoint:Point = new Point();
  673. if( HandleRoles.isResizeUp(resizeHandleRole) )
  674. {
  675. if( HandleRoles.isResizeLeft( resizeHandleRole ) )
  676. {
  677. // Upper left handle being used, so the lower right corner should not move.
  678. anchorPoint.x = -1;
  679. anchorPoint.y = -1;
  680. }
  681. else if( HandleRoles.isResizeRight( resizeHandleRole ) )
  682. {
  683. // Upper right handle
  684. anchorPoint.x = 0;
  685. anchorPoint.y = -1;
  686. }
  687. else
  688. {
  689. anchorPoint.x = -0.5;
  690. anchorPoint.y = -1;
  691. }
  692. }
  693. else if( HandleRoles.isResizeDown(resizeHandleRole) )
  694. {
  695. if( HandleRoles.isResizeLeft( resizeHandleRole ) )
  696. {
  697. // lower left handle
  698. anchorPoint.x = -1;
  699. anchorPoint.y = 0;
  700. }
  701. else if( HandleRoles.isResizeRight( resizeHandleRole ) )
  702. {
  703. // lower right handle
  704. anchorPoint.x = 0;
  705. anchorPoint.y = 0;
  706. }
  707. else
  708. {
  709. // middle bottom handle
  710. anchorPoint.x = -0.5;
  711. anchorPoint.y = 0;
  712. }
  713. }
  714. else if( HandleRoles.isResizeLeft(resizeHandleRole) )
  715. {
  716. // left middle handle
  717. anchorPoint.x = -1;
  718. anchorPoint.y = -0.5;
  719. }
  720. else
  721. {
  722. // right middle
  723. anchorPoint.x = 0;
  724. anchorPoint.y = -0.5;
  725. }
  726. return anchorPoint;
  727. }
  728. protected function applyTranslationForSingleObject( current:Object, translation:DragGeometry , originalGeometry:DragGeometry) : void
  729. {
  730. if( current.hasOwnProperty("x") ) current.x = translation.x + originalGeometry.x;
  731. if( current.hasOwnProperty("y") ) current.y = translation.y + originalGeometry.y;
  732. if( current.hasOwnProperty("width") ) current.width = translation.width + originalGeometry.width;
  733. if( current.hasOwnProperty("height") ) current.height = translation.height + originalGeometry.height;
  734. if( current.hasOwnProperty("rotation") ) current.rotation = translation.rotation + originalGeometry.rotation;
  735. updateHandlePositions( current );
  736. }
  737. protected function applyTranslation( translation:DragGeometry) : void
  738. {
  739. if( selectionManager.currentlySelected.length == 1 )
  740. {
  741. applyTranslationForSingleObject( selectionManager.currentlySelected[0], translation, originalGeometry );
  742. }
  743. else if( selectionManager.currentlySelected.length > 1 )
  744. {
  745. applyTranslationForSingleObject(multiSelectModel, translation , originalGeometry);
  746. for each ( var subObject:Object in selectionManager.currentlySelected )
  747. {
  748. var subTranslation:DragGeometry = calculateTranslationFromMultiTranslation( translation, subObject );
  749. var originalGeometry:DragGeometry = originalModelGeometry[ subObject ]
  750. // At this point, constraints to the entire group have already been applied, but we need to apply per component constraints.
  751. applySingleObjectConstraints(subObject, originalGeometry, subTranslation, currentDragRole );
  752. applyTranslationForSingleObject( subObject, subTranslation , originalModelGeometry[subObject] );
  753. }
  754. }
  755. }
  756. /**
  757. * Convienence method for dealing with tempGeometry, again BE VERY CAREFUL when calling it.
  758. **/
  759. private function copyToTempGeometry( obj:Object ) : void
  760. {
  761. tempGeometry.height = obj.height;
  762. tempGeometry.width = obj.width;
  763. tempGeometry.x = obj.x;
  764. tempGeometry.y = obj.y;
  765. if( obj.hasOwnProperty("rotation") ) tempGeometry.rotation = obj.rotation;
  766. if( obj.hasOwnProperty("isLocked") ) tempGeometry.isLocked = obj.isLocked;
  767. }
  768. private var selectionMatrix:Matrix = new Matrix();
  769. private var objectMatrix:Matrix = new Matrix();
  770. private var relativeGeometry:Point = new Point();
  771. /**
  772. * Calculates the translation of a single object in a group of objects that is selected
  773. * based on the translation of the entire group.
  774. **/
  775. protected function calculateTranslationFromMultiTranslation(overallTranslation:DragGeometry , object:Object) : DragGeometry
  776. {
  777. var rv:DragGeometry = new DragGeometry();
  778. // This is the rotation, scaling, and translation of the entire selection.
  779. selectionMatrix.identity();
  780. selectionMatrix.rotate( toRadians( overallTranslation.rotation ));
  781. selectionMatrix.scale( (originalGeometry.width + overallTranslation.width) / originalGeometry.width,
  782. (originalGeometry.height + overallTranslation.height) / originalGeometry.height );
  783. selectionMatrix.translate( overallTranslation.x + originalGeometry.x, overallTranslation.y + originalGeometry.y);
  784. // This is the point the object is relative to the selection
  785. relativeGeometry.x = originalModelGeometry[object].x - originalGeometry.x;
  786. relativeGeometry.y = originalModelGeometry[object].y - originalGeometry.y;
  787. objectMatrix.identity();
  788. objectMatrix.rotate( toRadians( overallTranslation.rotation + originalModelGeometry[object].rotation) );
  789. objectMatrix.translate(relativeGeometry.x, relativeGeometry.y);
  790. var translatedZeroPoint:Point = objectMatrix.transformPoint( zero );
  791. var translatedTopRightCorner:Point = objectMatrix.transformPoint( new Point(originalModelGeometry[object].width,0) );
  792. var translatedBottomLeftCorner:Point = objectMatrix.transformPoint( new Point(0,originalModelGeometry[object].height) );
  793. translatedZeroPoint = selectionMatrix.transformPoint( translatedZeroPoint );
  794. translatedTopRightCorner = selectionMatrix.transformPoint( translatedTopRightCorner );
  795. translatedBottomLeftCorner = selectionMatrix.transformPoint( translatedBottomLeftCorner );
  796. // uncomment to draw debug graphics.
  797. // container.graphics.lineStyle(2,0xff0000,0.5);
  798. // container.graphics.drawCircle(translatedZeroPoint.x, translatedZeroPoint.y , 4);
  799. // container.graphics.lineStyle(2,0xffff00,0.5);
  800. // container.graphics.drawCircle(translatedTopRightCorner.x, translatedTopRightCorner.y , 4);
  801. var targetWidth:Number = Point.distance( translatedZeroPoint, translatedTopRightCorner);
  802. var targetHeight:Number = Point.distance( translatedZeroPoint, translatedBottomLeftCorner ) ;
  803. // remember, rv is the CHANGE in value from the original, not an absolute value.
  804. rv.x = translatedZeroPoint.x - originalModelGeometry[object].x;
  805. rv.y = translatedZeroPoint.y - originalModelGeometry[object].y;
  806. rv.width = targetWidth - originalModelGeometry[object].width;
  807. rv.height = targetHeight - originalModelGeometry[object].height;
  808. var targetAngle:Number = toDegrees(Math.atan2( translatedTopRightCorner.y - translatedZeroPoint.y, translatedTopRightCorner.x - translatedZeroPoint.x));
  809. rv.rotation = targetAngle - originalModelGeometry[object].rotation - overallTranslation.rotation;
  810. return rv;
  811. }
  812. protected function applyConstraints(translation:DragGeometry, currentDragRole:uint):void
  813. {
  814. var constraint:IConstraint;
  815. if (currentHandleConstraint != null)
  816. {
  817. currentHandleConstraint.newInstance().applyConstraint( originalGeometry, translation, currentDragRole );
  818. }
  819. if( selectionManager.currentlySelected.length > 1 )
  820. {
  821. // Deal with multi-select
  822. for each ( constraint in multiSelectConstraints )
  823. {
  824. constraint.applyConstraint( originalGeometry, translation, currentDragRole );
  825. }
  826. return;
  827. // we'll apply per-component constraints in the applyTranslation method
  828. }
  829. // Single object selection constraint...
  830. applySingleObjectConstraints( selectionManager.currentlySelected[0], originalGeometry, translation, currentDragRole );
  831. }
  832. protected function applySingleObjectConstraints(modelObject:Object, originalGeometry:DragGeometry, translation:DragGeometry, currentDragRole:uint):void
  833. {
  834. var constraint:IConstraint;
  835. // Default ObjectHandles wide constraints
  836. for each ( constraint in defaultConstraints )
  837. {
  838. constraint.applyConstraint( originalGeometry, translation, currentDragRole );
  839. }
  840. // Constraints that are set on a per component basis in the registerComponent call
  841. var customConstraints:Array = constraints[ modelObject ];
  842. if( customConstraints )
  843. {
  844. for each ( constraint in customConstraints )
  845. {
  846. constraint.applyConstraint( originalGeometry, translation, currentDragRole );
  847. }
  848. }
  849. }
  850. protected function applyRotate( event:MouseEvent, proposed:DragGeometry ) : void
  851. {
  852. var centerRotatedAmount:Number = toRadians(originalGeometry.rotation) -
  853. toRadians(mouseDownRotation) +
  854. getAngleInRadians(event.stageX, event.stageY);
  855. tempMatrix.identity();
  856. //var oldRotationMatrix:Matrix = new Matrix();
  857. tempMatrix.rotate( toRadians( originalGeometry.rotation) );
  858. var oldCenter:Point = tempMatrix.transformPoint(new Point(originalGeometry.width/2,originalGeometry.height/2));
  859. //
  860. var newRotationMatrix:Matrix = new Matrix();
  861. //newRotationMatrix.rotate( toRadians(originalGeometry.rotation) );
  862. newRotationMatrix.translate(-oldCenter.x, -oldCenter.y);//-originalGeometry.width/2,-originalGeometry.height/2);
  863. newRotationMatrix.rotate( centerRotatedAmount );
  864. newRotationMatrix.translate(oldCenter.x, oldCenter.y);
  865. var newOffset:Point = newRotationMatrix.transformPoint( zero );
  866. // proposed.x += newOffset.x;
  867. // proposed.y += newOffset.y;
  868. proposed.rotation = toDegrees(centerRotatedAmount);
  869. }
  870. protected function getAngleInRadians(x:Number,y:Number):Number
  871. {
  872. tempMatrix.identity();
  873. var mousePos:Point = container.globalToLocal( new Point(x,y) );
  874. var angle1:Number;
  875. tempMatrix.rotate( toRadians( originalGeometry.rotation) );
  876. var originalCenter:Point = tempMatrix.transformPoint( new Point(originalGeometry.width/2, originalGeometry.height/2) );
  877. originalCenter.offset( originalGeometry.x, originalGeometry.y );
  878. //// This will draw some debug lines
  879. // container.graphics.clear();
  880. // container.graphics.lineStyle(1,0xff0000);
  881. // container.graphics.moveTo( originalCenter.x, originalCenter.y );
  882. // container.graphics.lineTo( mousePos.x, mousePos.y );
  883. // container.graphics.lineStyle(1,0x00ff00);
  884. // var ang:Number = Math.atan2(mousePos.y - originalCenter.x, mousePos.x - originalCenter.y) ;
  885. // container.graphics.moveTo( originalCenter.x, originalCenter.y );
  886. // container.graphics.lineTo( originalCenter.x + Math.cos(ang)*300, originalCenter.y + Math.sin(ang)*300);
  887. if( container is Canvas) {
  888. var parentCanvas:Canvas = container as Canvas;
  889. return Math.atan2((mousePos.y + parentCanvas.verticalScrollPosition) - originalCenter.y, (mousePos.x + parentCanvas.horizontalScrollPosition) - originalCenter.x) ;
  890. }
  891. else
  892. return Math.atan2(mousePos.y - originalCenter.y, mousePos.x - originalCenter.x) ;
  893. }
  894. protected function applyMovement( event:MouseEvent, translation:DragGeometry ) : void
  895. {
  896. temp.x = event.stageX;
  897. temp.y = event.stageY;
  898. //var localDown:Point = container.globalToLocal( mouseDownPoint );
  899. var current:Point = container.globalToLocal( temp );
  900. var mouseDelta:Point = new Point( current.x - containerMouseDownPoint.x, current.y - containerMouseDownPoint.y );
  901. translation.x = mouseDelta.x;
  902. translation.y = mouseDelta.y;
  903. }
  904. protected function applyResizeRight( event:MouseEvent, translation:DragGeometry ) : void
  905. {
  906. var containerOriginalMousePoint:Point = container.globalToLocal(new Point( mouseDownPoint.x, mouseDownPoint.y ));
  907. var containerMousePoint:Point = container.globalToLocal( new Point(event.stageX, event.stageY) );
  908. // "local coordinates" = the coordinate system that is relative to the piece that moves around.
  909. // matrix describes the current rotation and helps us to go from container to local coordinates
  910. tempMatrix.identity();
  911. tempMatrix.rotate( toRadians( originalGeometry.rotation ) );
  912. // The inverse matrix helps us to go from local to container coordinates
  913. var invMatrix:Matrix = tempMatrix.clone();
  914. invMatrix.invert();
  915. // The point where we pressed the mouse down in local coordinates
  916. var localOriginalMousePoint:Point = invMatrix.transformPoint( containerOriginalMousePoint );
  917. // The point where the mouse is currently in local coordinates
  918. var localMousePoint:Point = invMatrix.transformPoint( containerMousePoint );
  919. // How far along the X axis (in local coordinates) has the mouse been moved? This is the amount the user has tried to resize the object
  920. var resizeDistance:Number = localMousePoint.x - localOriginalMousePoint.x;
  921. // So our new width is the original width plus that resize amount
  922. translation.width += resizeDistance;
  923. // applyConstraints(translation, currentDragRole );
  924. // // Now, that we've resize the object, we need to know where the upper left corner should get moved to because when we resize left, we have to move left.
  925. // var translationp:Point = matrix.transformPoint( zero );
  926. //
  927. // translation.x += translationp.x;
  928. // translation.y += translationp.y;
  929. }
  930. protected function applyResizeDown( event:MouseEvent, translation:DragGeometry ) : void
  931. {
  932. var containerOriginalMousePoint:Point = container.globalToLocal(new Point( mouseDownPoint.x, mouseDownPoint.y ));
  933. var containerMousePoint:Point = container.globalToLocal( new Point(event.stageX, event.stageY) );
  934. // "local coordinates" = the coordinate system that is relative to the piece that moves around.
  935. // matrix describes the current rotation and helps us to go from container to local coordinates
  936. tempMatrix.identity();
  937. tempMatrix.rotate( toRadians( originalGeometry.rotation ) );
  938. // The inverse matrix helps us to go from local to container coordinates
  939. var invMatrix:Matrix = tempMatrix.clone();
  940. invMatrix.invert();
  941. // The point where we pressed the mouse down in local coordinates
  942. var localOriginalMousePoint:Point = invMatrix.transformPoint( containerOriginalMousePoint );
  943. // The point where the mouse is currently in local coordinates
  944. var localMousePoint:Point = invMatrix.transformPoint( containerMousePoint );
  945. // How far along the X axis (in local coordinates) has the mouse been moved? This is the amount the user has tried to resize the object
  946. var resizeDistance:Number = localMousePoint.y - localOriginalMousePoint.y;
  947. // So our new width is the original width plus that resize amount
  948. translation.height += resizeDistance;
  949. // applyConstraints(translation, currentDragRole );
  950. // // Now, that we've resize the object, we need to know where the upper left corner should get moved to because when we resize left, we have to move left.
  951. // var translationp:Point = matrix.transformPoint( zero );
  952. //
  953. // translation.x += translationp.x;
  954. // translation.y += translationp.y;
  955. }
  956. protected function applyResizeLeft( event:MouseEvent, translation:DragGeometry ) : void
  957. {
  958. var containerOriginalMousePoint:Point = container.globalToLocal(new Point( mouseDownPoint.x, mouseDownPoint.y ));
  959. var containerMousePoint:Point = container.globalToLocal( new Point(event.stageX, event.stageY) );
  960. // "local coordinates" = the coordinate system that is relative to the piece that moves around.
  961. // matrix describes the current rotation and helps us to go from container to local coordinates
  962. tempMatrix.identity();
  963. tempMatrix.rotate( toRadians( originalGeometry.rotation ) );
  964. // The inverse matrix helps us to go from local to container coordinates
  965. var invMatrix:Matrix = tempMatrix.clone();
  966. invMatrix.invert();
  967. // The point where we pressed the mouse down in local coordinates
  968. var localOriginalMousePoint:Point = invMatrix.transformPoint( containerOriginalMousePoint );
  969. // The point where the mouse is currently in local coordinates
  970. var localMousePoint:Point = invMatrix.t