PageRenderTime 43ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/ui/source/temple/ui/buttons/behaviors/ButtonBehavior.as

http://templelibrary.googlecode.com/
ActionScript | 556 lines | 372 code | 63 blank | 121 comment | 66 complexity | 1767ea7f64028968d9c75f8cb6296937 MD5 | raw file
  1. /*
  2. * Temple Library for ActionScript 3.0
  3. * Copyright Š MediaMonks B.V.
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or without
  7. * modification, are permitted provided that the following conditions are met:
  8. * 1. Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * 2. Redistributions in binary form must reproduce the above copyright
  11. * notice, this list of conditions and the following disclaimer in the
  12. * documentation and/or other materials provided with the distribution.
  13. * 3. All advertising materials mentioning features or use of this software
  14. * must display the following acknowledgement:
  15. * This product includes software developed by MediaMonks B.V.
  16. * 4. Neither the name of MediaMonks B.V. nor the
  17. * names of its contributors may be used to endorse or promote products
  18. * derived from this software without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY MEDIAMONKS B.V. ''AS IS'' AND ANY
  21. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  22. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  23. * DISCLAIMED. IN NO EVENT SHALL MEDIAMONKS B.V. BE LIABLE FOR ANY
  24. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  25. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  26. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  27. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. *
  31. *
  32. * Note: This license does not apply to 3rd party classes inside the Temple
  33. * repository with their own license!
  34. */
  35. package temple.ui.buttons.behaviors
  36. {
  37. import temple.core.errors.TempleError;
  38. import temple.core.errors.throwError;
  39. import temple.core.utils.CoreTimer;
  40. import temple.ui.buttons.BaseButton;
  41. import temple.ui.eventtunneling.EventTunneler;
  42. import temple.ui.focus.FocusManager;
  43. import temple.utils.keys.KeyCode;
  44. import flash.display.DisplayObject;
  45. import flash.display.DisplayObjectContainer;
  46. import flash.display.InteractiveObject;
  47. import flash.display.Sprite;
  48. import flash.display.Stage;
  49. import flash.events.Event;
  50. import flash.events.FocusEvent;
  51. import flash.events.KeyboardEvent;
  52. import flash.events.MouseEvent;
  53. import flash.events.TimerEvent;
  54. import flash.utils.Dictionary;
  55. /**
  56. * @eventType temple.ui.buttons.behaviors.ButtonEvent.UPDATE
  57. */
  58. [Event(name = "ButtonEvent.update", type = "temple.ui.buttons.behaviors.ButtonEvent")]
  59. /**
  60. * The ButtonBehavior can make every DisplayObject act like a button. The ButtonBehavior keeps track of status of the button.
  61. * The status of the button indicates if the mouse is currently up, over or down the button or if the button is selected of disabled.
  62. * The the reaction of mouseOver and mouseOut Events can be delayed.
  63. *
  64. * <p>The status of the buttons is dispatch in a tunneled ButtonEvent on all the children (and grandchildren etc.) of the button.
  65. * This Button is used by ButtonDesignBehaviors to show the current status.</p>
  66. *
  67. * @see temple.ui.eventtunneling.EventTunneler
  68. * @see temple.ui.buttons.behaviors.IButtonDesignBehavior
  69. *
  70. * @author Thijs Broerse
  71. */
  72. public class ButtonBehavior extends AbstractButtonBehavior
  73. {
  74. private static const _dictionary:Dictionary = new Dictionary(true);
  75. /**
  76. * Returns the ButtonBehavior of a DisplayObject if the DisplayObject has ButtonBehavior. Otherwise null is returned.
  77. */
  78. public static function getInstance(target:DisplayObject):ButtonBehavior
  79. {
  80. return ButtonBehavior._dictionary[target] as ButtonBehavior;
  81. }
  82. private var _stage:Stage;
  83. private var _eventTunneler:EventTunneler;
  84. private var _inDelayTimer:CoreTimer;
  85. private var _outDelayTimer:CoreTimer;
  86. private var _outOnDragOut:Boolean = true;
  87. private var _downOnDragIn:Boolean = false;
  88. private var _clickOnEnter:Boolean = false;
  89. private var _clickOnSpacebar:Boolean = false;
  90. /**
  91. *
  92. */
  93. public function ButtonBehavior(target:InteractiveObject)
  94. {
  95. super(target);
  96. if (ButtonBehavior._dictionary[target]) throwError(new TempleError(this, target + " already has ButtonBehavior"));
  97. ButtonBehavior._dictionary[target] = this;
  98. if (target is InteractiveObject)
  99. {
  100. if (target is DisplayObjectContainer)
  101. {
  102. (target as DisplayObjectContainer).mouseChildren = false;
  103. // act as button
  104. if (target is Sprite)
  105. {
  106. this._eventTunneler = new EventTunneler(target as Sprite, ButtonEvent.UPDATE);
  107. (target as Sprite).buttonMode = true;
  108. if (target.hasOwnProperty(BaseButton.hitAreaInstanceName) && target[BaseButton.hitAreaInstanceName] is Sprite)
  109. {
  110. (target as Sprite).hitArea = target[BaseButton.hitAreaInstanceName] as Sprite;
  111. (target[BaseButton.hitAreaInstanceName] as Sprite).visible = false;
  112. }
  113. }
  114. }
  115. }
  116. target.addEventListener(MouseEvent.ROLL_OVER, this.handleRollOver);
  117. target.addEventListener(MouseEvent.ROLL_OUT, this.handleRollOut);
  118. target.addEventListener(MouseEvent.MOUSE_DOWN, this.handleMouseDown);
  119. target.addEventListener(MouseEvent.MOUSE_UP, this.handleMouseUp);
  120. target.addEventListener(MouseEvent.CLICK, this.handleClick);
  121. target.addEventListener(FocusEvent.FOCUS_IN, this.handleFocusIn);
  122. target.addEventListener(FocusEvent.FOCUS_OUT, this.handleFocusOut);
  123. target.addEventListener(KeyboardEvent.KEY_DOWN, this.handleKeyDown);
  124. target.addEventListener(KeyboardEvent.KEY_UP, this.handleKeyUp);
  125. this._inDelayTimer = new CoreTimer(0);
  126. this._inDelayTimer.addEventListener(TimerEvent.TIMER, this.handleInDelay);
  127. this._outDelayTimer = new CoreTimer(0);
  128. this._outDelayTimer.addEventListener(TimerEvent.TIMER, this.handleOutDelay);
  129. if (target.stage)
  130. {
  131. this._stage = target.stage;
  132. this._stage.addEventListener(Event.MOUSE_LEAVE, this.handleMouseLeave);
  133. this._stage.addEventListener(Event.DEACTIVATE, this.handleMouseLeave);
  134. FocusManager.init(this._stage);
  135. }
  136. else
  137. {
  138. target.addEventListener(Event.ADDED_TO_STAGE, this.handleAddedToStage);
  139. }
  140. }
  141. /**
  142. * @inheritDoc
  143. */
  144. override public function set over(value:Boolean):void
  145. {
  146. if (this.over != value)
  147. {
  148. super.over = value;
  149. this.update();
  150. }
  151. }
  152. /**
  153. * @inheritDoc
  154. */
  155. override public function set lockOver(value:Boolean):void
  156. {
  157. if (this.lockOver != value)
  158. {
  159. super.lockOver = value;
  160. this.update();
  161. }
  162. }
  163. /**
  164. * @inheritDoc
  165. */
  166. override public function set down(value:Boolean):void
  167. {
  168. if (this.down != value)
  169. {
  170. super.down = value;
  171. this.update();
  172. }
  173. }
  174. /**
  175. * @inheritDoc
  176. */
  177. override public function set lockDown(value:Boolean):void
  178. {
  179. if (this.lockDown != value)
  180. {
  181. super.lockDown = value;
  182. this.update();
  183. }
  184. }
  185. /**
  186. * @inheritDoc
  187. */
  188. override public function set selected(value:Boolean):void
  189. {
  190. if (this.selected != value)
  191. {
  192. super.selected = value;
  193. this.update();
  194. }
  195. }
  196. /**
  197. * @inheritDoc
  198. */
  199. override public function set disabled(value:Boolean):void
  200. {
  201. if (this.disabled != value)
  202. {
  203. super.disabled = value;
  204. this.update();
  205. }
  206. }
  207. /**
  208. * @inheritDoc
  209. */
  210. override public function set focus(value:Boolean):void
  211. {
  212. if (this.focus != value)
  213. {
  214. super.focus = value;
  215. this.update();
  216. }
  217. }
  218. /**
  219. * Get or set the delay before mouse over is performed, in milliseconds.
  220. */
  221. public function get inDelay():Number
  222. {
  223. return this._inDelayTimer.delay;
  224. }
  225. /**
  226. * @private
  227. */
  228. public function set inDelay(value:Number):void
  229. {
  230. if (this._inDelayTimer.delay != value && !isNaN(value))
  231. {
  232. this._inDelayTimer.delay = value;
  233. }
  234. }
  235. /**
  236. * (Hysteresis) delay before mouse out action is performed, in milliseconds.
  237. */
  238. public function get outDelay():Number
  239. {
  240. return this._outDelayTimer.delay;
  241. }
  242. /**
  243. * @private
  244. */
  245. public function set outDelay(value:Number):void
  246. {
  247. if (this._outDelayTimer.delay != value && !isNaN(value))
  248. {
  249. this._outDelayTimer.delay = value;
  250. }
  251. }
  252. /**
  253. * Indicates if the ButtonBehavior should go in out state (true) when dragging (mouse out while mouse down) out the target.
  254. * If set to false, buttons stays in down state when dragging out. Default: true
  255. */
  256. public function get outOnDragOut():Boolean
  257. {
  258. return this._outOnDragOut;
  259. }
  260. /**
  261. * @private
  262. */
  263. public function set outOnDragOut(value:Boolean):void
  264. {
  265. this._outOnDragOut = value;
  266. }
  267. /**
  268. * Indicates if the ButtonBehavior should go in down state (true) when dragging (mouse in while mouse down) in the target.
  269. */
  270. public function get downOnDragIn():Boolean
  271. {
  272. return this._downOnDragIn;
  273. }
  274. /**
  275. * @private
  276. */
  277. public function set downOnDragIn(value:Boolean):void
  278. {
  279. this._downOnDragIn = value;
  280. }
  281. /**
  282. * Indicates if the target should dispatch a CLICK event when the target has focus and the spacebar is pressed. Default: true
  283. */
  284. public function get clickOnSpacebar():Boolean
  285. {
  286. return this._clickOnSpacebar;
  287. }
  288. /**
  289. * @private
  290. */
  291. public function set clickOnSpacebar(value:Boolean):void
  292. {
  293. this._clickOnSpacebar = value;
  294. }
  295. /**
  296. * Indicates if the target should dispatch a CLICK event when the target has focus and the spacebar is pressed. Default: true
  297. */
  298. public function get clickOnEnter():Boolean
  299. {
  300. return this._clickOnEnter;
  301. }
  302. /**
  303. * @private
  304. */
  305. public function set clickOnEnter(value:Boolean):void
  306. {
  307. this._clickOnEnter = value;
  308. }
  309. /**
  310. * Updates all IButtonDesignBehaviors of this ButtonBehavior
  311. */
  312. public function update():void
  313. {
  314. if (this.enabled)
  315. {
  316. if (this.debug) this.logDebug("update: over=" + this.over + ", down=" + this.down + ", selected=" + this.selected + ", disabled=" + this.disabled);
  317. this.displayObject.dispatchEvent(new ButtonEvent(ButtonEvent.UPDATE, this));
  318. this.dispatchEvent(new ButtonEvent(ButtonEvent.UPDATE, this, false));
  319. }
  320. else if (this.debug) this.logWarn("ButtonBehavior is disabled");
  321. }
  322. private function handleRollOver(event:MouseEvent):void
  323. {
  324. if (this.debug) this.logDebug("handleRollOver");
  325. super.over = true;
  326. if (!super.down) super.down = event.buttonDown && this._downOnDragIn;
  327. this.resetTimers();
  328. this._inDelayTimer.start();
  329. }
  330. private function handleRollOut(event:MouseEvent):void
  331. {
  332. if (this.debug) this.logDebug("handleRollOut");
  333. this.resetTimers();
  334. if (this.over)
  335. {
  336. super.over = false;
  337. if (this._outDelayTimer.delay > 0)
  338. {
  339. this._outDelayTimer.start();
  340. }
  341. else
  342. {
  343. this.onOutDelay();
  344. }
  345. }
  346. }
  347. private function handleMouseDown(event:MouseEvent):void
  348. {
  349. if (this.debug) this.logDebug("handleMouseDown");
  350. this.resetTimers();
  351. this.down = true;
  352. }
  353. private function handleMouseUp(event:MouseEvent):void
  354. {
  355. if (this.debug) this.logDebug("handleMouseUp");
  356. this.resetTimers();
  357. this.down = false;
  358. }
  359. private function handleClick(event:MouseEvent):void
  360. {
  361. if (this.debug) this.logDebug("handleClick");
  362. }
  363. private function handleInDelay(event:TimerEvent):void
  364. {
  365. this.resetTimers();
  366. this.update();
  367. }
  368. private function handleOutDelay(event:TimerEvent):void
  369. {
  370. this.onOutDelay();
  371. }
  372. private function onOutDelay():void
  373. {
  374. this.resetTimers();
  375. super.over = false;
  376. if (this.down)
  377. {
  378. if (this._outOnDragOut)
  379. {
  380. this.down = false;
  381. }
  382. else
  383. {
  384. this._stage.addEventListener(MouseEvent.MOUSE_UP, this.handleStageMouseUp, false, 0, true);
  385. }
  386. }
  387. else
  388. {
  389. this.update();
  390. }
  391. }
  392. private function handleAddedToStage(event:Event):void
  393. {
  394. this.displayObject.removeEventListener(Event.ADDED_TO_STAGE, this.handleAddedToStage);
  395. this._stage = this.displayObject.stage;
  396. this._stage.addEventListener(Event.MOUSE_LEAVE, this.handleMouseLeave);
  397. this._stage.addEventListener(Event.DEACTIVATE, this.handleMouseLeave);
  398. FocusManager.init(this._stage);
  399. }
  400. private function resetTimers():void
  401. {
  402. if (this._inDelayTimer) this._inDelayTimer.reset();
  403. if (this._outDelayTimer) this._outDelayTimer.reset();
  404. }
  405. private function handleStageMouseUp(event:MouseEvent):void
  406. {
  407. this._stage.removeEventListener(MouseEvent.MOUSE_UP, this.handleStageMouseUp);
  408. if (this.down)
  409. {
  410. if (this.debug) this.logDebug("handleStageMouseUp");
  411. this.resetTimers();
  412. this.down = false;
  413. }
  414. }
  415. private function handleMouseLeave(event:Event):void
  416. {
  417. if (this.over)
  418. {
  419. if (this.debug) this.logDebug("handleMouseLeave");
  420. super.down = false;
  421. this.over = false;
  422. }
  423. }
  424. private function handleFocusIn(event:FocusEvent):void
  425. {
  426. this.focus = true;
  427. }
  428. private function handleFocusOut(event:FocusEvent):void
  429. {
  430. this.focus = false;
  431. }
  432. private function handleKeyDown(event:KeyboardEvent):void
  433. {
  434. if (this._clickOnEnter && event.keyCode == KeyCode.ENTER || this._clickOnSpacebar && event.keyCode == KeyCode.SPACE)
  435. {
  436. event.stopPropagation();
  437. this.displayObject.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_DOWN, true, false, NaN, NaN, null, event.ctrlKey, event.altKey, event.shiftKey));
  438. this.displayObject.dispatchEvent(new MouseEvent(MouseEvent.CLICK, true, false, NaN, NaN, null, event.ctrlKey, event.altKey, event.shiftKey));
  439. }
  440. }
  441. private function handleKeyUp(event:KeyboardEvent):void
  442. {
  443. if (this._clickOnEnter && event.keyCode == KeyCode.ENTER || this._clickOnSpacebar && event.keyCode == KeyCode.SPACE)
  444. {
  445. event.stopPropagation();
  446. this.displayObject.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_UP, true, false, NaN, NaN, null, event.ctrlKey, event.altKey, event.shiftKey));
  447. }
  448. }
  449. /**
  450. * @inheritDoc
  451. */
  452. override public function destruct():void
  453. {
  454. if (this.target) delete ButtonBehavior._dictionary[this.target];
  455. if (this.displayObject)
  456. {
  457. this.displayObject.removeEventListener(MouseEvent.ROLL_OVER, this.handleRollOver);
  458. this.displayObject.removeEventListener(MouseEvent.ROLL_OUT, this.handleRollOut);
  459. this.displayObject.removeEventListener(MouseEvent.MOUSE_DOWN, this.handleMouseDown);
  460. this.displayObject.removeEventListener(MouseEvent.MOUSE_UP, this.handleMouseUp);
  461. this.displayObject.removeEventListener(MouseEvent.CLICK, this.handleClick);
  462. this.displayObject.removeEventListener(Event.ADDED_TO_STAGE, this.handleAddedToStage);
  463. this.displayObject.removeEventListener(FocusEvent.FOCUS_IN, this.handleFocusIn);
  464. this.displayObject.removeEventListener(FocusEvent.FOCUS_OUT, this.handleFocusOut);
  465. this.displayObject.removeEventListener(KeyboardEvent.KEY_DOWN, this.handleKeyDown);
  466. this.displayObject.removeEventListener(KeyboardEvent.KEY_UP, this.handleKeyUp);
  467. }
  468. if (this._inDelayTimer)
  469. {
  470. this._inDelayTimer.destruct();
  471. this._inDelayTimer = null;
  472. }
  473. if (this._outDelayTimer)
  474. {
  475. this._outDelayTimer.destruct();
  476. this._outDelayTimer = null;
  477. }
  478. if (this._stage)
  479. {
  480. this._stage.removeEventListener(MouseEvent.MOUSE_UP, this.handleStageMouseUp);
  481. this._stage.removeEventListener(Event.MOUSE_LEAVE, this.handleMouseLeave);
  482. this._stage.removeEventListener(Event.DEACTIVATE, this.handleMouseLeave);
  483. this._stage = null;
  484. }
  485. if (this._eventTunneler)
  486. {
  487. this._eventTunneler.destruct();
  488. this._eventTunneler = null;
  489. }
  490. super.destruct();
  491. }
  492. }
  493. }