WindowShade.as 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. package com.imt.intimamedia.helpers
  2. {
  3. import flash.events.MouseEvent;
  4. import flexlib.events.WindowShadeEvent;
  5. import mx.core.EdgeMetrics;
  6. import mx.core.IFactory;
  7. import mx.core.LayoutContainer;
  8. import mx.core.ScrollPolicy;
  9. import mx.effects.Resize;
  10. import mx.effects.effectClasses.ResizeInstance;
  11. import mx.events.EffectEvent;
  12. import mx.events.PropertyChangeEvent;
  13. import mx.styles.CSSStyleDeclaration;
  14. import mx.styles.StyleManager;
  15. import mx.utils.StringUtil;
  16. /**
  17. * This is the icon displayed on the headerButton when the WindowShade is in the open state.
  18. */
  19. [Style(name="openIcon", type="Class", inherit="no")]
  20. /**
  21. * This is the icon displayed on the headerButton when the WindowShade is in the closed state.
  22. */
  23. [Style(name="closeIcon", type="Class", inherit="no")]
  24. /**
  25. * The duration of the WindowShade opening transition, in milliseconds. The value 0 specifies no transition.
  26. *
  27. * @default 250
  28. */
  29. [Style(name="openDuration", type="Number", format="Time", inherit="no")]
  30. /**
  31. * The duration of the WindowShade closing transition, in milliseconds. The value 0 specifies no transition.
  32. *
  33. * @default 250
  34. */
  35. [Style(name="closeDuration", type="Number", format="Time", inherit="no")]
  36. /**
  37. * The class from which the headerButton will be instantiated. Must be mx.controls.Button
  38. * or a subclass.
  39. *
  40. * @default mx.controls.Button
  41. */
  42. [Style(name="headerClass", type="Class", inherit="no")]
  43. /**
  44. * Name of CSS style declaration that specifies styles for the headerButton.
  45. */
  46. [Style(name="headerStyleName", type="String", inherit="no")]
  47. /**
  48. * Alignment of text on the headerButton. The value set for this style is used as
  49. * the textAlign style on the headerButton. Valid values are "left", "center" and "right".
  50. *
  51. * @default "right"
  52. */
  53. [Style(name="headerTextAlign", type="String", inherit="no")]
  54. /**
  55. * If true, the value of the headerButton's <code>toggle</code> property will be set to true;
  56. * otherwise the <code>toggle</code> property will be left in its default state.
  57. *
  58. * @default false
  59. */
  60. [Style(name="toggleHeader", type="Boolean", inherit="no")]
  61. /**
  62. * Dispatched when the <code>opened</code> property is changed, either through user action
  63. * or programatically. This event is cancelable. When cancelled through a call to Event.preventDefault(),
  64. * the <code>opened</code> property will be restored to its previous state.
  65. *
  66. * @eventType flexlib.events.WindowShadeEvent.OPENED_CHANGED
  67. */
  68. [Event(name="openedChanged", type="flexlib.events.WindowShadeEvent")]
  69. /**
  70. * Dispatched when the WindowShade is about to be opened.
  71. *
  72. * <p>In most cases, an event of this type will be followed by an event of type WindowShadeEvent.OPEN_END (<code>openEnd</code>); however,
  73. * if the user clicks the header button before the closing transition has run to completion, the <code>openEnd</code> event will
  74. * not be dispatched, since the WindowShade will not be left in the opened state.</p>
  75. *
  76. * @eventType flexlib.events.WindowShadeEvent.OPEN_BEGIN
  77. */
  78. [Event(name="openBegin", type="flexlib.events.WindowShadeEvent")]
  79. /**
  80. * Dispatched when the WindowShade has finished opening. This event cannot be cancelled.
  81. *
  82. * @eventType flexlib.events.WindowShadeEvent.OPEN_END
  83. */
  84. [Event(name="openEnd", type="flexlib.events.WindowShadeEvent")]
  85. /**
  86. * Dispatched when the WindowShade is about to be closed. This event cannot be cancelled.
  87. *
  88. * <p>In most cases, an event of this type will be followed by an event of type WindowShadeEvent.CLOSE_END (<code>closeEnd</code>); however,
  89. * if the user clicks the header button before the closing transition has run to completion, the <code>closeEnd</code> event will
  90. * not be dispatched, since the WindowShade will not be left in the closed state.</p>
  91. *
  92. * @eventType flexlib.events.WindowShadeEvent.CLOSE_BEGIN
  93. */
  94. [Event(name="closeBegin", type="flexlib.events.WindowShadeEvent")]
  95. /**
  96. * Dispatched when the WindowShade has finished closing. This event cannot be cancelled.
  97. *
  98. * @eventType flexlib.events.WindowShadeEvent.CLOSE_END
  99. */
  100. [Event(name="closeEnd", type="flexlib.events.WindowShadeEvent")]
  101. /**
  102. * This control displays a button, which when clicked, will cause a panel to "unroll" beneath
  103. * it like a windowshade being pulled down; or if the panel is already displayed it
  104. * will be "rolled up" like a windowshade being rolled up. When multiple WindowShades are stacked
  105. * in a VBox, the result will be similar to an mx.containers.Accordian container, except that multiple
  106. * WindowShades can be opened simultaneously whereas an Accordian acts like a tab navigator, with only
  107. * one panel visible at a time.
  108. */
  109. public class WindowShade extends LayoutContainer {
  110. private static var styleDefaults:Object = {
  111. openDuration:250
  112. ,closeDuration:250
  113. ,paddingTop:10
  114. ,headerClass:WindowShadeButton
  115. ,headerTextAlign:"left"
  116. ,toggleHeader:false
  117. ,headerStyleName:null
  118. };
  119. private static var classConstructed:Boolean = constructClass();
  120. private static function constructClass():Boolean {
  121. var css:CSSStyleDeclaration = StyleManager.getStyleDeclaration("WindowShade")
  122. var changed:Boolean = false;
  123. if(!css) {
  124. // If there is no CSS definition for WindowShade,
  125. // then create one and set the default value.
  126. css = new CSSStyleDeclaration();
  127. changed = true;
  128. }
  129. // make sure we have a valid values for each style. If not, set the defaults.
  130. for(var styleProp:String in styleDefaults) {
  131. if(!StyleManager.isValidStyleValue(css.getStyle(styleProp))) {
  132. css.setStyle(styleProp, styleDefaults[styleProp]);
  133. changed = true;
  134. }
  135. }
  136. if(changed) {
  137. StyleManager.setStyleDeclaration("WindowShade", css, true);
  138. }
  139. return true;
  140. }
  141. /**
  142. * @private
  143. * A reference to the Button that will be used for the header. Must always be a Button or subclass of Button.
  144. */
  145. private var _headerButton : WindowShadeButton = null;
  146. private var headerChanged:Boolean;
  147. /**
  148. * @private
  149. * The header renderer factory that will get used to create the header.
  150. */
  151. private var _headerRenderer:IFactory;
  152. /**
  153. * To control the header used on the WindowShade component you can either set the <code>headerClass</code> or the
  154. * <code>headerRenderer</code>. The <code>headerRenderer</code> works similar to the itemRenderer of a List control.
  155. * You can set this using MXML using any Button control. This would let you customize things like button skin. You could
  156. * even combine this with the CanvasButton component to make complex headers.
  157. */
  158. public function set headerRenderer(value:IFactory):void {
  159. _headerRenderer = value;
  160. headerChanged = true;
  161. invalidateProperties();
  162. }
  163. public function get headerRenderer():IFactory {
  164. return _headerRenderer;
  165. }
  166. /**
  167. * @private
  168. * Boolean dirty flag to let us know if we need to change the icon in the commitProperties method.
  169. */
  170. private var _openedChanged:Boolean = false;
  171. public function WindowShade() {
  172. super();
  173. //default scroll policies are off
  174. this.verticalScrollPolicy = ScrollPolicy.OFF;
  175. this.horizontalScrollPolicy = ScrollPolicy.OFF;
  176. addEventListener(EffectEvent.EFFECT_END, onEffectEnd);
  177. }
  178. protected function createOrReplaceHeaderButton():void {
  179. if(_headerButton) {
  180. _headerButton.removeEventListener(MouseEvent.CLICK, headerButton_clickHandler);
  181. if(rawChildren.contains(_headerButton)) {
  182. rawChildren.removeChild(_headerButton);
  183. }
  184. }
  185. if(_headerRenderer) {
  186. _headerButton = _headerRenderer.newInstance() as WindowShadeButton;
  187. }
  188. else {
  189. var headerClass:Class = getStyle("headerClass");
  190. _headerButton = new headerClass();
  191. }
  192. applyHeaderButtonStyles(_headerButton);
  193. _headerButton.addEventListener(MouseEvent.CLICK, headerButton_clickHandler);
  194. rawChildren.addChild(_headerButton);
  195. // Fix for Issue #85
  196. _headerButton.tabEnabled = false;
  197. }
  198. protected function applyHeaderButtonStyles(button:WindowShadeButton):void {
  199. button.setStyle("textAlign", getStyle("headerTextAlign"));
  200. var headerStyleName:String = getStyle("headerStyleName");
  201. if(headerStyleName) {
  202. headerStyleName = StringUtil.trim(headerStyleName);
  203. button.styleName = headerStyleName;
  204. }
  205. button.toggle = getStyle("toggleHeader");
  206. button.label = label;
  207. if(_opened) {
  208. button.setStyle('icon', getStyle("openIcon"));
  209. }
  210. else {
  211. button.setStyle('icon', getStyle("closeIcon"));
  212. }
  213. if(button.toggle) {
  214. button.selected = _opened;
  215. }
  216. }
  217. /**
  218. * The text that appears on the headerButton.
  219. */
  220. override public function get label():String {
  221. // This override is here only to keep the ASDoc tool from incorrectly marking this a write-only property.
  222. return super.label;
  223. }
  224. /**
  225. * @private
  226. */
  227. override public function set label(value:String):void {
  228. super.label = value;
  229. if(_headerButton) _headerButton.label = value;
  230. }
  231. /**
  232. * @private
  233. */
  234. private var _opened:Boolean = true;
  235. /**
  236. * Sets or gets the state of this WindowShade, either opened (true) or closed (false).
  237. */
  238. public function get opened():Boolean {
  239. return _opened;
  240. }
  241. private var _headerLocation:String = "top";
  242. [Bindable]
  243. [Inspectable(enumeration="top,bottom", defaultValue="top")]
  244. /**
  245. * Specifies where the header button is placed relative tot he content of this WindowShade. Possible
  246. * values are <code>top</code> and <code>bottom</code>.
  247. */
  248. public function set headerLocation(value:String):void {
  249. _headerLocation = value;
  250. invalidateSize();
  251. invalidateDisplayList();
  252. }
  253. public function get headerLocation():String {
  254. return _headerLocation;
  255. }
  256. /**
  257. * @private
  258. */
  259. [Bindable]
  260. public function set opened(value:Boolean):void {
  261. var old:Boolean = _opened;
  262. _opened = value;
  263. _openedChanged = _openedChanged || (old != _opened);
  264. if(_openedChanged && initialized) {
  265. // we only want to dispatch the WindowShadeEvent.OPENED_CHANGED when the property actually changes. The _openedChanged
  266. // flag may be set from a previous call with the same value. In that case we want to leave it set
  267. // for the commitProperties() method.
  268. if((old != _opened) && willTrigger(WindowShadeEvent.OPENED_CHANGED)) {
  269. var evt:WindowShadeEvent = new WindowShadeEvent(WindowShadeEvent.OPENED_CHANGED, false, true);
  270. dispatchEvent(evt);
  271. var cancelled:Boolean = evt.isDefaultPrevented();
  272. if(evt.isDefaultPrevented()) {
  273. // restore the old setting. We use callLater so it happens after the PropertyChangeEvent fired
  274. // by the binding wrapper.
  275. callLater(restoreOpened, [old]);
  276. return;
  277. }
  278. }
  279. measure();
  280. runResizeEffect();
  281. invalidateProperties();
  282. }
  283. }
  284. /**
  285. * @private
  286. *
  287. * This exists to allow us to restore a previous opened state when a WindowShadeEvent.OPENED_CHANGED event is
  288. * cancelled, while bypassing the code that changes the visual state of the WindowShade and dispatches the WindowShadeEvent.
  289. */
  290. protected function restoreOpened(value:Boolean):void {
  291. var old:Boolean = _opened;
  292. _opened = value;
  293. _openedChanged = _openedChanged || (old != _opened);
  294. if(_opened != old) {
  295. dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "opened", old, _opened));
  296. }
  297. }
  298. /**
  299. * @private
  300. */
  301. override public function styleChanged(styleProp:String):void {
  302. super.styleChanged(styleProp);
  303. if(styleProp == "headerClass") {
  304. headerChanged = true;
  305. invalidateProperties();
  306. }
  307. else if(styleProp == "headerStyleName" || styleProp == "headerTextAlign" || styleProp == "toggleHeader"
  308. || styleProp == "openIcon" || styleProp == "closeIcon") {
  309. applyHeaderButtonStyles(_headerButton);
  310. }
  311. invalidateDisplayList();
  312. }
  313. /**
  314. * @private
  315. */
  316. override protected function createChildren():void {
  317. super.createChildren();
  318. createOrReplaceHeaderButton();
  319. }
  320. /**
  321. * @private
  322. */
  323. override protected function commitProperties():void {
  324. super.commitProperties();
  325. if(headerChanged) {
  326. createOrReplaceHeaderButton();
  327. headerChanged = false;
  328. }
  329. if(_openedChanged) {
  330. if(_opened) {
  331. _headerButton.setStyle('icon', getStyle("openIcon"));
  332. }
  333. else {
  334. _headerButton.setStyle('icon', getStyle("closeIcon"));
  335. }
  336. _openedChanged = false;
  337. }
  338. }
  339. /**
  340. * @private
  341. */
  342. override protected function updateDisplayList(w:Number, h:Number):void {
  343. super.updateDisplayList(w, h);
  344. if(_headerLocation == "top") {
  345. _headerButton.move(0,0);
  346. }
  347. else if(_headerLocation == "bottom") {
  348. _headerButton.move(0,h - _headerButton.getExplicitOrMeasuredHeight());
  349. }
  350. _headerButton.setActualSize(w, _headerButton.getExplicitOrMeasuredHeight());
  351. }
  352. /**
  353. * @private
  354. */
  355. private var _viewMetrics:EdgeMetrics;
  356. override public function get viewMetrics():EdgeMetrics
  357. {
  358. // The getViewMetrics function needs to return its own object.
  359. // Rather than allocating a new one each time, we'll allocate
  360. // one once and then hold a pointer to it.
  361. if (!_viewMetrics)
  362. _viewMetrics = new EdgeMetrics(0, 0, 0, 0);
  363. var vm:EdgeMetrics = _viewMetrics;
  364. var o:EdgeMetrics = super.viewMetrics;
  365. vm.left = o.left;
  366. vm.top = o.top;
  367. vm.right = o.right;
  368. vm.bottom = o.bottom;
  369. var hHeight:Number = _headerButton.getExplicitOrMeasuredHeight();
  370. if (!isNaN(hHeight)) {
  371. if(_headerLocation == "top") {
  372. vm.top += hHeight;
  373. }
  374. else if(_headerLocation == "bottom") {
  375. vm.bottom += hHeight;
  376. }
  377. }
  378. return vm;
  379. }
  380. public var closedHeight:Number = 0;
  381. /**
  382. * @private
  383. */
  384. override protected function measure():void {
  385. super.measure();
  386. if(_opened) {
  387. //if this WindowShade is opened then we have to include the height of the header button
  388. //measuredHeight += _headerButton.getExplicitOrMeasuredHeight();
  389. }
  390. else {
  391. //if the WindowShade is closed then the height is only the height of the header button
  392. measuredHeight = closedHeight + _headerButton.getExplicitOrMeasuredHeight();
  393. }
  394. }
  395. /**
  396. * @private
  397. */
  398. private var resize:Resize;
  399. /**
  400. * @private
  401. */
  402. private var resizeInstance:ResizeInstance;
  403. /**
  404. * @private
  405. */
  406. private var resetExplicitHeight:Boolean;
  407. private var transitionCompleted:Boolean = true;
  408. /**
  409. * @private
  410. */
  411. protected function runResizeEffect():void {
  412. if(resize && resize.isPlaying) {
  413. // The user has clicked the header button before an open or close transition has run
  414. // to completion. We'll set the transitionCompleted flag to false to prevent the
  415. // completion event from being dispatched in onEffectEnd.
  416. transitionCompleted = false;
  417. // before the call to end() returns, the onEffectEnd method will have been called
  418. // for the currently playing resize.
  419. resize.end();
  420. }
  421. transitionCompleted = true;
  422. var beginEvent:String = _opened ? WindowShadeEvent.OPEN_BEGIN : WindowShadeEvent.CLOSE_BEGIN;
  423. if(willTrigger(beginEvent)) {
  424. dispatchEvent(new WindowShadeEvent(beginEvent, false, false));
  425. }
  426. var duration:Number = _opened ? getStyle("openDuration") : getStyle("closeDuration");
  427. if(duration == 0) {
  428. this.setActualSize(getExplicitOrMeasuredWidth(), measuredHeight);
  429. invalidateSize();
  430. invalidateDisplayList();
  431. var endEvent:String = _opened ? WindowShadeEvent.OPEN_END : WindowShadeEvent.CLOSE_END;
  432. if(willTrigger(endEvent)) {
  433. dispatchEvent(new WindowShadeEvent(endEvent, false, false));
  434. }
  435. return;
  436. }
  437. resize = new Resize(this);
  438. // If this WindowShade currently has no explicit height set, we want to
  439. // restore that state when the resize effect is finished, in the onEffectEnd method.
  440. // If it does, then the final height set by the effect will be retained.
  441. resetExplicitHeight = isNaN(explicitHeight);
  442. resize.heightTo = Math.min(maxHeight, measuredHeight);
  443. resize.duration = duration;
  444. var instances:Array = resize.play();
  445. if(instances && instances.length) {
  446. resizeInstance = instances[0];
  447. }
  448. }
  449. /**
  450. * @private
  451. */
  452. protected function onEffectEnd(evt:EffectEvent):void {
  453. // Make sure this is our effect ending
  454. if(evt.effectInstance == resizeInstance) {
  455. if(resetExplicitHeight) explicitHeight = NaN;
  456. resizeInstance = null;
  457. // the transitionCompleted flag will be false if the user clicked the headerButton
  458. // twice in succession. We only want to fire events for transitions that run
  459. // all the way to completion.
  460. if(transitionCompleted) {
  461. var endEvent:String = _opened ? WindowShadeEvent.OPEN_END : WindowShadeEvent.CLOSE_END;
  462. if(willTrigger(endEvent)) {
  463. dispatchEvent(new WindowShadeEvent(endEvent, false, false));
  464. }
  465. }
  466. }
  467. }
  468. /**
  469. * @private
  470. */
  471. protected function headerButton_clickHandler(event:MouseEvent):void {
  472. //opened = !_opened;
  473. }
  474. }
  475. }