1////////////////////////////////////////////////////////////////////////////////
2//
3//  ADOBE SYSTEMS INCORPORATED
4//  Copyright 2005-2007 Adobe Systems Incorporated
5//  All Rights Reserved.
6//
7//  NOTICE: Adobe permits you to use, modify, and distribute this file
8//  in accordance with the terms of the license agreement accompanying it.
9//
10////////////////////////////////////////////////////////////////////////////////
11
12package mx.effects.effectClasses
13{
14
15import flash.events.Event;
16import mx.containers.Panel;
17import mx.core.Application;
18import mx.core.Container;
19import mx.core.IUIComponent;
20import mx.core.ScrollPolicy;
21import mx.core.mx_internal;
22import mx.effects.EffectManager;
23import mx.events.EffectEvent;
24import mx.events.ResizeEvent;
25import mx.styles.IStyleClient;
26
27use namespace mx_internal;
28
29/**
30 *  The ResizeInstance class implements the instance class
31 *  for the Resize effect.
32 *  Flex creates an instance of this class when it plays a Resize effect;
33 *  you do not create one yourself.
34 *
35 *  <p>Every effect class that is a subclass of the TweenEffect class
36 *  supports the following events:</p>
37 *
38 *  <ul>
39 *    <li><code>tweenEnd</code>: Dispatched when the tween effect ends. </li>
40 *
41 *    <li><code>tweenUpdate</code>: Dispatched every time a TweenEffect
42 *      class calculates a new value.</li>
43 *  </ul>
44 *
45 *  <p>The event object passed to the event listener for these events is of type TweenEvent.
46 *  The TweenEvent class defines the property <code>value</code>, which contains
47 *  the tween value calculated by the effect.
48 *  For the Resize effect,
49 *  the <code>TweenEvent.value</code> property contains a 2-item Array, where: </p>
50 *  <ul>
51 *    <li>value[0]:Number  A value between the values of the <code>Resize.widthFrom</code>
52 *    and <code>Resize.widthTo</code> property.</li>
53 *
54 *    <li>value[1]:Number  A value between the values of the <code>Resize.heightFrom</code>
55 *    and <code>Resize.heightTo</code> property.</li>
56 *  </ul>
57 *
58 *  @see mx.effects.Resize
59 *  @see mx.events.TweenEvent
60 */
61public class ResizeInstance extends TweenEffectInstance
62{
63    include "../../core/Version.as";
64
65	//--------------------------------------------------------------------------
66	//
67	//  Constructor
68	//
69	//--------------------------------------------------------------------------
70
71	/**
72	 *  Constructor.
73	 *
74	 *  @param target The Object to animate with this effect.
75	 */
76	public function ResizeInstance(target:Object)
77	{
78		super(target);
79
80		needToLayout = true;
81	}
82
83	//--------------------------------------------------------------------------
84	//
85	//  Variables
86	//
87	//--------------------------------------------------------------------------
88
89	/**
90	 *  @private
91	 */
92	private var restoreVisibleArray:Array;
93
94	/**
95	 *  @private
96	 */
97	private var restoreAutoLayoutArray:Array;
98
99	/**
100	 *  @private
101	 */
102	private var numHideEffectsPlaying:Number = 0;
103
104	/**
105	 *  @private
106	 */
107	private var origPercentHeight:Number;
108
109	/**
110	 *  @private
111	 */
112	private var origPercentWidth:Number;
113
114	/**
115	 *  @private
116	 */
117	private var origExplicitHeight:Number;
118
119	/**
120	 *  @private
121	 */
122	private var origExplicitWidth:Number;
123
124	/**
125	 *  @private
126	 */
127	private var heightSet:Boolean;
128
129	/**
130	 *  @private
131	 */
132	private var widthSet:Boolean;
133
134	/**
135	 *  @private
136	 */
137	private var explicitWidthSet:Boolean;
138
139	/**
140	 *  @private
141	 */
142	private var explicitHeightSet:Boolean;
143
144	/**
145	 *  @private
146	 */
147	private var origVerticalScrollPolicy:String = "";
148
149	/**
150	 *  @private
151	 */
152	private var origHorizontalScrollPolicy:String = "";
153
154	/**
155	 *  @private
156	 */
157	private var parentOrigVerticalScrollPolicy:String = "";
158
159	/**
160	 *  @private
161	 */
162	private var parentOrigHorizontalScrollPolicy:String = "";
163
164	/**
165	 *  @private
166	 *  Stores the left style of the target
167	 */
168	private var left:*;
169
170	/**
171	 *  @private
172	 *  Stores the right style of the target
173	 */
174	private var right:*;
175
176	/**
177	 *  @private
178	 *  Stores the top style of the target
179	 */
180	private var top:*;
181
182	/**
183	 *  @private
184	 *  Stores the bottom style of the target
185	 */
186	private var bottom:*;
187
188	//--------------------------------------------------------------------------
189	//
190	//  Properties
191	//
192	//--------------------------------------------------------------------------
193
194	//----------------------------------
195	//  heightBy
196	//----------------------------------
197
198	/**
199	 *  @private
200	 *  Storage for the heightBy property.
201	 */
202	private var _heightBy:Number;
203
204	/**
205	 *  Number of pixels by which to modify the height of the component.
206	 *  Values may be negative.
207	 */
208	public function get heightBy():Number
209	{
210		return _heightBy;
211	}
212
213	/**
214	 *  @private
215	 */
216	public function set heightBy(value:Number):void
217	{
218		_heightBy = value;
219		heightSet = !isNaN(value);
220	}
221
222	//----------------------------------
223	//  heightFrom
224	//----------------------------------
225
226	/**
227	 *  Initial height. If omitted, Flex uses the current size.
228	 */
229	public var heightFrom:Number;
230
231	//----------------------------------
232	//  heightTo
233	//----------------------------------
234
235	/**
236	 *  @private
237	 *  Storage for the heightTo property.
238	 */
239	private var _heightTo:Number;
240
241	/**
242	 *  Final height, in pixels.
243	 */
244	public function get heightTo():Number
245	{
246		return _heightTo;
247	}
248
249	/**
250	 *  @private
251	 */
252	public function set heightTo(value:Number):void
253	{
254		_heightTo = value;
255		heightSet = !isNaN(value);
256	}
257
258	//----------------------------------
259	//  hideChildrenTargets
260	//----------------------------------
261
262	/**
263	 *  An Array of Panels.
264	 *  The children of these Panels are hidden while the Resize effect plays.
265	 */
266	public var hideChildrenTargets:Array /* of Panel */;
267
268	//----------------------------------
269	//  widthBy
270	//----------------------------------
271
272	/**
273	 *  @private
274	 *  Storage for the widthBy property.
275	 */
276	private var _widthBy:Number;
277
278	/**
279	 *  Number of pixels by which to modify the width of the component.
280	 *  Values may be negative.
281	 */
282	public function get widthBy():Number
283	{
284		return _widthBy;
285	}
286
287	/**
288	 *  @private
289	 */
290	public function set widthBy(value:Number):void
291	{
292		_widthBy = value;
293		widthSet = !isNaN(value);
294	}
295
296	//----------------------------------
297	//  widthFrom
298	//----------------------------------
299
300	/**
301	 *  Initial width. If omitted, Flex uses the current size.
302	 */
303	public var widthFrom:Number;
304
305	//----------------------------------
306	//  widthTo
307	//----------------------------------
308
309	/**
310	 *  @private
311	 *  Storage for the widthTo property.
312	 */
313	private var _widthTo:Number;
314
315	/**
316	 *  Final width, in pixels.
317	 */
318	public function get widthTo():Number
319	{
320		return _widthTo;
321	}
322
323	/**
324	 *  @private
325	 */
326	public function set widthTo(value:Number):void
327	{
328		_widthTo = value;
329		widthSet = !isNaN(value);
330	}
331
332	//--------------------------------------------------------------------------
333	//
334	//  Overridden methods
335	//
336	//--------------------------------------------------------------------------
337
338	/**
339	 *  @private
340	 */
341	override public function initEffect(event:Event):void
342	{
343		super.initEffect(event);
344
345		if (event is ResizeEvent && event.type == ResizeEvent.RESIZE)
346		{
347			if (isNaN(widthBy))
348			{
349				if (isNaN(widthFrom))
350				{
351					widthFrom = ResizeEvent(event).oldWidth;
352				}
353				if (isNaN(widthTo))
354				{
355					_widthTo = target.width;
356				}
357			}
358
359			if (isNaN(heightBy))
360			{
361				if (isNaN(heightFrom))
362				{
363					heightFrom = ResizeEvent(event).oldHeight;
364				}
365
366				if (isNaN(heightTo))
367				{
368					_heightTo = target.height;
369				}
370			}
371		}
372	}
373
374	/**
375	 *  @private
376	 */
377	override public function play():void
378	{
379		// Dispatch an effectStart event from the target.
380		super.play();
381
382		calculateExplicitDimensionChanges();
383
384		// If the target is a Panel, then find all Panel objects that will
385		// be affected by the animation.  Deliver a "resizeStart" event to
386		// each affected Panel, and then wait until the Panel finishes
387		// hiding its children.
388		var childrenHiding:Boolean = hidePanelChildren();
389
390		if (target is IStyleClient)
391		{
392			left = target.getStyle("left");
393			if (left != undefined)
394				target.setStyle("left",undefined);
395
396			right = target.getStyle("right");
397			if (right != undefined)
398				target.setStyle("right",undefined);
399
400			top = target.getStyle("top");
401			if (top != undefined)
402				target.setStyle("top",undefined);
403
404			bottom = target.getStyle("bottom");
405			if (bottom != undefined)
406				target.setStyle("bottom",undefined);
407		}
408
409		if (!childrenHiding)
410			startResizeTween();
411	}
412
413	/**
414	 *  @private
415     */
416	override public function onTweenUpdate(value:Object):void
417	{
418		EffectManager.suspendEventHandling();
419
420		// Use Math.round instead of Math.floor, so that the animation appears
421		// to begin playing after the value has changed by only half a pixel,
422		// instead of waiting for it to change by a whole pixel.
423		// Because of the way that the easing function mimics acceleration,
424		// it can take a while for the animation to get started.
425		target.width = Math.round(value[0]);
426		target.height = Math.round(value[1]);
427
428		// Set a flag indicating that LayoutManager.validateNow()
429		// should be called after we're finished processing
430		// all the effects for this frame.
431		if (tween)
432			tween.needToLayout = true;
433		needToLayout = true;
434
435		EffectManager.resumeEventHandling();
436	}
437
438	/**
439	 *  @private
440     */
441	override public function onTweenEnd(value:Object):void
442	{
443		EffectManager.endVectorEffect(IUIComponent(target));
444
445		// Wait a frame before starting to restore the childrens' visibility.
446		// That way, we have a chance to run a measurement/layout pass with
447		// the final sizes and update the screen.
448		Application.application.callLater(restorePanelChildren);
449
450		super.onTweenEnd(value);
451
452		EffectManager.suspendEventHandling();
453
454		var targetAsContainer:Container;
455		var parentAsContainer:Container;
456
457		// Restore the target's percent and explicit sizes
458		if (!heightSet)
459		{
460			target.percentHeight = origPercentHeight;
461			target.explicitHeight = origExplicitHeight;
462
463			if (origVerticalScrollPolicy != "")
464			{
465				targetAsContainer = target as Container;
466				if (targetAsContainer)
467				{
468					targetAsContainer.verticalScrollPolicy = origVerticalScrollPolicy;
469					origVerticalScrollPolicy = "";
470				}
471			}
472
473			if (parentOrigVerticalScrollPolicy != "" && target.parent)
474			{
475				parentAsContainer = target.parent as Container;
476				if (parentAsContainer)
477				{
478					parentAsContainer.verticalScrollPolicy = parentOrigVerticalScrollPolicy;
479					parentOrigVerticalScrollPolicy = "";
480				}
481			}
482		}
483
484		if (!widthSet)
485		{
486			target.percentWidth = origPercentWidth;
487			target.explicitWidth = origExplicitWidth;
488
489			if (origHorizontalScrollPolicy != "")
490			{
491				targetAsContainer = target as Container;
492				if (targetAsContainer)
493				{
494					targetAsContainer.horizontalScrollPolicy = origHorizontalScrollPolicy;
495					origHorizontalScrollPolicy = "";
496				}
497			}
498
499			if (parentOrigHorizontalScrollPolicy != "" && target.parent)
500			{
501				parentAsContainer = target.parent as Container;
502				if (parentAsContainer)
503				{
504					parentAsContainer.horizontalScrollPolicy = parentOrigHorizontalScrollPolicy;
505					parentOrigHorizontalScrollPolicy = "";
506				}
507			}
508		}
509
510		if (left != undefined)
511			target.setStyle("left",left);
512		if (right != undefined)
513			target.setStyle("right",right);
514		if (top != undefined)
515			target.setStyle("top",top);
516		if (bottom != undefined)
517			target.setStyle("bottom",bottom);
518
519		EffectManager.resumeEventHandling();
520	}
521
522	/**
523	 *  @private
524     */
525	override public function end():void
526	{
527		// If we were waiting for the initial "hide children" effect to
528		// finish playing, then the tween might not be created yet.
529		// In that case, we need to explicitly jump to the end of the Resize,
530		// because the TweenEffect.end() function won't do it for us.
531
532		if (!tween)
533		{
534			calculateExplicitDimensionChanges();
535
536			onTweenEnd(playReversed ?
537					   [ widthFrom, heightFrom ] :
538					   [ widthTo, heightTo ]);
539		}
540
541
542		super.end();
543	}
544
545	//--------------------------------------------------------------------------
546	//
547	//  Methods
548	//
549	//--------------------------------------------------------------------------
550
551	/**
552	 *  @private
553	 */
554	private function startResizeTween():void
555	{
556		EffectManager.startVectorEffect(IUIComponent(target));
557
558		// Create a tween to resize the object
559		tween = createTween(this, [ widthFrom, heightFrom ],
560										 [ widthTo, heightTo ], duration);
561
562		// Set back to initial size before the screen refreshes.
563		//EffectManager.suspendEventHandling();
564		applyTweenStartValues();
565		/*if (needToLayout)
566			UIComponent.layoutManager.validateNow();
567		*/
568		//EffectManager.resumeEventHandling();
569	}
570
571
572	/**
573	 *  @private
574	 *  Hides children of Panels while the effect is playing.
575	 */
576	private function hidePanelChildren():Boolean
577	{
578		if (!hideChildrenTargets)
579			return false;
580
581		// Initialize a couple arrays that will be needed later
582		restoreVisibleArray = [];
583		restoreAutoLayoutArray = [];
584
585		// Send each panel a "resizeStart" event, which will trigger
586		// the resizeStartEffect (if any)
587		var n:int = hideChildrenTargets.length;
588		for (var i:int = 0; i < n; i++)
589		{
590			var p:Object = hideChildrenTargets[i];
591
592			if (p is Panel)
593			{
594				var prevNumHideEffectsPlaying:Number = numHideEffectsPlaying;
595
596				p.addEventListener(EffectEvent.EFFECT_START, eventHandler);
597				p.dispatchEvent(new Event("resizeStart"));
598				p.removeEventListener(EffectEvent.EFFECT_START, eventHandler);
599
600				// If no effect started playing, then make children invisible
601				// immediately instead of waiting for the end of the effect
602				if (numHideEffectsPlaying == prevNumHideEffectsPlaying)
603					makePanelChildrenInvisible(Panel(p), i);
604			}
605		}
606
607		return numHideEffectsPlaying > 0;
608	}
609
610	/**
611	 *  @private
612	 */
613	private function makePanelChildrenInvisible(panel:Container,
614												panelIndex:Number):void
615	{
616		var childArray:Array = [];
617
618		var child:IUIComponent;
619
620		// Hide the Panel's children while the Resize is occurring.
621		var n:int = panel.numChildren;
622		for (var i:int = 0; i < n; i++)
623		{
624			child = IUIComponent(panel.getChildAt(i));
625			if (child.visible)
626			{
627				childArray.push(child);
628				child.setVisible(false, true);
629			}
630		}
631
632		// Hide the Panel's scrollbars while the Resize is occurring.
633		child = panel.horizontalScrollBar;
634		if (child && child.visible)
635		{
636			childArray.push(child);
637			child.setVisible(false, true);
638		}
639		child = panel.verticalScrollBar;
640		if (child && child.visible)
641		{
642			childArray.push(child);
643			child.setVisible(false, true);
644		}
645
646		restoreVisibleArray[panelIndex] = childArray;
647
648		// Set autoLayout = false, which prevents the Panel's updateDisplayList()
649		// method from executing while the Panel is resizing.
650		if (panel.autoLayout)
651		{
652			panel.autoLayout = false;
653			restoreAutoLayoutArray[panelIndex] = true;
654		}
655	}
656
657	/**
658	 * Method is used to explicitely determine widthTo and heightTo, taking into
659	 * account the current state of the component and the inputs to this ResizeEffect
660	 *
661	 * @private
662	 */
663	private function calculateExplicitDimensionChanges():void
664	{
665		var explicitWidth:* = propertyChanges ? propertyChanges.end["explicitWidth"] : undefined;
666		var explicitHeight:* = propertyChanges ? propertyChanges.end["explicitHeight"] : undefined;
667		var percentWidth:* = propertyChanges ? propertyChanges.end["percentWidth"] : undefined;
668		var percentHeight:* = propertyChanges ? propertyChanges.end["percentHeight"] : undefined;
669
670		var targetAsContainer:Container;
671		var parentAsContainer:Container;
672
673		if (!heightSet)
674		{
675			// Determine the percentHeight/explicitHeight to apply to target when effect ends
676			if (percentHeight !== undefined)
677				origPercentHeight = percentHeight;
678			else
679				origPercentHeight = target.percentHeight;
680
681			if (isNaN(origPercentHeight))
682			{
683				if (explicitHeight !== undefined)
684					origExplicitHeight = explicitHeight;
685				else
686					origExplicitHeight = target.explicitHeight;
687			}
688
689			targetAsContainer = target as Container;
690			if (targetAsContainer && targetAsContainer.verticalScrollBar == null)
691			{
692				origVerticalScrollPolicy = targetAsContainer.verticalScrollPolicy;
693				targetAsContainer.verticalScrollPolicy = ScrollPolicy.OFF;
694			}
695
696			if (target.parent)
697			{
698				parentAsContainer = target.parent as Container;
699				if (parentAsContainer && parentAsContainer.verticalScrollBar == null)
700				{
701					parentOrigVerticalScrollPolicy = parentAsContainer.verticalScrollPolicy;
702					parentAsContainer.verticalScrollPolicy = ScrollPolicy.OFF;
703				}
704			}
705		}
706
707		if (!widthSet)
708		{
709			// Determine the percentHeight/explicitHeight to apply to target when effect ends
710			if (percentWidth !== undefined)
711				origPercentWidth = percentWidth;
712			else
713				origPercentWidth = target.percentWidth;
714
715			if (isNaN(origPercentWidth))
716			{
717				if (explicitWidth !== undefined)
718					origExplicitWidth = explicitWidth;
719				else
720					origExplicitWidth = target.explicitWidth;
721			}
722
723			targetAsContainer = target as Container;
724			if (targetAsContainer && targetAsContainer.horizontalScrollBar == null)
725			{
726				origHorizontalScrollPolicy = targetAsContainer.horizontalScrollPolicy;
727				targetAsContainer.horizontalScrollPolicy = ScrollPolicy.OFF;
728			}
729
730			if (target.parent)
731			{
732				parentAsContainer = target.parent as Container;
733				if (parentAsContainer && parentAsContainer.horizontalScrollBar == null)
734				{
735					parentOrigHorizontalScrollPolicy = parentAsContainer.horizontalScrollPolicy;
736					parentAsContainer.horizontalScrollPolicy = ScrollPolicy.OFF;
737				}
738			}
739		}
740
741		// The user may have supplied some combination of widthFrom,
742		// widthTo, and widthBy. If either widthFrom or widthTo is
743		// not explicitly defined, calculate its value based on the
744		// other two values.
745		if (isNaN(widthFrom))
746		{
747			widthFrom = (!isNaN(widthTo) && !isNaN(widthBy)) ?
748						widthTo - widthBy :
749						target.width;
750		}
751		if (isNaN(widthTo))
752		{
753			if (isNaN(widthBy) &&
754				propertyChanges &&
755				(propertyChanges.end["width"] !== undefined ||
756				 explicitWidth !== undefined ))
757			{
758				if (explicitWidth !== undefined && !isNaN(explicitWidth))
759				{
760					explicitWidthSet = true;
761					_widthTo = explicitWidth;
762				}
763				else
764				{
765					_widthTo = propertyChanges.end["width"];
766				}
767			}
768			else
769			{
770				_widthTo = (!isNaN(widthBy)) ?
771						  widthFrom + widthBy :
772						  target.width;
773			}
774		}
775
776		// Ditto for heightFrom, heightTo, and heightBy.
777		if (isNaN(heightFrom))
778		{
779			heightFrom = (!isNaN(heightTo) && !isNaN(heightBy)) ?
780						 heightTo - heightBy :
781						 target.height;
782		}
783		if (isNaN(heightTo))
784		{
785			if (isNaN(heightBy) &&
786				propertyChanges &&
787				(propertyChanges.end["height"] !== undefined ||
788				 explicitHeight !== undefined))
789			{
790				if (explicitHeight !== undefined && !isNaN(explicitHeight))
791				{
792					explicitHeightSet = true;
793					_heightTo = explicitHeight;
794				}
795				else
796				{
797					_heightTo = propertyChanges.end["height"];
798				}
799			}
800			else
801			{
802				_heightTo = (!isNaN(heightBy))?
803						   heightFrom + heightBy :
804						   target.height;
805			}
806		}
807	}
808
809	/**
810	 *  @private
811	 */
812	private function restorePanelChildren():void
813	{
814		if (hideChildrenTargets)
815		{
816			var n:int = hideChildrenTargets.length;
817			for (var i:int = 0; i < n; i++)
818			{
819				var p:IUIComponent = hideChildrenTargets[i];
820
821				var childArray:Array = restoreVisibleArray[i];
822				if (childArray)
823				{
824					var m:int = childArray.length;
825					for (var j:int = 0; j < m; j++)
826					{
827						childArray[j].setVisible(true, true);
828					}
829				}
830
831				if (restoreAutoLayoutArray[i])
832					Container(p).autoLayout = true;
833
834				// Trigger the resizeEndEffect (if any)
835				p.dispatchEvent(new Event("resizeEnd"));
836			}
837		}
838	}
839
840	//--------------------------------------------------------------------------
841	//
842	//  Overridden event handlers
843	//
844	//--------------------------------------------------------------------------
845
846	/**
847	 *  @private
848	 *  This function is called when one of the Panels finishes
849	 *  its "hide children" animation.
850	 */
851	override mx_internal function eventHandler(event:Event):void
852	{
853		var panel:Container = event.target as Container;
854
855		super.eventHandler(event);
856
857		if (event.type == EffectEvent.EFFECT_START)
858		{
859			// Call my eventHandler() method when the effect finishes playing.
860			panel.addEventListener(EffectEvent.EFFECT_END, eventHandler);
861
862			// Remember how many effects we're waiting for
863			numHideEffectsPlaying++;
864		}
865
866		else if (event.type == EffectEvent.EFFECT_END)
867		{
868			// Remove the event listener that triggered this callback.
869			panel.removeEventListener(EffectEvent.EFFECT_END, eventHandler);
870
871			// Get the array index of the panel
872			var n:int = hideChildrenTargets.length;
873			for (var i:int = 0; i < n; i++)
874			{
875				if (hideChildrenTargets[i] == panel)
876					break;
877			}
878
879			makePanelChildrenInvisible(panel, i);
880
881			// If all panels have finished their "hide children" effect,
882			// then it's time to start our Resize effect.
883			if (--numHideEffectsPlaying == 0)
884				startResizeTween();
885		}
886	}
887}
888
889}
890