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.skins
13{
14
15import flash.display.Graphics;
16import flash.display.Shape;
17import flash.geom.Matrix;
18import mx.core.IInvalidating;
19import mx.core.IFlexDisplayObject;
20import mx.core.UIComponentGlobals;
21import mx.core.mx_internal;
22import mx.managers.ILayoutManagerClient;
23import mx.styles.ISimpleStyleClient;
24import mx.styles.IStyleClient;
25import mx.utils.GraphicsUtil;
26import mx.utils.NameUtil;
27import mx.core.FlexShape;
28import mx.core.IProgrammaticSkin;
29
30use namespace mx_internal;
31
32/**
33 *  This class is the base class for skin elements
34 *  which draw themselves programmatically.
35 */
36public class ProgrammaticSkin extends FlexShape
37							  implements IFlexDisplayObject, IInvalidating,
38							  ILayoutManagerClient, ISimpleStyleClient, IProgrammaticSkin
39{
40	include "../core/Version.as";
41
42	//--------------------------------------------------------------------------
43	//
44	//  Class variables
45	//
46	//--------------------------------------------------------------------------
47
48	/**
49	 *  @private
50	 *  Set by horizontalGradientMatrix() or verticalGradientMatrix().
51	 */
52	private static var tempMatrix:Matrix = new Matrix();
53
54	//--------------------------------------------------------------------------
55	//
56	//  Constructor
57	//
58	//--------------------------------------------------------------------------
59
60	/**
61	 *  Constructor.
62	 */
63	public function ProgrammaticSkin()
64	{
65		super();
66
67		// If nobody explicitly sets a size for this object,
68		// then set its width and height to be its measured size.
69		_width = measuredWidth;
70		_height = measuredHeight;
71	}
72
73	//--------------------------------------------------------------------------
74	//
75	//  Variables
76	//
77	//--------------------------------------------------------------------------
78
79    /**
80     *  @private
81     */
82    private var invalidateDisplayListFlag:Boolean = false;
83
84	//--------------------------------------------------------------------------
85	//
86	//  Overridden properties
87	//
88	//--------------------------------------------------------------------------
89
90	//----------------------------------
91	//  height
92	//----------------------------------
93
94	/**
95	 *  @private
96	 *  Storage for the height property.
97	 */
98	private var _height:Number;
99
100	/**
101	 *  @private
102	 */
103	override public function get height():Number
104	{
105		return _height;
106	}
107
108	/**
109	 *  @private
110	 */
111	override public function set height(value:Number):void
112	{
113		_height = value;
114
115		invalidateDisplayList();
116	}
117
118	//----------------------------------
119	//  width
120	//----------------------------------
121
122	/**
123	 *  @private
124	 *  Storage for the width property.
125	 */
126	private var _width:Number;
127
128	/**
129	 *  @private
130	 */
131	override public function get width():Number
132	{
133		return _width;
134	}
135
136	/**
137	 *  @private
138	 */
139	override public function set width(value:Number):void
140	{
141		_width = value;
142
143		invalidateDisplayList();
144	}
145
146	//--------------------------------------------------------------------------
147	//
148	//  Properties: IFlexDisplayObject
149	//
150	//--------------------------------------------------------------------------
151
152	//----------------------------------
153	//  measuredHeight
154	//----------------------------------
155
156	/**
157	 *  The measured height of this object.
158	 *  This should be overridden by subclasses to return the preferred height for
159	 *  the skin.
160	 *
161	 *  @return The measured height of the object, in pixels.
162	 */
163	public function get measuredHeight():Number
164	{
165		return 0;
166	}
167
168	//----------------------------------
169	//  measuredWidth
170	//----------------------------------
171
172	/**
173	 *  The measured width of this object.
174	 *  This should be overridden by subclasses to return the preferred width for
175	 *  the skin.
176	 *
177	 *  @return The measured width of the object, in pixels.
178	 */
179	public function get measuredWidth():Number
180	{
181		return 0;
182	}
183
184	//--------------------------------------------------------------------------
185	//
186	//  Properties: ILayoutManagerClient
187	//
188	//--------------------------------------------------------------------------
189
190	//----------------------------------
191	//  initialized
192	//----------------------------------
193
194    /**
195	 *  @private
196	 *  Storage for the initialized property.
197	 */
198	private var _initialized:Boolean = false;
199
200    /**
201	 *  @copy mx.core.UIComponent#initialized
202     */
203    public function get initialized():Boolean
204	{
205		return _initialized;
206	}
207
208    /**
209     *  @private
210     */
211    public function set initialized(value:Boolean):void
212	{
213		_initialized = value;
214	}
215
216    //----------------------------------
217    //  nestLevel
218    //----------------------------------
219
220    /**
221	 *  @private
222	 *  Storage for the nestLevel property.
223	 */
224	private var _nestLevel:int = 0;
225
226	/**
227     *  @copy mx.core.UIComponent#nestLevel
228     */
229	public function get nestLevel():int
230	{
231		return _nestLevel;
232	}
233
234	/**
235     *  @private
236     */
237	public function set nestLevel(value:int):void
238	{
239		_nestLevel = value;
240
241		// After nestLevel is initialized, add this object to the
242		// LayoutManager's queue, so that it is drawn at least once
243		invalidateDisplayList();
244	}
245
246	//----------------------------------
247	//  processedDescriptors
248	//----------------------------------
249
250    /**
251     *  @private
252	 *  Storage for the processedDescriptors property.
253     */
254	private var _processedDescriptors:Boolean = false;
255
256    /**
257     *  @copy mx.core.UIComponent#processedDescriptors
258     */
259    public function get processedDescriptors():Boolean
260	{
261		return _processedDescriptors;
262	}
263
264    /**
265     *  @private
266     */
267    public function set processedDescriptors(value:Boolean):void
268	{
269		_processedDescriptors = value;
270	}
271
272	//----------------------------------
273	//  updateCompletePendingFlag
274	//----------------------------------
275
276    /**
277     *  @private
278	 *  Storage for the updateCompletePendingFlag property.
279     */
280	private var _updateCompletePendingFlag:Boolean = true;
281
282    /**
283	 *  A flag that determines if an object has been through all three phases
284	 *  of layout validation (provided that any were required).
285     */
286    public function get updateCompletePendingFlag():Boolean
287	{
288		return _updateCompletePendingFlag;
289	}
290
291    /**
292     *  @private
293     */
294    public function set updateCompletePendingFlag(value:Boolean):void
295	{
296		_updateCompletePendingFlag = value;
297	}
298
299	//--------------------------------------------------------------------------
300	//
301	//  Properties: ISimpleStyleClient
302	//
303	//--------------------------------------------------------------------------
304
305    //----------------------------------
306    //  styleName
307    //----------------------------------
308
309    /**
310     *  @private
311     *  Storage for the styleName property.
312	 *  For skins, it is always a UIComponent.
313     */
314    private var _styleName:IStyleClient;
315
316    /**
317     *  A parent component used to obtain style values. This is typically set to the
318     *  component that created this skin.
319     */
320    public function get styleName():Object
321    {
322        return _styleName;
323    }
324
325    /**
326     *  @private
327     */
328    public function set styleName(value:Object):void
329    {
330        if (_styleName != value)
331		{
332			_styleName = value as IStyleClient;
333			invalidateDisplayList();
334		}
335    }
336
337	//--------------------------------------------------------------------------
338	//
339	//  Methods: IFlexDisplayObject
340	//
341	//--------------------------------------------------------------------------
342
343	/**
344	 *  Moves this object to the specified x and y coordinates.
345	 *
346     	 *  @param x The horizontal position, in pixels.
347	 *
348     	 *  @param y The vertical position, in pixels.
349	 */
350	public function move(x:Number, y:Number):void
351	{
352		this.x = x;
353		this.y = y;
354	}
355
356	/**
357	 *  Sets the height and width of this object.
358	 *
359     	 *  @param newWidth The width, in pixels, of this object.
360	 *
361     	 *  @param newHeight The height, in pixels, of this object.
362	 */
363	public function setActualSize(newWidth:Number, newHeight:Number):void
364	{
365		var changed:Boolean = false;
366
367		if (_width != newWidth)
368		{
369			_width = newWidth;
370			changed = true;
371		}
372
373		if (_height != newHeight)
374		{
375			_height = newHeight;
376			changed = true;
377		}
378
379		if (changed)
380			invalidateDisplayList();
381	}
382
383	//--------------------------------------------------------------------------
384	//
385	//  Methods: ILayoutManagerClient
386	//
387	//--------------------------------------------------------------------------
388
389	/**
390	 *  This function is an empty stub so that ProgrammaticSkin
391	 *  can implement the ILayoutManagerClient  interface.
392	 *  Skins do not call <code>LayoutManager.invalidateProperties()</code>,
393	 *  which would normally trigger a call to this method.
394	 */
395	public function validateProperties():void
396	{
397	}
398
399	/**
400	 *  This function is an empty stub so that ProgrammaticSkin
401	 *  can implement the ILayoutManagerClient  interface.
402	 *  Skins do not call <code>LayoutManager.invalidateSize()</code>,
403	 *  which would normally trigger a call to this method.
404         *
405     	 *  @param recursive Determines whether children of this skin are validated.
406	 */
407	public function validateSize(recursive:Boolean = false):void
408	{
409	}
410
411	/**
412	 *  This function is called by the LayoutManager
413	 *  when it's time for this control to draw itself.
414	 *  The actual drawing happens in the <code>updateDisplayList</code>
415	 *  function, which is called by this function.
416	 */
417	public function validateDisplayList():void
418	{
419		invalidateDisplayListFlag = false;
420
421		updateDisplayList(width, height);
422	}
423
424	//--------------------------------------------------------------------------
425	//
426	//  Methods: ISimpleStyleClient
427	//
428	//--------------------------------------------------------------------------
429
430	/**
431	 *  Whenever any style changes, redraw this skin.
432	 *  Subclasses can override this method
433	 *  and perform a more specific test before calling invalidateDisplayList().
434	 *
435	 *  @param styleProp The name of the style property that changed, or null
436	 *  if all styles have changed.
437	 */
438	public function styleChanged(styleProp:String):void
439	{
440		invalidateDisplayList();
441	}
442
443	//--------------------------------------------------------------------------
444	//
445	//  Methods: Other
446	//
447	//--------------------------------------------------------------------------
448
449	/**
450	 *  @copy mx.core.UIComponent#invalidateDisplayList()
451	 */
452	public function invalidateDisplayList():void
453	{
454		// Don't try to add the object to the display list queue until we've
455		// been assigned a nestLevel, or we'll get added at the wrong place in
456		// the LayoutManager's priority queue.
457		if (!invalidateDisplayListFlag && nestLevel > 0)
458		{
459			invalidateDisplayListFlag = true;
460			UIComponentGlobals.layoutManager.invalidateDisplayList(this);
461		}
462	}
463
464	/**
465	 *  Programmatically draws the graphics for this skin.
466	 *
467	 *  <p>Subclasses should override this method and include calls
468	 *  to methods such as <code>graphics.moveTo()</code> and
469	 *  <code>graphics.lineTo()</code>.</p>
470	 *
471	 *  <p>This occurs before any scaling from sources
472	 *  such as user code or zoom effects.
473	 *  The component is unaware of the scaling that takes place later.</p>
474	 *
475         *  @param unscaledWidth
476	 *  The width, in pixels, of this object before any scaling.
477	 *
478         *  @param unscaledHeight
479	 *  The height, in pixels, of this object before any scaling.
480	 */
481	protected function updateDisplayList(unscaledWidth:Number,
482									     unscaledHeight:Number):void
483	{
484	}
485
486	/**
487	 *  @inheritDoc
488	 */
489	public function invalidateSize():void
490	{
491	}
492
493	/**
494	 *  @inheritDoc
495	 */
496	public function invalidateProperties():void
497	{
498	}
499
500	/**
501	 *  Validate and update the properties and layout of this object
502	 *  and redraw it, if necessary.
503	 */
504	public function validateNow():void
505	{
506		// Since we don't have commit/measure/layout phases,
507		// all we need to do here is the draw phase
508		if (invalidateDisplayListFlag)
509			validateDisplayList();
510	}
511
512    /**
513     *  Returns the value of the specified style property.
514     *
515     *  @param styleProp Name of the style property.
516     *
517     *  @return The style value. This can be any type of object that style properties can be, such as
518     *  int, Number, String, etc.
519     */
520    public function getStyle(styleProp:String):*
521    {
522        return _styleName ? _styleName.getStyle(styleProp) : null;
523    }
524
525	/**
526	 *  Utility function to create a horizontal gradient matrix.
527	 *
528	 *  @param x The left edge of the gradient.
529	 *
530	 *  @param y The top edge of the gradient.
531	 *
532	 *  @param width The width of the gradient.
533	 *
534	 *  @param height The height of the gradient.
535	 *
536	 *  @return The horizontal gradient matrix. This is a temporary
537	 *  object that should only be used for a single subsequent call
538	 *  to the <code>drawRoundRect()</code> method.
539	 */
540	protected function horizontalGradientMatrix(x:Number, y:Number,
541												width:Number,
542												height:Number):Matrix
543	{
544		return rotatedGradientMatrix(x, y, width, height, 0);
545	}
546
547	/**
548	 *  Utility function to create a vertical gradient matrix.
549	 *
550	 *  @param x The left edge of the gradient.
551	 *
552	 *  @param y The top edge of the gradient.
553	 *
554	 *  @param width The width of the gradient.
555	 *
556	 *  @param height The height of the gradient.
557	 *
558	 *  @return The horizontal gradient matrix. This is a temporary
559	 *  object that should only be used for a single subsequent call
560	 *  to the <code>drawRoundRect()</code> method.
561	 */
562	protected function verticalGradientMatrix(x:Number, y:Number,
563											  width:Number,
564											  height:Number):Matrix
565	{
566		return rotatedGradientMatrix(x, y, width, height, 90);
567	}
568
569	/**
570	 *  Utility function to create a rotated gradient matrix.
571	 *
572	 *  @param x The left edge of the gradient.
573	 *
574	 *  @param y The top edge of the gradient.
575	 *
576	 *  @param width The width of the gradient.
577	 *
578	 *  @param height The height of the gradient.
579	 *
580	 *  @param rotation The amount to rotate, in degrees.
581	 *
582	 *  @return The horizontal gradient matrix. This is a temporary
583	 *  object that should only be used for a single subsequent call
584	 *  to the <code>drawRoundRect()</code> method.
585	 */
586	protected function rotatedGradientMatrix(x:Number, y:Number,
587											 width:Number,
588											 height:Number,
589											 rotation:Number):Matrix
590	{
591		tempMatrix.createGradientBox(width, height,
592									 rotation * Math.PI / 180, x, y);
593		return tempMatrix;
594	}
595
596	/**
597	 *  Programatically draws a rectangle into this skin's Graphics object.
598	 *
599	 *  <p>The rectangle can have rounded corners.
600	 *  Its edges are stroked with the current line style
601	 *  of the Graphics object.
602	 *  It can have a solid color fill, a gradient fill, or no fill.
603	 *  A solid fill can have an alpha transparency.
604	 *  A gradient fill can be linear or radial. You can specify
605	 *  up to 15 colors and alpha values at specified points along
606	 *  the gradient, and you can specify a rotation angle
607	 *  or transformation matrix for the gradient.
608	 *  Finally, the rectangle can have a rounded rectangular hole
609	 *  carved out of it.</p>
610	 *
611	 *  <p>This versatile rectangle-drawing routine is used by many skins.
612	 *  It calls the <code>drawRect()</code> or
613	 *  <code>drawRoundRect()</code>
614	 *  methods (in the flash.display.Graphics class) to draw into this
615	 *  skin's Graphics object.</p>
616	 *
617	 *	@param x Horizontal position of upper-left corner
618	 *  of rectangle within this skin.
619	 *
620	 *	@param y Vertical position of upper-left corner
621	 *  of rectangle within this skin.
622	 *
623	 *	@param width Width of rectangle, in pixels.
624	 *
625	 *	@param height Height of rectangle, in pixels.
626	 *
627	 *	@param cornerRadius Corner radius/radii of rectangle.
628	 *  Can be <code>null</code>, a Number, or an Object.
629	 *  If it is <code>null</code>, it specifies that the corners should be square
630	 *  rather than rounded.
631	 *  If it is a Number, it specifies the same radius, in pixels,
632	 *  for all four corners.
633	 *  If it is an Object, it should have properties named
634	 *  <code>tl</code>, <code>tr</code>, <code>bl</code>, and
635	 *  <code>br</code>, whose values are Numbers specifying
636	 *  the radius, in pixels, for the top left, top right,
637	 *  bottom left, and bottom right corners.
638	 *  For example, you can pass a plain Object such as
639	 *  <code>{ tl: 5, tr: 5, bl: 0, br: 0 }</code>.
640	 *  The default value is null (square corners).
641	 *
642	 *	@param color The RGB color(s) for the fill.
643	 *  Can be <code>null</code>, a uint, or an Array.
644	 *  If it is <code>null</code>, the rectangle not filled.
645	 *  If it is a uint, it specifies an RGB fill color.
646	 *  For example, pass <code>0xFF0000</code> to fill with red.
647	 *  If it is an Array, it should contain uints
648	 *  specifying the gradient colors.
649	 *  For example, pass <code>[ 0xFF0000, 0xFFFF00, 0x0000FF ]</code>
650	 *  to fill with a red-to-yellow-to-blue gradient.
651	 *  You can specify up to 15 colors in the gradient.
652	 *  The default value is null (no fill).
653	 *
654	 *	@param alpha Alpha value(s) for the fill.
655	 *  Can be null, a Number, or an Array.
656	 *  This argument is ignored if <code>color</code> is null.
657	 *  If <code>color</code> is a uint specifying an RGB fill color,
658	 *  then <code>alpha</code> should be a Number specifying
659	 *  the transparency of the fill, where 0.0 is completely transparent
660	 *  and 1.0 is completely opaque.
661	 *  You can also pass null instead of 1.0 in this case
662	 *  to specify complete opaqueness.
663	 *  If <code>color</code> is an Array specifying gradient colors,
664	 *  then <code>alpha</code> should be an Array of Numbers, of the
665	 *  same length, that specifies the corresponding alpha values
666	 *  for the gradient.
667	 *  In this case, the default value is <code>null</code> (completely opaque).
668	 *
669     *  @param gradientMatrix Matrix object used for the gradient fill.
670     *  The utility methods <code>horizontalGradientMatrix()</code>,
671     *  <code>verticalGradientMatrix()</code>, and
672     *  <code>rotatedGradientMatrix()</code> can be used to create the value for
673     *  this parameter.
674	 *
675	 *	@param gradientType Type of gradient fill. The possible values are
676	 *  <code>GradientType.LINEAR</code> or <code>GradientType.RADIAL</code>.
677	 *  (The GradientType class is in the package flash.display.)
678	 *
679	 *	@param gradientRatios (optional default [0,255])
680	 *  Specifies the distribution of colors. The number of entries must match
681	 *  the number of colors defined in the <code>color</code> parameter.
682	 *  Each value defines the percentage of the width where the color is
683	 *  sampled at 100%. The value 0 represents the left-hand position in
684	 *  the gradient box, and 255 represents the right-hand position in the
685	 *  gradient box.
686	 *
687	 *	@param hole (optional) A rounded rectangular hole
688	 *  that should be carved out of the middle
689	 *  of the otherwise solid rounded rectangle
690	 *  { x: #, y: #, w: #, h: #, r: # or { br: #, bl: #, tl: #, tr: # } }
691	 *
692	 *  @see flash.display.Graphics#beginGradientFill()
693	 */
694	protected function drawRoundRect(
695							x:Number, y:Number, width:Number, height:Number,
696							cornerRadius:Object = null,
697							color:Object = null,
698							alpha:Object = null,
699							gradientMatrix:Matrix = null,
700							gradientType:String = "linear",
701							gradientRatios:Array /* of Number */ = null,
702							hole:Object = null):void
703	{
704		var g:Graphics = graphics;
705
706		// Quick exit if weight or height is zero.
707		// This happens when scaling a component to a very small value,
708		// which then gets rounded to 0.
709		if (width == 0 || height == 0)
710			return;
711
712		// If color is an object then allow for complex fills.
713		if (color !== null)
714		{
715			if (color is uint)
716			{
717				g.beginFill(uint(color), Number(alpha));
718			}
719			else if (color is Array)
720			{
721				var alphas:Array = alpha is Array ?
722								   alpha as Array :
723								   [ alpha, alpha ];
724
725				if (!gradientRatios)
726					gradientRatios = [ 0, 0xFF ];
727
728				g.beginGradientFill(gradientType,
729									color as Array, alphas,
730									gradientRatios, gradientMatrix);
731			}
732		}
733
734		var ellipseSize:Number;
735
736		// Stroke the rectangle.
737		if (!cornerRadius)
738		{
739			g.drawRect(x, y, width, height);
740		}
741		else if (cornerRadius is Number)
742		{
743			ellipseSize = Number(cornerRadius) * 2;
744			g.drawRoundRect(x, y, width, height,
745							ellipseSize, ellipseSize);
746		}
747		else
748		{
749			GraphicsUtil.drawRoundRectComplex(g,
750								   x, y, width, height,
751								   cornerRadius.tl, cornerRadius.tr,
752								   cornerRadius.bl, cornerRadius.br);
753		}
754
755		// Carve a rectangular hole out of the middle of the rounded rect.
756		if (hole)
757		{
758			var holeR:Object = hole.r;
759			if (holeR is Number)
760			{
761				ellipseSize = Number(holeR) * 2;
762				g.drawRoundRect(hole.x, hole.y, hole.w, hole.h,
763								ellipseSize, ellipseSize);
764			}
765			else
766			{
767				GraphicsUtil.drawRoundRectComplex(g,
768									   hole.x, hole.y, hole.w, hole.h,
769									   holeR.tl, holeR.tr, holeR.bl, holeR.br);
770			}
771		}
772
773		if (color !== null)
774			g.endFill();
775	}
776}
777
778}
779