Fix the Focus Rect for fl.controls.TextInput in AS3

The AS3 components in CS4 allow for visually showing that the component is focused, using a code generated or libary asset sprite as the focusRect. This can be changed using the focusRect property or through the #setStyle method inherited by all UIComponent's. The outlying size of the focusRect can also be altered by setting the focusRectPadding style, which designates the number of pixels outside the size of the component that the focusRect sits. Unfortunately, if you are using TextInput, and likely TextArea, setting this padding to 0 results in hiding the focusRect behind the component skin (the grey outline for the TextInput). This is a result of the fact that the UIComponent#drawFocus sets the childIndex of the focusRect sprite to 0, essentially placing it behind the TextInput border. I wanted to create a subclass of TextInput for which I didn't need to reset several defaults every time I used it, so I created the class below, which also fixes the 0 padding focusRect issue.

CODE:
  1. package com.stickam.editor.ui {
  2.  
  3.     import fl.controls.TextInput;
  4.     import fl.managers.IFocusManagerComponent;
  5.  
  6.     /**
  7.      * Extends the TextInput component in order to set the default width and
  8.      * height, and sets the focusRectPadding to 0 (so the focus rectangle lies
  9.      * directly on top of the grey text input border skin.
  10.      */
  11.     public class TextInput2 extends TextInput implements IFocusManagerComponent {
  12.  
  13.         /**
  14.          * @constructor
  15.          */
  16.         public function TextInput2() {
  17.             super();
  18.             textField.width = 1;
  19.             textField.height = 1;
  20.             width = 64;
  21.             height = 18;
  22.             setStyle("focusRectPadding", 0);
  23.         }
  24.  
  25.         /**
  26.          * The UIComponent class, when the component is in focus, creates the
  27.          * uiFocusRect and sets it at childIndex 0. Unfortunately, the text input
  28.          * skin is then above the focus rect, and so the focus rect disappears
  29.          * when the focusRectPadding is 0. This calls the parent implementation,
  30.          * then ensures that the focus rect is visible by changing its
  31.          * child index.
  32.          */
  33.         override public function drawFocus(draw:Boolean):void {
  34.             super.drawFocus(draw);
  35.  
  36.             if (uiFocusRect != null) {
  37.                 setChildIndex(uiFocusRect, numChildren - 1);
  38.             }
  39.         }
  40.     }
  41. }

UPDATE: I updated the above script to set the size of the TextField in the TextInput component to be 1x1. This also fixes the problem that the TextField is created as 100x100 pixels when it is first created (as are all dynamically generated text fields). This matters if you wish to use any customized layout (such as the Yahoo astra layout managers), and don't want to have to call drawNow() or wait a frame to finalize layout settings in a UI.

Line-width on Shapes in Flash, and scale9Grid Results

A quick note regarding something I've been meaning to post a short note about for a while, for those that might be running into problems resulting from shapes drawn in flash with an outline. I ran into this recently again, so I thought I'd post for any concerned parties out there.

Regardless of whether or not a shape, such as a rectangle, is drawn in Flash with a hairline border line, or a borderline of 1, the result on an unscaled shape is essentially the same. The crisp outline of the shape is drawn with a pixel width of 1. The downside of this occurs when trying to create pixel perfect UI's, and especially those using shapes with a scalenine grid assigned to them.

Using the AS3 drawing API, you can create a rectangle that has a lineStyle with a width of 1, and some fill, which would look something like this:

CODE:
  1. var sh:Shape = new Shape();
  2. sh.graphics.lineStyle(1, 0xFF0000);
  3. sh.graphics.beginFill(0xFF6600);
  4. sh.graphics.drawRect(0, 0, 100, 100);
  5. sh.graphics.endFill();
  6. addChild(sh);
  7. trace("Size: " + sh.width + " x " + sh.height);

The code above so draws an orange rectangle with a red border that is 201 pixels wide and 101 pixels tall. If you don't know why, the reason is because flash draws the line on the pixel width and height of the rectangle with a line size of 1. This makes your shape 201 x 101; thankfully, Flash will report the width and height correctly as 201 x 101.

This gets stickier if you have a shape that is used as a component skin, and has a scale9Grid applied to it. In this case you will likely be working with a component that you expect to take up 200x100 pixels on the stage when you tell it to be that size. However, if the skin used features "line" outlines at its borders, your component will get drawn on the stage with extra pixels. If you dig into the Flash component skins, you will notice that most of the skins use fills for any borders on the shapes (even 1 pixel wide borders) so that the scale9Grid enabled shapes will scale correctly to the pixel. That being said, the proper way to draw the simple skin shape in this case, programmatically, would be:

CODE:
  1. var sh2:Shape = new Shape();
  2. sh2.graphics.beginFill(0xFF0000);
  3. sh2.graphics.drawRect(0, 0, 200, 100);
  4. sh2.graphics.beginFill(0xFF6600);
  5. sh2.graphics.drawRect(1, 1, 198, 98);
  6. sh2.graphics.endFill();
  7. addChild(sh2);
  8. trace("Size: " + sh2.width + " x " + sh2.height);

Incidentally, this also often cleans up some anti-aliasing at the corners of rectangular shapes which I believe is cause by some weirdness in the way the "caps" are drawn on the shapes. (but don't quote me on that...)

AS3 ComboBox close error.

Just a quick note for those possibly searching for ComboBox AS3 and "Cannot access a property or method of a null object reference." When using the AS3 ComboBox, the above error will be thrown when the ComboBox loses focus, if the ComboBox is in its open state and it has lost reference to the stage. In other words, if you have a UI pane that contains the ComboBox, and you are removing the pane, make sure to first call #close() on all instances of the ComboBox, then remove the container from its parent container. (This can be replicated by clicking open the ComboBox, then clicking on the stage.) Unfortunately, the isOpen property for the ComboBox is not publicly accessible, but the #close() method is a NOOP if it is already closed, so just making sure to close it is no big deal. I ran into this error because I was listening for a stage focus event which triggered the immediate removal of the container pane in question, but I didn't find any relevant references in a quick web search, so I thought I'd make a quick post with the solution.