title: AS3/Flex Custom TextInput Component Padding
body: <p>I'm trying to create a custom TextInput component that has a label inside of it with the actual input box next to the label. As depicted in the following image (Photoshop Slice):<br>
  <img src="https://i.stack.imgur.com/LABKD.png" alt="Custom TextInput">
  <br>I got most of it working. However, when the text reaches the end of the box, it goes all the way to the border (completely ignoring the padding) and then when more characters are entered, it pushes the left side of the text right up against the <code>USERNAME</code> label (again, ignoring the padding). As you can see in this image <em>(Flex Screenshot)</em>:<br>
  <img src="https://i.stack.imgur.com/FaJ9W.png" alt="Bugged Custom TextInput"></p>
  
  <p>Aside from changing the skin of the scrollbars, this is the first custom component and skin I've done, and I've been banging my head at it for a day now. I know it's probably something really easy, but I cannot figure it out. I'd greatly appreciate it if someone could tell me how to do this properly...</p>
  
  <p>The code I have thus-far looks like this...</p>
  
  <p><strong>com.controls.LabeledTextInput.as:</strong></p>
  
  <pre><code>package com.controls
  {
      import flash.events.FocusEvent;
      import spark.components.TextInput;
      import spark.core.IDisplayText;
      import mx.core.FlexGlobals;
      import mx.styles.CSSStyleDeclaration;
  
      //--------------------------------------
      //  Styles
      //--------------------------------------
  
      /**
       *  The alpha of the border for this component.
       */
      [Style(name="focusBorderAlpha", type="Number", inherit="no", theme="spark", minValue="0.0", maxValue="1.0")]
  
      /**
       *  The color of the border for this component.
       */
      [Style(name="focusBorderColor", type="uint", format="Color", inherit="no", theme="spark")]
  
      /**
       *  Controls the visibility of the border for this component.
       */
      [Style(name="focusBorderVisible", type="Boolean", inherit="no", theme="spark, mobile")]
  
      /**
       *  Shorthand padding around the textDisplay subcomponent.
       *  This property works like the CSS padding style. Eg.
       *  padding: 2 4 2 4    (top right bottom left)
       *  padding: 2 4 2      (top right/left bottom)
       *  padding: 2 4        (top/bottom right/left)
       *  padding: 2          (top/right/bottom/left)
       */
      [Style(name="padding", type="String", inherit="no", theme="spark")]
  
      /**
       *  Padding around the labelDisplay subcomponent.
       *  This property works like the CSS padding style. Eg.
       *  padding: 2 4 2 4    (top right bottom left)
       *  padding: 2 4 2      (top right/left bottom)
       *  padding: 2 4        (top/bottom right/left)
       *  padding: 2          (top/right/bottom/left)
       */
      [Style(name="labelPadding", type="String", inherit="no", theme="spark")]
  
      //--------------------------------------
      //  Skin states
      //--------------------------------------
  
      /**
       *  Focus State of the TextInput
       */
      [SkinState("focused")]
  
      public class LabeledTextInput extends TextInput
      {
          [SkinPart(required="false")]
  
          /**
           *  A skin part that defines the label of the input. 
           */
          public var labelDisplay:IDisplayText;
  
          /**
           *  Var for storing the label.
           */
          private var _label:String;
  
          /**
           *  Keeps track of our focus state.
           */
          private var inFocus:Boolean;
  
          /**
           *  Used for setting default property values.
           */
          private static var classConstructed:Boolean = classConstruct();
  
          // Set default style values
          private static function classConstruct() : Boolean
          {
              if (!FlexGlobals.topLevelApplication.styleManager.getStyleDeclaration("com.controls.LabeledTextInput"))
              {
                  var labeledTextInputStyles:CSSStyleDeclaration = new CSSStyleDeclaration();
                  labeledTextInputStyles.defaultFactory = function() : void
                  {
                      this.focusBorderAlpha = 1;
                      this.focusBorderColor = 0xE2E2D9;
                      this.focusBorderVisible = true;
                      this.paddingTop = NaN;
                      this.paddingRight = NaN;
                      this.paddingBottom = NaN;
                      this.paddingLeft = NaN;
                      this.padding = null;
                      this.labelPadding = null;
                  }
                  FlexGlobals.topLevelApplication.styleManager.setStyleDeclaration("com.controls.LabeledTextInput", labeledTextInputStyles, true);
              }
              return true;
          }       
  
          public function LabeledTextInput()
          {
              super();
          }
  
          [Bindable]
          public function get label() : String
          {
              return _label;
          }
  
          public function set label(value:String) : void
          {
              if(_label != value)
              {
                  _label = value;
                  if(labelDisplay != null)
                  {
                      labelDisplay.text = value;
                  }
              }
          }
  
          /* Implement the getCurrentSkinState() method to set the view state of the skin class. */
          override protected function getCurrentSkinState() : String
          {
              return (inFocus == true) ? "focused" : super.getCurrentSkinState();
          } 
  
          /* Set the label and attach focus even handlers. */ 
          override protected function partAdded(partName:String, instance:Object) : void
          {
              super.partAdded(partName, instance);
  
              if (instance == labelDisplay)
              {
                  labelDisplay.text = label;
              }
              else if(instance == textDisplay)
              {
                  textDisplay.addEventListener(FocusEvent.FOCUS_IN, onFocusInHandler);
                  textDisplay.addEventListener(FocusEvent.FOCUS_OUT, onFocusOutHandler);
              }
          }
  
          /* Remove the focus event handlers. */
          override protected function partRemoved(partName:String, instance:Object) : void
          {
              super.partRemoved(partName, instance);
  
              if(instance == textDisplay)
              {
                  textDisplay.removeEventListener(FocusEvent.FOCUS_IN, onFocusInHandler);
                  textDisplay.removeEventListener(FocusEvent.FOCUS_OUT, onFocusOutHandler);
              }
          }
  
          /* Handler for FocusIn Event */
          private function onFocusInHandler(event:FocusEvent) : void
          {
              inFocus = true;
              invalidateSkinState();
          }
  
          /* Handler for FocusOut */
          private function onFocusOutHandler(event:FocusEvent) : void
          {
              inFocus = false;
              invalidateSkinState();
          }
      }
  }
  </code></pre>
  
  <p><strong>com.controls.skins.LabeledTextInputSkin.mxml:</strong></p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
      xmlns:fb="http://ns.adobe.com/flashbuilder/2009" 
      alpha.disabledStates="0.5" blendMode="normal"&gt;
  
      &lt;fx:Metadata&gt;
      &lt;![CDATA[ 
          [HostComponent("com.controls.LabeledTextInput")]
      ]]&gt;
      &lt;/fx:Metadata&gt; 
  
      &lt;fx:Script fb:purpose="styling"&gt;
          &lt;![CDATA[
          import mx.core.FlexVersion;
  
          private var paddingChanged:Boolean;
  
          /* Define the skin elements that should not be colorized. */
          static private const exclusions:Array = ["background", "textDisplay", "promptDisplay", "border"];
  
          /* exclusions before Flex 4.5 for backwards-compatibility purposes */
          static private const exclusions_4_0:Array = ["background", "textDisplay", "promptDisplay"];
  
          /**
           * @private
           */
          override public function get colorizeExclusions() : Array 
          {
              // Since border is styleable via borderColor, no need to allow chromeColor to affect
              // the border.  This is wrapped in a compatibility flag since this change was added  
              // in Flex 4.5
              if (FlexVersion.compatibilityVersion &lt; FlexVersion.VERSION_4_5)
              {
                  return exclusions_4_0;
              }
  
              return exclusions;
          }
  
          /* Define the content fill items that should be colored by the "contentBackgroundColor" style. */
          static private const contentFill:Array = ["bgFill"];
  
          /**
           *  @private
           */
          override public function get contentItems():Array {return contentFill};
  
          /**
           *  @private
           */
          override protected function commitProperties() : void
          {
              super.commitProperties();
  
              if (paddingChanged)
              {
                  updatePadding();
                  paddingChanged = false;
              }
          }
  
          /**
           * @private
           */
          override protected function initializationComplete() : void
          {
              useChromeColor = false;
              super.initializationComplete();
          }
  
          /**
           *  Update the display list.
           * 
           *  @private
           */
          override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number) : void
          {
              var visibleState:Boolean = (getStyle("borderVisible") == true) ? true : false;
              border.visible = visibleState;
              shadow.visible = visibleState;
  
              if(currentState == 'focused')
              {
                  focusBorder.visible = getStyle("focusBorderVisible");
                  focusBorderStroke.color = getStyle("focusBorderColor");
                  focusBorderStroke.alpha = getStyle("focusBorderAlpha");
              }
              else
              {
                  borderStroke.color = getStyle("borderColor");
                  borderStroke.alpha = getStyle("borderAlpha");
              }
  
              super.updateDisplayList(unscaledWidth, unscaledHeight);
          }
  
          /**
           *  Adjusts the border to be visible and within' the bounds.
           * 
           *  @private
           */
          private function updateBorderPos() : void
          {
              if(getStyle("borderVisible") == true || getStyle("focusBorderVisible") )
              {
                  background.left = background.top = background.right = background.bottom = 1;
                  textDisplay.left = textDisplay.top = textDisplay.right = textDisplay.bottom = 1;
                  if(labelDisplay)
                  {
                      labelDisplay.setLayoutBoundsSize(unscaledWidth - 2, unscaledHeight - 2);
                      labelDisplay.setLayoutBoundsPosition(1, 1);
                  }
              }
              else
              {
                  background.left = background.top = background.right = background.bottom = 0;
                  textDisplay.left = textDisplay.top = textDisplay.right = textDisplay.bottom = 0;
                  if(labelDisplay)
                  {
                      labelDisplay.setLayoutBoundsSize(unscaledWidth, unscaledHeight);
                      labelDisplay.setLayoutBoundsPosition(0, 0);
                  }
              }
          }
  
          /**
           *  Adds the padding properties to the controls.
           * 
           *  @private
           */
          private function updatePadding() : void
          {
              var sides:Array = [ "Top", "Right", "Bottom", "Left" ];
              var padding:Number;
              var paddingStr:String;
              var paddingArr:Array;
              var paddingLen:Number
              var side:Number;
              var i:String;
  
              /* Set the padding on the textDisplay control.
              This will also support for the individual paddingTop,
              paddingRight, [...] properties and they will override
              the short-hand padding property. */
              if(textDisplay)
              {
                  paddingStr = getStyle("padding");
                  if(paddingStr != null)
                  {
                      paddingArr = paddingStr.replace(/^\s+|\s+$/g, '').split(" ");
                      paddingLen = paddingArr.length;
  
                      for(i in sides) 
                      {
                          side = (paddingLen == 3 &amp;&amp; side == 2) ? 1 : Number(i) % paddingLen;
  
                          if(textDisplay.getStyle("padding" + sides[i]) != paddingArr[side])
                          {
                              textDisplay.setStyle("padding" + sides[i], parseInt(paddingArr[side]));
                          }
                      }
                  }
  
                  for (i in sides) 
                  {
                      padding = getStyle("padding" + sides[i]);
  
                      if(!isNaN(padding) &amp;&amp; textDisplay.getStyle("padding" + sides[i]) != padding)
                          textDisplay.setStyle("padding" + sides[i], padding);
                  }
              }
  
              /* Set the padding on the labelDisplay control. */
              if(labelDisplay)
              {
                  paddingStr = getStyle("labelPadding");
                  if(paddingStr != null)
                  {
                      paddingArr = paddingStr.replace(/^\s+|\s+$/g, '').split(" ");
                      paddingLen = paddingArr.length;
  
                      for (i in sides) 
                      {
                          side = (paddingLen == 3 &amp;&amp; side == 2) ? 1 : Number(i) % paddingLen;
                          if(labelDisplay.getStyle("padding" + sides[i]) != paddingArr[side])
                              labelDisplay.setStyle("padding" + sides[i], paddingArr[side]);
                      }
                  }
              }
          }
  
          /**
           *  @private
           */
          override public function styleChanged(styleProp:String):void
          {
              var allStyles:Boolean = !styleProp || styleProp == "styleName";
  
              super.styleChanged(styleProp);
  
              if (allStyles || styleProp.indexOf("padding") == 0)
              {
                  paddingChanged = true;
                  invalidateProperties();
              }
          }
          ]]&gt;
      &lt;/fx:Script&gt;
  
      &lt;fx:Script&gt;
          &lt;![CDATA[
          /** 
           * @private 
           */     
          private static const focusExclusions:Array = ["textDisplay"];
  
          /**
           *  @private
           */
          override public function get focusSkinExclusions():Array { return focusExclusions;};
          ]]&gt;
      &lt;/fx:Script&gt;
  
      &lt;s:states&gt;
          &lt;s:State name="normal"/&gt;
          &lt;s:State name="focused"/&gt;
          &lt;s:State name="disabled" stateGroups="disabledStates"/&gt;
          &lt;s:State name="normalWithPrompt"/&gt;
          &lt;s:State name="disabledWithPrompt" stateGroups="disabledStates"/&gt;
      &lt;/s:states&gt;
  
      &lt;!-- border --&gt; 
      &lt;!--- @private --&gt;
      &lt;s:Rect left="0" right="0" top="0" bottom="0" id="border" radiusX="3" excludeFrom="focused"&gt;
          &lt;s:stroke&gt;     
              &lt;!--- @private --&gt;
              &lt;s:SolidColorStroke id="borderStroke" weight="1" /&gt;
          &lt;/s:stroke&gt;
      &lt;/s:Rect&gt;  
  
      &lt;!-- focusBorder --&gt; 
      &lt;!--- @private --&gt;
      &lt;s:Rect left="0" right="0" top="0" bottom="0" id="focusBorder" radiusX="3" includeIn="focused"&gt;
          &lt;s:stroke&gt;     
              &lt;!--- @private --&gt;
              &lt;s:SolidColorStroke id="focusBorderStroke" weight="1" /&gt;
          &lt;/s:stroke&gt;
      &lt;/s:Rect&gt;
  
      &lt;!-- fill --&gt;
      &lt;!--- Defines the appearance of the TextInput component's background. --&gt;
      &lt;s:Rect id="background" left="1" right="1" top="1" bottom="1" radiusX="3"&gt;
          &lt;s:fill&gt;
              &lt;!--- @private Defines the background fill color. --&gt;
              &lt;s:SolidColor id="bgFill" color="0xFFFFFF" /&gt;
          &lt;/s:fill&gt;
      &lt;/s:Rect&gt;
  
      &lt;!-- shadow --&gt;
      &lt;!--- @private --&gt;
      &lt;s:Rect left="1" top="1" right="1" height="1" id="shadow" radiusX="3"&gt;
          &lt;s:fill&gt;
              &lt;s:SolidColor color="0x000000" alpha="0.12" /&gt;
          &lt;/s:fill&gt;
      &lt;/s:Rect&gt;
  
      &lt;s:HGroup gap="0"&gt;
          &lt;!--- Defines the Label that is used for prompt text. The includeInLayout property is false so the prompt text does not affect measurement. --&gt;
          &lt;s:Label id="labelDisplay"/&gt;
  
          &lt;!-- text --&gt;
          &lt;!--- @copy spark.components.supportClasses.SkinnableTextBase#textDisplay --&gt;
          &lt;s:RichEditableText id="textDisplay"
                    verticalAlign="middle"
                    widthInChars="20"/&gt;
      &lt;/s:HGroup&gt;
  
  &lt;/s:SparkSkin&gt;
  </code></pre>