scaffold.libs_as.feathers.controls.TextInput.as Maven / Gradle / Ivy
/*
Feathers
Copyright 2012-2015 Bowler Hat LLC. All Rights Reserved.
This program is free software. You can redistribute and/or modify it in
accordance with the terms of the accompanying license agreement.
*/
package feathers.controls
{
import feathers.core.FeathersControl;
import feathers.core.IFeathersControl;
import feathers.core.IMultilineTextEditor;
import feathers.core.INativeFocusOwner;
import feathers.core.IStateContext;
import feathers.core.IStateObserver;
import feathers.core.ITextBaselineControl;
import feathers.core.ITextEditor;
import feathers.core.ITextRenderer;
import feathers.core.IValidating;
import feathers.core.PopUpManager;
import feathers.core.PropertyProxy;
import feathers.events.FeathersEventType;
import feathers.layout.VerticalAlign;
import feathers.skins.IStyleProvider;
import flash.display.InteractiveObject;
import flash.geom.Point;
import flash.ui.Mouse;
import flash.ui.MouseCursor;
import starling.display.DisplayObject;
import starling.events.Event;
import starling.events.Touch;
import starling.events.TouchEvent;
import starling.events.TouchPhase;
/**
* Dispatched when the text input's text
property changes.
*
* The properties of the event object have the following values:
*
* Property Value
* bubbles
false
* currentTarget
The Object that defines the
* event listener that handles the event. For example, if you use
* myButton.addEventListener()
to register an event listener,
* myButton is the value of the currentTarget
.
* data
null
* target
The Object that dispatched the event;
* it is not always the Object listening for the event. Use the
* currentTarget
property to always access the Object
* listening for the event.
*
*
* @eventType starling.events.Event.CHANGE
*/
[Event(name="change",type="starling.events.Event")]
/**
* Dispatched when the user presses the Enter key while the text input
* has focus. This event may not be dispatched at all times. Certain text
* editors will not dispatch an event for the enter key on some platforms,
* depending on the values of certain properties. This may include the
* default values for some platforms! If you've encountered this issue,
* please see the specific text editor's API documentation for complete
* details of this event's limitations and requirements.
*
* The properties of the event object have the following values:
*
* Property Value
* bubbles
false
* currentTarget
The Object that defines the
* event listener that handles the event. For example, if you use
* myButton.addEventListener()
to register an event listener,
* myButton is the value of the currentTarget
.
* data
null
* target
The Object that dispatched the event;
* it is not always the Object listening for the event. Use the
* currentTarget
property to always access the Object
* listening for the event.
*
*
* @eventType feathers.events.FeathersEventType.ENTER
*/
[Event(name="enter",type="starling.events.Event")]
/**
* Dispatched when the text input receives focus.
*
* The properties of the event object have the following values:
*
* Property Value
* bubbles
false
* currentTarget
The Object that defines the
* event listener that handles the event. For example, if you use
* myButton.addEventListener()
to register an event listener,
* myButton is the value of the currentTarget
.
* data
null
* target
The Object that dispatched the event;
* it is not always the Object listening for the event. Use the
* currentTarget
property to always access the Object
* listening for the event.
*
*
* @eventType feathers.events.FeathersEventType.FOCUS_IN
*/
[Event(name="focusIn",type="starling.events.Event")]
/**
* Dispatched when the text input loses focus.
*
* The properties of the event object have the following values:
*
* Property Value
* bubbles
false
* currentTarget
The Object that defines the
* event listener that handles the event. For example, if you use
* myButton.addEventListener()
to register an event listener,
* myButton is the value of the currentTarget
.
* data
null
* target
The Object that dispatched the event;
* it is not always the Object listening for the event. Use the
* currentTarget
property to always access the Object
* listening for the event.
*
*
* @eventType feathers.events.FeathersEventType.FOCUS_OUT
*/
[Event(name="focusOut",type="starling.events.Event")]
/**
* Dispatched when the soft keyboard is activated by the text editor. Not
* all text editors will activate a soft keyboard.
*
* The properties of the event object have the following values:
*
* Property Value
* bubbles
false
* currentTarget
The Object that defines the
* event listener that handles the event. For example, if you use
* myButton.addEventListener()
to register an event listener,
* myButton is the value of the currentTarget
.
* data
null
* target
The Object that dispatched the event;
* it is not always the Object listening for the event. Use the
* currentTarget
property to always access the Object
* listening for the event.
*
*
* @eventType feathers.events.FeathersEventType.SOFT_KEYBOARD_ACTIVATE
*/
[Event(name="softKeyboardActivate",type="starling.events.Event")]
/**
* Dispatched when the soft keyboard is deactivated by the text editor. Not
* all text editors will activate a soft keyboard.
*
* The properties of the event object have the following values:
*
* Property Value
* bubbles
false
* currentTarget
The Object that defines the
* event listener that handles the event. For example, if you use
* myButton.addEventListener()
to register an event listener,
* myButton is the value of the currentTarget
.
* data
null
* target
The Object that dispatched the event;
* it is not always the Object listening for the event. Use the
* currentTarget
property to always access the Object
* listening for the event.
*
*
* @eventType feathers.events.FeathersEventType.SOFT_KEYBOARD_DEACTIVATE
*/
[Event(name="softKeyboardDeactivate",type="starling.events.Event")]
/**
* Dispatched when the display object's state changes.
*
* The properties of the event object have the following values:
*
* Property Value
* bubbles
false
* currentTarget
The Object that defines the
* event listener that handles the event. For example, if you use
* myButton.addEventListener()
to register an event listener,
* myButton is the value of the currentTarget
.
* data
null
* target
The Object that dispatched the event;
* it is not always the Object listening for the event. Use the
* currentTarget
property to always access the Object
* listening for the event.
*
*
* @eventType feathers.events.FeathersEventType.STATE_CHANGE
*
* @see #currentState
*/
[Event(name="stateChange",type="starling.events.Event")]
/**
* A text entry control that allows users to enter and edit a single line of
* uniformly-formatted text.
*
* The following example sets the text in a text input, selects the text,
* and listens for when the text value changes:
*
*
* var input:TextInput = new TextInput();
* input.text = "Hello World";
* input.selectRange( 0, input.text.length );
* input.addEventListener( Event.CHANGE, input_changeHandler );
* this.addChild( input );
*
* @see ../../../help/text-input.html How to use the Feathers TextInput component
* @see ../../../help/text-editors.html Introduction to Feathers text editors
* @see feathers.core.ITextEditor
* @see feathers.controls.AutoComplete
* @see feathers.controls.TextArea
*/
public class TextInput extends FeathersControl implements ITextBaselineControl, INativeFocusOwner, IStateContext
{
/**
* @private
*/
private static const HELPER_POINT:Point = new Point();
/**
* @private
*/
protected static const INVALIDATION_FLAG_PROMPT_FACTORY:String = "promptFactory";
/**
* @private
*/
protected static const INVALIDATION_FLAG_ERROR_CALLOUT_FACTORY:String = "errorCalloutFactory";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.TextInputState.ENABLED
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const STATE_ENABLED:String = "enabled";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.TextInputState.DISABLED
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const STATE_DISABLED:String = "disabled";
/**
* @private
* DEPRECATED: Replaced by feathers.controls.TextInputState.FOCUSED
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const STATE_FOCUSED:String = "focused";
/**
* The default value added to the styleNameList
of the text
* editor.
*
* @see feathers.core.FeathersControl#styleNameList
*/
public static const DEFAULT_CHILD_STYLE_NAME_TEXT_EDITOR:String = "feathers-text-input-text-editor";
/**
* The default value added to the styleNameList
of the
* prompt text renderer.
*
* @see feathers.core.FeathersControl#styleNameList
*/
public static const DEFAULT_CHILD_STYLE_NAME_PROMPT:String = "feathers-text-input-prompt";
/**
* The default value added to the styleNameList
of the
* error callout.
*
* @see feathers.core.FeathersControl#styleNameList
*/
public static const DEFAULT_CHILD_STYLE_NAME_ERROR_CALLOUT:String = "feathers-text-input-error-callout";
/**
* An alternate style name to use with TextInput
to allow a
* theme to give it a search input style. If a theme does not provide a
* style for the search text input, the theme will automatically fal
* back to using the default text input style.
*
* An alternate style name should always be added to a component's
* styleNameList
before the component is initialized. If
* the style name is added later, it will be ignored.
*
* In the following example, the search style is applied to a text
* input:
*
*
* var input:TextInput = new TextInput();
* input.styleNameList.add( TextInput.ALTERNATE_STYLE_NAME_SEARCH_TEXT_INPUT );
* this.addChild( input );
*
* @see feathers.core.FeathersControl#styleNameList
*/
public static const ALTERNATE_STYLE_NAME_SEARCH_TEXT_INPUT:String = "feathers-search-text-input";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.VerticalAlign.TOP
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const VERTICAL_ALIGN_TOP:String = "top";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.VerticalAlign.MIDDLE
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const VERTICAL_ALIGN_MIDDLE:String = "middle";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.VerticalAlign.BOTTOM
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const VERTICAL_ALIGN_BOTTOM:String = "bottom";
/**
* @private
* DEPRECATED: Replaced by feathers.layout.VerticalAlign.JUSTIFY
.
*
* DEPRECATION WARNING: This constant is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public static const VERTICAL_ALIGN_JUSTIFY:String = "justify";
/**
* The default IStyleProvider
for all TextInput
* components.
*
* @default null
* @see feathers.core.FeathersControl#styleProvider
*/
public static var globalStyleProvider:IStyleProvider;
/**
* Constructor.
*/
public function TextInput()
{
super();
this.addEventListener(TouchEvent.TOUCH, textInput_touchHandler);
this.addEventListener(Event.REMOVED_FROM_STAGE, textInput_removedFromStageHandler);
}
/**
* The text editor sub-component.
*
* For internal use in subclasses.
*/
protected var textEditor:ITextEditor;
/**
* The prompt text renderer sub-component.
*
* For internal use in subclasses.
*/
protected var promptTextRenderer:ITextRenderer;
/**
* The currently selected background, based on state.
*
* For internal use in subclasses.
*/
protected var currentBackground:DisplayObject;
/**
* The currently visible icon. The value will be null
if
* there is no currently visible icon.
*
* For internal use in subclasses.
*/
protected var currentIcon:DisplayObject;
/**
* The TextCallout
that displays the value of the
* errorString
property. The value may be
* null
if there is no current error string or the text
* input does not have focus.
*
* For internal use in subclasses.
*/
protected var callout:TextCallout;
/**
* The value added to the styleNameList
of the text editor.
* This variable is protected
so that sub-classes can
* customize the text editor style name in their constructors instead of
* using the default style name defined by
* DEFAULT_CHILD_STYLE_NAME_TEXT_EDITOR
.
*
* @see feathers.core.FeathersControl#styleNameList
*/
protected var textEditorStyleName:String = DEFAULT_CHILD_STYLE_NAME_TEXT_EDITOR;
/**
* The value added to the styleNameList
of the prompt text
* renderer. This variable is protected
so that sub-classes
* can customize the prompt text renderer style name in their
* constructors instead of using the default style name defined by
* DEFAULT_CHILD_STYLE_NAME_PROMPT
.
*
* @see feathers.core.FeathersControl#styleNameList
*/
protected var promptStyleName:String = DEFAULT_CHILD_STYLE_NAME_PROMPT;
/**
* The value added to the styleNameList
of the error
* callout. This variable is protected
so that sub-classes
* can customize the prompt text renderer style name in their
* constructors instead of using the default style name defined by
* DEFAULT_CHILD_STYLE_NAME_ERROR_CALLOUT
.
*
* @see feathers.core.FeathersControl#styleNameList
*/
protected var errorCalloutStyleName:String = DEFAULT_CHILD_STYLE_NAME_ERROR_CALLOUT;
/**
* @private
*/
protected var _textEditorHasFocus:Boolean = false;
/**
* A text editor may be an INativeFocusOwner
, so we need to
* return the value of its nativeFocus
property. If not,
* then we return null
.
*
* @see feathers.core.INativeFocusOwner
*/
public function get nativeFocus():InteractiveObject
{
if(this.textEditor is INativeFocusOwner)
{
return INativeFocusOwner(this.textEditor).nativeFocus;
}
return null;
}
/**
* @private
*/
protected var _ignoreTextChanges:Boolean = false;
/**
* @private
*/
protected var _touchPointID:int = -1;
/**
* @private
*/
override protected function get defaultStyleProvider():IStyleProvider
{
return TextInput.globalStyleProvider;
}
/**
* When the FocusManager
isn't enabled, hasFocus
* can be used instead of FocusManager.focus == textInput
* to determine if the text input has focus.
*/
public function get hasFocus():Boolean
{
if(!this._focusManager)
{
return this._textEditorHasFocus;
}
return this._hasFocus;
}
/**
* @private
*/
override public function set isEnabled(value:Boolean):void
{
super.isEnabled = value;
this.refreshState();
}
/**
* @private
*/
protected var _currentState:String = TextInputState.ENABLED;
/**
* The current state of the text input.
*
* @see feathers.controls.TextInputState
* @see #event:stateChange feathers.events.FeathersEventType.STATE_CHANGE
*/
public function get currentState():String
{
return this._currentState;
}
/**
* @private
*/
protected var _text:String = "";
/**
* The text displayed by the text input. The text input dispatches
* Event.CHANGE
when the value of the text
* property changes for any reason.
*
* In the following example, the text input's text is updated:
*
*
* input.text = "Hello World";
*
* @see #event:change
*
* @default ""
*/
public function get text():String
{
return this._text;
}
/**
* @private
*/
public function set text(value:String):void
{
if(!value)
{
//don't allow null or undefined
value = "";
}
if(this._text == value)
{
return;
}
this._text = value;
this.invalidate(INVALIDATION_FLAG_DATA);
this.dispatchEventWith(Event.CHANGE);
}
/**
* The baseline measurement of the text, in pixels.
*/
public function get baseline():Number
{
if(!this.textEditor)
{
return 0;
}
return this.textEditor.y + this.textEditor.baseline;
}
/**
* @private
*/
protected var _prompt:String = null;
/**
* The prompt, hint, or description text displayed by the input when the
* value of its text is empty.
*
* In the following example, the text input's prompt is updated:
*
*
* input.prompt = "User Name";
*
* @default null
*/
public function get prompt():String
{
return this._prompt;
}
/**
* @private
*/
public function set prompt(value:String):void
{
if(this._prompt == value)
{
return;
}
this._prompt = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _typicalText:String = null;
/**
* If not null
, the dimensions of the
* typicalText
will be used in the calculation of the text
* input's full dimensions. If the text input's dimensions haven't been
* set explicitly, it's calculated dimensions will be at least large
* enough to display the typicalText
. Other children, such
* as the background skin and the prompt text renderer may also affect
* the dimensions of the text input, allowing it to, potentially, be
* bigger than the rendered typicalText
.
*
* In the following example, the text input's typical text is
* updated:
*
*
* input.text = "We want to allow the text input to show all of this text";
*
* @default null
*/
public function get typicalText():String
{
return this._typicalText;
}
/**
* @private
*/
public function set typicalText(value:String):void
{
if(this._typicalText === value)
{
return;
}
this._typicalText = value;
this.invalidate(INVALIDATION_FLAG_DATA);
}
/**
* @private
*/
protected var _maxChars:int = 0;
/**
* The maximum number of characters that may be entered. If 0
,
* any number of characters may be entered.
*
* In the following example, the text input's maximum characters is
* specified:
*
*
* input.maxChars = 10;
*
* @default 0
*/
public function get maxChars():int
{
return this._maxChars;
}
/**
* @private
*/
public function set maxChars(value:int):void
{
if(this._maxChars == value)
{
return;
}
this._maxChars = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _restrict:String;
/**
* Limits the set of characters that may be entered.
*
* In the following example, the text input's allowed characters are
* restricted:
*
*
* input.restrict = "0-9";
*
* @default null
*/
public function get restrict():String
{
return this._restrict;
}
/**
* @private
*/
public function set restrict(value:String):void
{
if(this._restrict == value)
{
return;
}
this._restrict = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _displayAsPassword:Boolean = false;
/**
* Determines if the entered text will be masked so that it cannot be
* seen, such as for a password input.
*
* In the following example, the text input's text is displayed as
* a password:
*
*
* input.displayAsPassword = true;
*
* @default false
*/
public function get displayAsPassword():Boolean
{
return this._displayAsPassword;
}
/**
* @private
*/
public function set displayAsPassword(value:Boolean):void
{
if(this._displayAsPassword == value)
{
return;
}
this._displayAsPassword = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _isEditable:Boolean = true;
/**
* Determines if the text input is editable. If the text input is not
* editable, it will still appear enabled.
*
* In the following example, the text input is not editable:
*
*
* input.isEditable = false;
*
* @default true
*
* @see #isSelectable
*/
public function get isEditable():Boolean
{
return this._isEditable;
}
/**
* @private
*/
public function set isEditable(value:Boolean):void
{
if(this._isEditable == value)
{
return;
}
this._isEditable = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _isSelectable:Boolean = true;
/**
* If the isEditable
property is set to false
,
* the isSelectable
property determines if the text is
* selectable. If the isEditable
property is set to
* true
, the text will always be selectable.
*
* In the following example, the text input is not selectable:
*
*
* input.isEditable = false;
* input.isSelectable = false;
*
* @default true
*
* @see #isEditable
*/
public function get isSelectable():Boolean
{
return this._isSelectable;
}
/**
* @private
*/
public function set isSelectable(value:Boolean):void
{
if(this._isSelectable == value)
{
return;
}
this._isSelectable = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _errorString:String = null;
/**
* Error text to display in a Callout
when the input has
* focus. When this value is not null
the input's state is
* changed to TextInputState.ERROR
.
*
* An empty string will change the background, but no
* Callout
will appear on focus.
*
* To clear an error, the errorString
property must be set
* to null
*
* The following example displays an error string:
*
*
* input.errorString = "Something is wrong";
*
* @default null
*
* @see #currentState
*/
public function get errorString():String
{
return this._errorString;
}
/**
* @private
*/
public function set errorString(value:String):void
{
if(this._errorString === value)
{
return;
}
this._errorString = value;
this.refreshState();
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _textEditorFactory:Function;
/**
* A function used to instantiate the text editor. If null,
* FeathersControl.defaultTextEditorFactory
is used
* instead. The text editor must be an instance of
* ITextEditor
. This factory can be used to change
* properties on the text editor when it is first created. For instance,
* if you are skinning Feathers components without a theme, you might
* use this factory to set styles on the text editor.
*
* The factory should have the following function signature:
* function():ITextEditor
*
* In the following example, a custom text editor factory is passed
* to the text input:
*
*
* input.textEditorFactory = function():ITextEditor
* {
* return new TextFieldTextEditor();
* };
*
* @default null
*
* @see feathers.core.ITextEditor
* @see feathers.core.FeathersControl#defaultTextEditorFactory
*/
public function get textEditorFactory():Function
{
return this._textEditorFactory;
}
/**
* @private
*/
public function set textEditorFactory(value:Function):void
{
if(this._textEditorFactory == value)
{
return;
}
this._textEditorFactory = value;
this.invalidate(INVALIDATION_FLAG_TEXT_EDITOR);
}
/**
* @private
*/
protected var _customTextEditorStyleName:String;
/**
* A style name to add to the text input's text editor sub-component.
* Typically used by a theme to provide different styles to different
* text inputs.
*
* In the following example, a custom text editor style name is
* passed to the text input:
*
*
* input.customTextEditorStyleName = "my-custom-text-input-text-editor";
*
* In your theme, you can target this sub-component style name to
* provide different styles than the default:
*
*
* getStyleProviderForClass( StageTextTextEditor ).setFunctionForStyleName( "my-custom-text-input-text-editor", setCustomTextInputTextEditorStyles );
*
* @default null
*
* @see #DEFAULT_CHILD_STYLE_NAME_TEXT_EDITOR
* @see feathers.core.FeathersControl#styleNameList
* @see #textEditorFactory
*/
public function get customTextEditorStyleName():String
{
return this._customTextEditorStyleName;
}
/**
* @private
*/
public function set customTextEditorStyleName(value:String):void
{
if(this._customTextEditorStyleName == value)
{
return;
}
this._customTextEditorStyleName = value;
this.invalidate(INVALIDATION_FLAG_TEXT_RENDERER);
}
/**
* @private
*/
protected var _promptFactory:Function;
/**
* A function used to instantiate the prompt text renderer. If null,
* FeathersControl.defaultTextRendererFactory
is used
* instead. The prompt text renderer must be an instance of
* ITextRenderer
. This factory can be used to change
* properties on the prompt when it is first created. For instance, if
* you are skinning Feathers components without a theme, you might use
* this factory to set styles on the prompt.
*
* The factory should have the following function signature:
* function():ITextRenderer
*
* If the prompt
property is null
, the
* prompt text renderer will not be created.
*
* In the following example, a custom prompt factory is passed to the
* text input:
*
*
* input.promptFactory = function():ITextRenderer
* {
* return new TextFieldTextRenderer();
* };
*
* @default null
*
* @see #prompt
* @see feathers.core.ITextRenderer
* @see feathers.core.FeathersControl#defaultTextRendererFactory
*/
public function get promptFactory():Function
{
return this._promptFactory;
}
/**
* @private
*/
public function set promptFactory(value:Function):void
{
if(this._promptFactory == value)
{
return;
}
this._promptFactory = value;
this.invalidate(INVALIDATION_FLAG_PROMPT_FACTORY);
}
/**
* @private
*/
protected var _customPromptStyleName:String;
/**
* A style name to add to the text input's prompt text renderer
* sub-component. Typically used by a theme to provide different styles
* to different text inputs.
*
* In the following example, a custom prompt text renderer style name
* is passed to the text input:
*
*
* input.customPromptStyleName = "my-custom-text-input-prompt";
*
* In your theme, you can target this sub-component style name to
* provide different styles than the default:
*
*
* getStyleProviderForClass( BitmapFontTextRenderer ).setFunctionForStyleName( "my-custom-text-input-prompt", setCustomTextInputPromptStyles );
*
* @default null
*
* @see #DEFAULT_CHILD_STYLE_NAME_PROMPT
* @see feathers.core.FeathersControl#styleNameList
* @see #promptFactory
*/
public function get customPromptStyleName():String
{
return this._customPromptStyleName;
}
/**
* @private
*/
public function set customPromptStyleName(value:String):void
{
if(this._customPromptStyleName == value)
{
return;
}
this._customPromptStyleName = value;
this.invalidate(INVALIDATION_FLAG_TEXT_RENDERER);
}
/**
* @private
*/
protected var _promptProperties:PropertyProxy;
/**
* An object that stores properties for the input's prompt text
* renderer sub-component, and the properties will be passed down to the
* text renderer when the input validates. The available properties
* depend on which ITextRenderer
implementation is returned
* by messageFactory
. Refer to
* feathers.core.ITextRenderer
* for a list of available text renderer implementations.
*
* If the subcomponent has its own subcomponents, their properties
* can be set too, using attribute @
notation. For example,
* to set the skin on the thumb which is in a SimpleScrollBar
,
* which is in a List
, you can use the following syntax:
* list.verticalScrollBarProperties.@thumbProperties.defaultSkin = new Image(texture);
*
* Setting properties in a promptFactory
function
* instead of using promptProperties
will result in
* better performance.
*
* In the following example, the text input's prompt's properties are
* updated (this example assumes that the prompt text renderer is a
* TextFieldTextRenderer
):
*
*
* input.promptProperties.textFormat = new TextFormat( "Source Sans Pro", 16, 0x333333 );
* input.promptProperties.embedFonts = true;
*
* @default null
*
* @see #prompt
* @see #promptFactory
* @see feathers.core.ITextRenderer
*/
public function get promptProperties():Object
{
if(!this._promptProperties)
{
this._promptProperties = new PropertyProxy(childProperties_onChange);
}
return this._promptProperties;
}
/**
* @private
*/
public function set promptProperties(value:Object):void
{
if(this._promptProperties == value)
{
return;
}
if(!value)
{
value = new PropertyProxy();
}
if(!(value is PropertyProxy))
{
var newValue:PropertyProxy = new PropertyProxy();
for(var propertyName:String in value)
{
newValue[propertyName] = value[propertyName];
}
value = newValue;
}
if(this._promptProperties)
{
this._promptProperties.removeOnChangeCallback(childProperties_onChange);
}
this._promptProperties = PropertyProxy(value);
if(this._promptProperties)
{
this._promptProperties.addOnChangeCallback(childProperties_onChange);
}
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _customErrorCalloutStyleName:String;
/**
* A style name to add to the text input's error callout sub-component.
* Typically used by a theme to provide different styles to different
* text inputs.
*
* In the following example, a custom error callout style name
* is passed to the text input:
*
*
* input.customErrorCalloutStyleName = "my-custom-text-input-error-callout";
*
* In your theme, you can target this sub-component style name to
* provide different styles than the default:
*
*
* getStyleProviderForClass( Callout ).setFunctionForStyleName( "my-custom-text-input-error-callout", setCustomTextInputErrorCalloutStyles );
*
* @default null
*
* @see #DEFAULT_CHILD_STYLE_NAME_ERROR_CALLOUT
* @see feathers.core.FeathersControl#styleNameList
*/
public function get customErrorCalloutStyleName():String
{
return this._customErrorCalloutStyleName;
}
/**
* @private
*/
public function set customErrorCalloutStyleName(value:String):void
{
if(this._customErrorCalloutStyleName == value)
{
return;
}
this._customErrorCalloutStyleName = value;
this.invalidate(INVALIDATION_FLAG_ERROR_CALLOUT_FACTORY);
}
/**
* @private
* The width of the first background skin that was displayed.
*/
protected var _originalSkinWidth:Number = NaN;
/**
* @private
* The height of the first background skin that was displayed.
*/
protected var _originalSkinHeight:Number = NaN;
/**
* @private
*/
protected var _backgroundSkin:DisplayObject;
/**
* The skin used when no other skin is defined for the current state.
* Intended for use when multiple states should use the same skin.
*
* The following example gives the input a default skin to use for
* all states when no specific skin is available:
*
*
* input.backgroundSkin = new Image( texture );
*
* @default null
*
* @see #setSkinForState()
*/
public function get backgroundSkin():DisplayObject
{
return this._backgroundSkin;
}
/**
* @private
*/
public function set backgroundSkin(value:DisplayObject):void
{
if(this._backgroundSkin === value)
{
return;
}
this._backgroundSkin = value;
this.invalidate(INVALIDATION_FLAG_SKIN);
}
/**
* @private
*/
protected var _stateToSkin:Object = {};
/**
* The skin used for the input's enabled state. If null
,
* then backgroundSkin
is used instead.
*
* The following example gives the input a skin for the enabled state:
*
*
* input.backgroundEnabledSkin = new Image( texture );
*
* @default null
*/
public function get backgroundEnabledSkin():DisplayObject
{
return this.getSkinForState(TextInputState.ENABLED);
}
/**
* @private
*/
public function set backgroundEnabledSkin(value:DisplayObject):void
{
this.setSkinForState(TextInputState.ENABLED, value);
}
/**
* The skin used for the input's focused state. If null
,
* then backgroundSkin
is used instead.
*
* The following example gives the input a skin for the focused state:
*
*
* input.backgroundFocusedSkin = new Image( texture );
*
* @default null
*/
public function get backgroundFocusedSkin():DisplayObject
{
return this.getSkinForState(TextInputState.FOCUSED);
}
/**
* @private
*/
public function set backgroundFocusedSkin(value:DisplayObject):void
{
this.setSkinForState(TextInputState.FOCUSED, value);
}
/**
* The skin used for the input's error state. If null
,
* then backgroundSkin
is used instead.
*
* The following example gives the input a skin for the error state:
*
*
* input.backgroundErrorSkin = new Image( texture );
*
* @default null
*/
public function get backgroundErrorSkin():DisplayObject
{
return this.getSkinForState(TextInputState.ERROR);
}
/**
* @private
*/
public function set backgroundErrorSkin(value:DisplayObject):void
{
this.setSkinForState(TextInputState.ERROR, value);
}
/**
* The skin used for the input's disabled state. If null
,
* then backgroundSkin
is used instead.
*
* The following example gives the input a skin for the disabled state:
*
*
* input.backgroundDisabledSkin = new Image( texture );
*
* @default null
*/
public function get backgroundDisabledSkin():DisplayObject
{
return this.getSkinForState(TextInputState.DISABLED);
}
/**
* @private
*/
public function set backgroundDisabledSkin(value:DisplayObject):void
{
this.setSkinForState(TextInputState.DISABLED, value);
}
/**
* @private
*/
protected var _stateToSkinFunction:Function;
/**
* DEPRECATED: Create a feathers.skins.ImageSkin
instead,
* and pass to the backgroundSkin
property.
*
* DEPRECATION WARNING: This property is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public function get stateToSkinFunction():Function
{
return this._stateToSkinFunction;
}
/**
* @private
*/
public function set stateToSkinFunction(value:Function):void
{
if(this._stateToSkinFunction == value)
{
return;
}
this._stateToSkinFunction = value;
this.invalidate(INVALIDATION_FLAG_SKIN);
}
/**
* @private
* The width of the first icon that was displayed.
*/
protected var _originalIconWidth:Number = NaN;
/**
* @private
* The height of the first icon that was displayed.
*/
protected var _originalIconHeight:Number = NaN;
/**
* @private
*/
protected var _defaultIcon:DisplayObject;
/**
* The icon used when no other icon is defined for the current state.
* Intended for use when multiple states should use the same icon.
*
* The following example gives the input a default icon to use for
* all states when no specific icon is available:
*
*
* input.defaultIcon = new Image( texture );
*
* @default null
*
* @see #setIconForState()
*/
public function get defaultIcon():DisplayObject
{
return this._defaultIcon;
}
/**
* @private
*/
public function set defaultIcon(value:DisplayObject):void
{
if(this._defaultIcon === value)
{
return;
}
this._defaultIcon = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _stateToIcon:Object = {};
/**
* The icon used for the input's enabled state. If null
,
* then defaultIcon
is used instead.
*
* The following example gives the input an icon for the enabled state:
*
*
* button.enabledIcon = new Image( texture );
*
* @default null
*/
public function get enabledIcon():DisplayObject
{
return this.getIconForState(TextInputState.ENABLED);
}
/**
* @private
*/
public function set enabledIcon(value:DisplayObject):void
{
this.setIconForState(TextInputState.ENABLED, value);
}
/**
* The icon used for the input's disabled state. If null
,
* then defaultIcon
is used instead.
*
* The following example gives the input an icon for the disabled state:
*
*
* button.disabledIcon = new Image( texture );
*
* @default null
*/
public function get disabledIcon():DisplayObject
{
return this.getIconForState(TextInputState.DISABLED);
}
/**
* @private
*/
public function set disabledIcon(value:DisplayObject):void
{
this.setIconForState(TextInputState.DISABLED, value);
}
/**
* The icon used for the input's focused state. If null
,
* then defaultIcon
is used instead.
*
* The following example gives the input an icon for the focused state:
*
*
* button.focusedIcon = new Image( texture );
*
* @default null
*/
public function get focusedIcon():DisplayObject
{
return this.getIconForState(TextInputState.FOCUSED);
}
/**
* @private
*/
public function set focusedIcon(value:DisplayObject):void
{
this.setIconForState(TextInputState.FOCUSED, value);
}
/**
* The icon used for the input's error state. If null
,
* then defaultIcon
is used instead.
*
* The following example gives the input an icon for the error state:
*
*
* button.errorIcon = new Image( texture );
*
* @default null
*/
public function get errorIcon():DisplayObject
{
return this.getIconForState(TextInputState.ERROR);
}
/**
* @private
*/
public function set errorIcon(value:DisplayObject):void
{
this.setIconForState(TextInputState.ERROR, value);
}
/**
* @private
*/
protected var _stateToIconFunction:Function;
/**
* DEPRECATED: Create a feathers.skins.ImageSkin
instead,
* and pass to the defaultIcon
property.
*
* DEPRECATION WARNING: This property is deprecated
* starting with Feathers 3.0. It will be removed in a future version of
* Feathers according to the standard
* Feathers deprecation policy.
*/
public function get stateToIconFunction():Function
{
return this._stateToIconFunction;
}
/**
* @private
*/
public function set stateToIconFunction(value:Function):void
{
if(this._stateToIconFunction == value)
{
return;
}
this._stateToIconFunction = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _gap:Number = 0;
/**
* The space, in pixels, between the icon and the text editor, if an
* icon exists.
*
* The following example creates a gap of 50 pixels between the icon
* and the text editor:
*
*
* button.defaultIcon = new Image( texture );
* button.gap = 50;
*
* @default 0
*/
public function get gap():Number
{
return this._gap;
}
/**
* @private
*/
public function set gap(value:Number):void
{
if(this._gap == value)
{
return;
}
this._gap = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* Quickly sets all padding properties to the same value. The
* padding
getter always returns the value of
* paddingTop
, but the other padding values may be
* different.
*
* In the following example, the text input's padding is set to
* 20 pixels:
*
*
* input.padding = 20;
*
* @default 0
*
* @see #paddingTop
* @see #paddingRight
* @see #paddingBottom
* @see #paddingLeft
*/
public function get padding():Number
{
return this._paddingTop;
}
/**
* @private
*/
public function set padding(value:Number):void
{
this.paddingTop = value;
this.paddingRight = value;
this.paddingBottom = value;
this.paddingLeft = value;
}
/**
* @private
*/
protected var _paddingTop:Number = 0;
/**
* The minimum space, in pixels, between the input's top edge and the
* input's content.
*
* In the following example, the text input's top padding is set to
* 20 pixels:
*
*
* input.paddingTop = 20;
*
* @default 0
*/
public function get paddingTop():Number
{
return this._paddingTop;
}
/**
* @private
*/
public function set paddingTop(value:Number):void
{
if(this._paddingTop == value)
{
return;
}
this._paddingTop = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _paddingRight:Number = 0;
/**
* The minimum space, in pixels, between the input's right edge and the
* input's content.
*
* In the following example, the text input's right padding is set to
* 20 pixels:
*
*
* input.paddingRight = 20;
*
* @default 0
*/
public function get paddingRight():Number
{
return this._paddingRight;
}
/**
* @private
*/
public function set paddingRight(value:Number):void
{
if(this._paddingRight == value)
{
return;
}
this._paddingRight = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _paddingBottom:Number = 0;
/**
* The minimum space, in pixels, between the input's bottom edge and
* the input's content.
*
* In the following example, the text input's bottom padding is set to
* 20 pixels:
*
*
* input.paddingBottom = 20;
*
* @default 0
*/
public function get paddingBottom():Number
{
return this._paddingBottom;
}
/**
* @private
*/
public function set paddingBottom(value:Number):void
{
if(this._paddingBottom == value)
{
return;
}
this._paddingBottom = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _paddingLeft:Number = 0;
/**
* The minimum space, in pixels, between the input's left edge and the
* input's content.
*
* In the following example, the text input's left padding is set to
* 20 pixels:
*
*
* input.paddingLeft = 20;
*
* @default 0
*/
public function get paddingLeft():Number
{
return this._paddingLeft;
}
/**
* @private
*/
public function set paddingLeft(value:Number):void
{
if(this._paddingLeft == value)
{
return;
}
this._paddingLeft = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected var _verticalAlign:String = VerticalAlign.MIDDLE;
[Inspectable(type="String",enumeration="top,middle,bottom,justify")]
/**
* The location where the text editor is aligned vertically (on
* the y-axis).
*
* The following example aligns the text editor to the top:
*
*
* input.verticalAlign = VerticalAlign.TOP;
*
* @default feathers.layout.VerticalAlign.MIDDLE
*
* @see feathers.layout.VerticalAlign#TOP
* @see feathers.layout.VerticalAlign#MIDDLE
* @see feathers.layout.VerticalAlign#BOTTOM
* @see feathers.layout.VerticalAlign#JUSTIFY
*/
public function get verticalAlign():String
{
return _verticalAlign;
}
/**
* @private
*/
public function set verticalAlign(value:String):void
{
if(this._verticalAlign == value)
{
return;
}
this._verticalAlign = value;
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
* Flag indicating that the text editor should get focus after it is
* created.
*/
protected var _isWaitingToSetFocus:Boolean = false;
/**
* @private
*/
protected var _pendingSelectionBeginIndex:int = -1;
/**
* @private
*/
protected var _pendingSelectionEndIndex:int = -1;
/**
* @private
*/
protected var _oldMouseCursor:String = null;
/**
* @private
*/
protected var _textEditorProperties:PropertyProxy;
/**
* An object that stores properties for the input's text editor
* sub-component, and the properties will be passed down to the
* text editor when the input validates. The available properties
* depend on which ITextEditor
implementation is returned
* by textEditorFactory
. Refer to
* feathers.core.ITextEditor
* for a list of available text editor implementations.
*
* If the subcomponent has its own subcomponents, their properties
* can be set too, using attribute @
notation. For example,
* to set the skin on the thumb which is in a SimpleScrollBar
,
* which is in a List
, you can use the following syntax:
* list.verticalScrollBarProperties.@thumbProperties.defaultSkin = new Image(texture);
*
* Setting properties in a textEditorFactory
function
* instead of using textEditorProperties
will result in
* better performance.
*
* In the following example, the text input's text editor properties
* are specified (this example assumes that the text editor is a
* StageTextTextEditor
):
*
*
* input.textEditorProperties.fontName = "Helvetica";
* input.textEditorProperties.fontSize = 16;
*
* @default null
*
* @see #textEditorFactory
* @see feathers.core.ITextEditor
*/
public function get textEditorProperties():Object
{
if(!this._textEditorProperties)
{
this._textEditorProperties = new PropertyProxy(childProperties_onChange);
}
return this._textEditorProperties;
}
/**
* @private
*/
public function set textEditorProperties(value:Object):void
{
if(this._textEditorProperties == value)
{
return;
}
if(!value)
{
value = new PropertyProxy();
}
if(!(value is PropertyProxy))
{
var newValue:PropertyProxy = new PropertyProxy();
for(var propertyName:String in value)
{
newValue[propertyName] = value[propertyName];
}
value = newValue;
}
if(this._textEditorProperties)
{
this._textEditorProperties.removeOnChangeCallback(childProperties_onChange);
}
this._textEditorProperties = PropertyProxy(value);
if(this._textEditorProperties)
{
this._textEditorProperties.addOnChangeCallback(childProperties_onChange);
}
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @copy feathers.core.ITextEditor#selectionBeginIndex
*/
public function get selectionBeginIndex():int
{
if(this._pendingSelectionBeginIndex >= 0)
{
return this._pendingSelectionBeginIndex;
}
if(this.textEditor)
{
return this.textEditor.selectionBeginIndex;
}
return 0;
}
/**
* @copy feathers.core.ITextEditor#selectionEndIndex
*/
public function get selectionEndIndex():int
{
if(this._pendingSelectionEndIndex >= 0)
{
return this._pendingSelectionEndIndex;
}
if(this.textEditor)
{
return this.textEditor.selectionEndIndex;
}
return 0;
}
/**
* @private
*/
override public function set visible(value:Boolean):void
{
if(!value)
{
this._isWaitingToSetFocus = false;
if(this._textEditorHasFocus)
{
this.textEditor.clearFocus();
}
}
super.visible = value;
}
/**
* @private
*/
override public function hitTest(localPoint:Point):DisplayObject
{
if(!this.visible || !this.touchable)
{
return null;
}
if(this.mask && !this.hitTestMask(localPoint))
{
return null;
}
return this._hitArea.containsPoint(localPoint) ? DisplayObject(this.textEditor) : null;
}
/**
* @inheritDoc
*/
override public function showFocus():void
{
if(!this._focusManager || this._focusManager.focus != this)
{
return;
}
this.selectRange(0, this._text.length);
super.showFocus();
}
/**
* Focuses the text input control so that it may be edited.
*/
public function setFocus():void
{
//if the text editor has focus, no need to set focus
//if this is invisible, it wouldn't make sense to set focus
//if there's a touch point ID, we'll be setting focus on our own
if(this._textEditorHasFocus || !this.visible || this._touchPointID >= 0)
{
return;
}
if(this.textEditor)
{
this._isWaitingToSetFocus = false;
this.textEditor.setFocus();
}
else
{
this._isWaitingToSetFocus = true;
this.invalidate(INVALIDATION_FLAG_SELECTED);
}
}
/**
* Manually removes focus from the text input control.
*/
public function clearFocus():void
{
this._isWaitingToSetFocus = false;
if(!this.textEditor || !this._textEditorHasFocus)
{
return;
}
this.textEditor.clearFocus();
}
/**
* Sets the range of selected characters. If both values are the same,
* or the end index is -1
, the text insertion position is
* changed and nothing is selected.
*/
public function selectRange(beginIndex:int, endIndex:int = -1):void
{
if(endIndex < 0)
{
endIndex = beginIndex;
}
if(beginIndex < 0)
{
throw new RangeError("Expected start index >= 0. Received " + beginIndex + ".");
}
if(endIndex > this._text.length)
{
throw new RangeError("Expected end index <= " + this._text.length + ". Received " + endIndex + ".");
}
//if it's invalid, we need to wait until validation before changing
//the selection
if(this.textEditor && (this._isValidating || !this.isInvalid()))
{
this._pendingSelectionBeginIndex = -1;
this._pendingSelectionEndIndex = -1;
this.textEditor.selectRange(beginIndex, endIndex);
}
else
{
this._pendingSelectionBeginIndex = beginIndex;
this._pendingSelectionEndIndex = endIndex;
this.invalidate(INVALIDATION_FLAG_SELECTED);
}
}
/**
* Gets the skin to be used by the text input when its
* currentState
property matches the specified state value.
*
* If a skin is not defined for a specific state, returns
* null
.
*
* @see #setSkinForState()
* @see feathers.controls.TextInputState
*/
public function getSkinForState(state:String):DisplayObject
{
return this._stateToSkin[state] as DisplayObject;
}
/**
* Sets the skin to be used by the text input when its
* currentState
property matches the specified state value.
*
* If a skin is not defined for a specific state, the value of the
* backgroundSkin
property will be used instead.
*
* @see #backgroundSkin
* @see #getSkinForState()
* @see feathers.controls.TextInputState
*/
public function setSkinForState(state:String, skin:DisplayObject):void
{
if(skin !== null)
{
this._stateToSkin[state] = skin;
}
else
{
delete this._stateToSkin[state];
}
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* Gets the icon to be used by the text input when its
* currentState
property matches the specified state value.
*
* If a icon is not defined for a specific state, returns
* null
.
*
* @see #setIconForState()
*/
public function getIconForState(state:String):DisplayObject
{
return this._stateToIcon[state] as DisplayObject;
}
/**
* Sets the icon to be used by the text input when its
* currentState
property matches the specified state value.
*
* If an icon is not defined for a specific state, the value of the
* defaultIcon
property will be used instead.
*
* @see #defaultIcon
* @see #getIconForState()
*/
public function setIconForState(state:String, icon:DisplayObject):void
{
if(icon !== null)
{
this._stateToIcon[state] = icon;
}
else
{
delete this._stateToIcon[state];
}
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
override public function dispose():void
{
//we don't dispose it if the text input is the parent because it'll
//already get disposed in super.dispose()
if(this._backgroundSkin !== null && this._backgroundSkin.parent !== this)
{
this._backgroundSkin.dispose();
}
for(var state:String in this._stateToSkin)
{
var skin:DisplayObject = this._stateToSkin[state] as DisplayObject;
if(skin !== null && skin.parent !== this)
{
skin.dispose();
}
}
if(this._defaultIcon !== null && this._defaultIcon.parent !== this)
{
this._defaultIcon.dispose();
}
for(state in this._stateToIcon)
{
var icon:DisplayObject = this._stateToIcon[state] as DisplayObject;
if(icon !== null && icon.parent !== this)
{
icon.dispose();
}
}
super.dispose();
}
/**
* @private
*/
override protected function draw():void
{
var stateInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STATE);
var stylesInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STYLES);
var dataInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_DATA);
var skinInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_SKIN);
var sizeInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_SIZE);
var textEditorInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_TEXT_EDITOR);
var promptFactoryInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_PROMPT_FACTORY);
var focusInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_FOCUS);
if(textEditorInvalid)
{
this.createTextEditor();
}
if(promptFactoryInvalid || (this._prompt !== null && !this.promptTextRenderer))
{
this.createPrompt();
}
if(textEditorInvalid || stylesInvalid)
{
this.refreshTextEditorProperties();
}
if(promptFactoryInvalid || stylesInvalid)
{
this.refreshPromptProperties();
}
if(textEditorInvalid || dataInvalid)
{
var oldIgnoreTextChanges:Boolean = this._ignoreTextChanges;
this._ignoreTextChanges = true;
this.textEditor.text = this._text;
this._ignoreTextChanges = oldIgnoreTextChanges;
}
if(this.promptTextRenderer)
{
if(promptFactoryInvalid || dataInvalid || stylesInvalid)
{
this.promptTextRenderer.visible = this._prompt && this._text.length == 0;
}
if(promptFactoryInvalid || stateInvalid)
{
this.promptTextRenderer.isEnabled = this._isEnabled;
}
}
if(textEditorInvalid || stateInvalid)
{
this.textEditor.isEnabled = this._isEnabled;
if(!this._isEnabled && Mouse.supportsNativeCursor && this._oldMouseCursor)
{
Mouse.cursor = this._oldMouseCursor;
this._oldMouseCursor = null;
}
}
if(stateInvalid || skinInvalid)
{
this.refreshBackgroundSkin();
}
if(stateInvalid || stylesInvalid)
{
this.refreshIcon();
}
sizeInvalid = this.autoSizeIfNeeded() || sizeInvalid;
this.layoutChildren();
if(sizeInvalid || focusInvalid)
{
this.refreshFocusIndicator();
}
this.doPendingActions();
}
/**
* If the component's dimensions have not been set explicitly, it will
* measure its content and determine an ideal size for itself. If the
* explicitWidth
or explicitHeight
member
* variables are set, those value will be used without additional
* measurement. If one is set, but not the other, the dimension with the
* explicit value will not be measured, but the other non-explicit
* dimension will still need measurement.
*
* Calls setSizeInternal()
to set up the
* actualWidth
and actualHeight
member
* variables used for layout.
*
* Meant for internal use, and subclasses may override this function
* with a custom implementation.
*/
protected function autoSizeIfNeeded():Boolean
{
var needsWidth:Boolean = this._explicitWidth !== this._explicitWidth; //isNaN
var needsHeight:Boolean = this._explicitHeight !== this._explicitHeight; //isNaN
var needsMinWidth:Boolean = this._explicitMinWidth !== this._explicitMinWidth; //isNaN
var needsMinHeight:Boolean = this._explicitMinHeight !== this._explicitMinHeight; //isNaN
if(!needsWidth && !needsHeight && !needsMinWidth && !needsMinHeight)
{
return false;
}
if(this.currentBackground is IValidating)
{
IValidating(this.currentBackground).validate();
}
if(this.currentIcon is IValidating)
{
IValidating(this.currentIcon).validate();
}
var measuredContentWidth:Number = 0;
var measuredContentHeight:Number = 0;
//if the typicalText is specified, the dimensions of the text editor
//can affect the final dimensions. otherwise, the background skin or
//prompt should be used for measurement.
if(this._typicalText !== null)
{
var oldTextEditorWidth:Number = this.textEditor.width;
var oldTextEditorHeight:Number = this.textEditor.height;
var oldIgnoreTextChanges:Boolean = this._ignoreTextChanges;
this._ignoreTextChanges = true;
this.textEditor.setSize(NaN, NaN);
this.textEditor.text = this._typicalText;
this.textEditor.measureText(HELPER_POINT);
this.textEditor.text = this._text;
this._ignoreTextChanges = oldIgnoreTextChanges;
measuredContentWidth = HELPER_POINT.x;
measuredContentHeight = HELPER_POINT.y;
}
if(this._prompt !== null)
{
this.promptTextRenderer.setSize(NaN, NaN);
this.promptTextRenderer.measureText(HELPER_POINT);
if(HELPER_POINT.x > measuredContentWidth)
{
measuredContentWidth = HELPER_POINT.x;
}
if(HELPER_POINT.y > measuredContentHeight)
{
measuredContentHeight = HELPER_POINT.y;
}
}
var newWidth:Number = this._explicitWidth;
if(needsWidth)
{
newWidth = measuredContentWidth;
if(this._originalIconWidth === this._originalIconWidth) //!isNaN
{
newWidth += this._originalIconWidth + this._gap;
}
newWidth += this._paddingLeft + this._paddingRight;
if(this._originalSkinWidth === this._originalSkinWidth && //!isNaN
this._originalSkinWidth > newWidth)
{
newWidth = this._originalSkinWidth;
}
}
var newHeight:Number = this._explicitHeight;
if(needsHeight)
{
newHeight = measuredContentHeight;
if(this._originalIconHeight === this._originalIconHeight && //!isNaN
this._originalIconHeight > newHeight)
{
newHeight = this._originalIconHeight;
}
newHeight += this._paddingTop + this._paddingBottom;
if(this._originalSkinHeight === this._originalSkinHeight && //!isNaN
this._originalSkinHeight > newHeight)
{
newHeight = this._originalSkinHeight;
}
}
var newMinWidth:Number = this._explicitMinWidth;
if(needsMinWidth)
{
newMinWidth = measuredContentWidth;
if(this.currentIcon is IFeathersControl)
{
newMinWidth += IFeathersControl(this.currentIcon).minWidth + this._gap;
}
else if(this._originalIconWidth === this._originalIconWidth)
{
newMinWidth += this._originalIconWidth + this._gap;
}
newMinWidth += this._paddingLeft + this._paddingRight;
if(this.currentBackground is IFeathersControl)
{
var backgroundMinWidth:Number = IFeathersControl(this.currentBackground).minWidth;
if(backgroundMinWidth > newMinWidth)
{
newMinWidth = backgroundMinWidth;
}
}
else if(this._originalSkinWidth === this._originalSkinWidth && //!isNaN
this._originalSkinWidth > newMinWidth)
{
newMinWidth = this._originalSkinWidth;
}
}
var newMinHeight:Number = this._explicitMinHeight;
if(needsMinHeight)
{
newMinHeight = measuredContentHeight;
if(this.currentIcon is IFeathersControl)
{
var iconMinHeight:Number = IFeathersControl(this.currentIcon).minHeight;
if(iconMinHeight > newMinHeight)
{
newMinHeight = iconMinHeight;
}
}
else if(this._originalIconHeight === this._originalIconHeight && //!isNaN
this._originalIconHeight > newMinHeight)
{
newMinHeight = this._originalIconHeight;
}
newMinHeight += this._paddingTop + this._paddingBottom;
if(this.currentBackground is IFeathersControl)
{
var backgroundMinHeight:Number = IFeathersControl(this.currentBackground).minHeight;
if(backgroundMinHeight > newMinHeight)
{
newMinHeight = backgroundMinHeight;
}
}
else if(this._originalSkinHeight === this._originalSkinHeight && //!isNaN
this._originalSkinHeight > newMinHeight)
{
newMinHeight = this._originalSkinHeight;
}
}
var isMultiline:Boolean = this.textEditor is IMultilineTextEditor && IMultilineTextEditor(this.textEditor).multiline;
if(this._typicalText !== null && (this._verticalAlign == VerticalAlign.JUSTIFY || isMultiline))
{
this.textEditor.width = oldTextEditorWidth;
this.textEditor.height = oldTextEditorHeight;
}
return this.saveMeasurements(newWidth, newHeight, newMinWidth, newMinHeight);
}
/**
* Creates and adds the textEditor
sub-component and
* removes the old instance, if one exists.
*
* Meant for internal use, and subclasses may override this function
* with a custom implementation.
*
* @see #textEditor
* @see #textEditorFactory
*/
protected function createTextEditor():void
{
if(this.textEditor)
{
this.removeChild(DisplayObject(this.textEditor), true);
this.textEditor.removeEventListener(Event.CHANGE, textEditor_changeHandler);
this.textEditor.removeEventListener(FeathersEventType.ENTER, textEditor_enterHandler);
this.textEditor.removeEventListener(FeathersEventType.FOCUS_IN, textEditor_focusInHandler);
this.textEditor.removeEventListener(FeathersEventType.FOCUS_OUT, textEditor_focusOutHandler);
this.textEditor = null;
}
var factory:Function = this._textEditorFactory != null ? this._textEditorFactory : FeathersControl.defaultTextEditorFactory;
this.textEditor = ITextEditor(factory());
var textEditorStyleName:String = this._customTextEditorStyleName != null ? this._customTextEditorStyleName : this.textEditorStyleName;
this.textEditor.styleNameList.add(textEditorStyleName);
if(this.textEditor is IStateObserver)
{
IStateObserver(this.textEditor).stateContext = this;
}
this.textEditor.addEventListener(Event.CHANGE, textEditor_changeHandler);
this.textEditor.addEventListener(FeathersEventType.ENTER, textEditor_enterHandler);
this.textEditor.addEventListener(FeathersEventType.FOCUS_IN, textEditor_focusInHandler);
this.textEditor.addEventListener(FeathersEventType.FOCUS_OUT, textEditor_focusOutHandler);
this.addChild(DisplayObject(this.textEditor));
}
/**
* @private
*/
protected function createPrompt():void
{
if(this.promptTextRenderer)
{
this.removeChild(DisplayObject(this.promptTextRenderer), true);
this.promptTextRenderer = null;
}
if(this._prompt === null)
{
return;
}
var factory:Function = this._promptFactory != null ? this._promptFactory : FeathersControl.defaultTextRendererFactory;
this.promptTextRenderer = ITextRenderer(factory());
var promptStyleName:String = this._customPromptStyleName != null ? this._customPromptStyleName : this.promptStyleName;
this.promptTextRenderer.styleNameList.add(promptStyleName);
this.addChild(DisplayObject(this.promptTextRenderer));
}
/**
* @private
*/
protected function createErrorCallout():void
{
if(this.callout)
{
this.callout.removeFromParent(true);
this.callout = null;
}
if(this._errorString === null)
{
return;
}
this.callout = new TextCallout();
var errorCalloutStyleName:String = this._customErrorCalloutStyleName != null ? this._customErrorCalloutStyleName : this.errorCalloutStyleName;
this.callout.styleNameList.add(errorCalloutStyleName);
this.callout.closeOnKeys = null;
this.callout.closeOnTouchBeganOutside = false;
this.callout.closeOnTouchEndedOutside = false;
this.callout.touchable = false;
this.callout.text = this._errorString;
this.callout.origin = this;
PopUpManager.addPopUp(this.callout, false, false);
}
/**
* @private
*/
protected function changeState(state:String):void
{
if(this._currentState === state)
{
return;
}
this._currentState = state;
this.invalidate(INVALIDATION_FLAG_STATE);
this.dispatchEventWith(FeathersEventType.STATE_CHANGE);
}
/**
* @private
*/
protected function doPendingActions():void
{
if(this._isWaitingToSetFocus)
{
this._isWaitingToSetFocus = false;
if(!this._textEditorHasFocus)
{
this.textEditor.setFocus();
}
}
if(this._pendingSelectionBeginIndex >= 0)
{
var startIndex:int = this._pendingSelectionBeginIndex;
var endIndex:int = this._pendingSelectionEndIndex;
this._pendingSelectionBeginIndex = -1;
this._pendingSelectionEndIndex = -1;
if(endIndex >= 0)
{
var textLength:int = this._text.length;
if(endIndex > textLength)
{
endIndex = textLength;
}
}
this.selectRange(startIndex, endIndex);
}
}
/**
* @private
*/
protected function refreshTextEditorProperties():void
{
this.textEditor.displayAsPassword = this._displayAsPassword;
this.textEditor.maxChars = this._maxChars;
this.textEditor.restrict = this._restrict;
this.textEditor.isEditable = this._isEditable;
this.textEditor.isSelectable = this._isSelectable;
for(var propertyName:String in this._textEditorProperties)
{
var propertyValue:Object = this._textEditorProperties[propertyName];
this.textEditor[propertyName] = propertyValue;
}
}
/**
* @private
*/
protected function refreshPromptProperties():void
{
if(!this.promptTextRenderer)
{
return;
}
this.promptTextRenderer.text = this._prompt;
var displayPrompt:DisplayObject = DisplayObject(this.promptTextRenderer);
for(var propertyName:String in this._promptProperties)
{
var propertyValue:Object = this._promptProperties[propertyName];
this.promptTextRenderer[propertyName] = propertyValue;
}
}
/**
* Sets the currentBackground
property.
*
* For internal use in subclasses.
*/
protected function refreshBackgroundSkin():void
{
var oldSkin:DisplayObject = this.currentBackground;
this.currentBackground = this.getCurrentSkin();
if(this.currentBackground !== oldSkin)
{
if(oldSkin)
{
if(oldSkin is IStateObserver)
{
IStateObserver(oldSkin).stateContext = null;
}
this.removeChild(oldSkin, false);
}
if(this.currentBackground)
{
if(this.currentBackground is IStateObserver)
{
IStateObserver(this.currentBackground).stateContext = this;
}
this.addChildAt(this.currentBackground, 0);
}
}
if(this.currentBackground &&
(this._originalSkinWidth !== this._originalSkinWidth || //isNaN
this._originalSkinHeight !== this._originalSkinHeight)) //isNaN
{
if(this.currentBackground is IValidating)
{
IValidating(this.currentBackground).validate();
}
this._originalSkinWidth = this.currentBackground.width;
this._originalSkinHeight = this.currentBackground.height;
}
}
/**
* Sets the currentIcon
property.
*
* For internal use in subclasses.
*/
protected function refreshIcon():void
{
var oldIcon:DisplayObject = this.currentIcon;
this.currentIcon = this.getCurrentIcon();
if(this.currentIcon is IFeathersControl)
{
IFeathersControl(this.currentIcon).isEnabled = this._isEnabled;
}
if(this.currentIcon !== oldIcon)
{
if(oldIcon)
{
if(oldIcon is IStateObserver)
{
IStateObserver(oldIcon).stateContext = null;
}
this.removeChild(oldIcon, false);
}
if(this.currentIcon)
{
if(this.currentIcon is IStateObserver)
{
IStateObserver(this.currentIcon).stateContext = this;
}
//we want the icon to appear below the text editor
var index:int = this.getChildIndex(DisplayObject(this.textEditor));
this.addChildAt(this.currentIcon, index);
}
}
if(this.currentIcon &&
(this._originalIconWidth !== this._originalIconWidth || //isNaN
this._originalIconHeight !== this._originalIconHeight)) //isNaN
{
if(this.currentIcon is IValidating)
{
IValidating(this.currentIcon).validate();
}
this._originalIconWidth = this.currentIcon.width;
this._originalIconHeight = this.currentIcon.height;
}
}
/**
* @private
*/
protected function getCurrentSkin():DisplayObject
{
if(this._stateToSkinFunction !== null)
{
return DisplayObject(this._stateToSkinFunction(this, this._currentState, this.currentBackground));
}
var result:DisplayObject = this._stateToSkin[this._currentState] as DisplayObject;
if(result !== null)
{
return result;
}
return this._backgroundSkin;
}
/**
* @private
*/
protected function getCurrentIcon():DisplayObject
{
if(this._stateToIconFunction !== null)
{
return DisplayObject(this._stateToIconFunction(this, this._currentState, this.currentIcon));
}
var result:DisplayObject = this._stateToIcon[this._currentState] as DisplayObject;
if(result !== null)
{
return result;
}
return this._defaultIcon;
}
/**
* Positions and sizes the text input's children.
*
* For internal use in subclasses.
*/
protected function layoutChildren():void
{
if(this.currentBackground)
{
this.currentBackground.visible = true;
this.currentBackground.touchable = true;
this.currentBackground.width = this.actualWidth;
this.currentBackground.height = this.actualHeight;
}
if(this.currentIcon is IValidating)
{
IValidating(this.currentIcon).validate();
}
if(this.currentIcon)
{
this.currentIcon.x = this._paddingLeft;
this.textEditor.x = this.currentIcon.x + this.currentIcon.width + this._gap;
if(this.promptTextRenderer)
{
this.promptTextRenderer.x = this.currentIcon.x + this.currentIcon.width + this._gap;
}
}
else
{
this.textEditor.x = this._paddingLeft;
if(this.promptTextRenderer)
{
this.promptTextRenderer.x = this._paddingLeft;
}
}
this.textEditor.width = this.actualWidth - this._paddingRight - this.textEditor.x;
if(this.promptTextRenderer)
{
this.promptTextRenderer.width = this.actualWidth - this._paddingRight - this.promptTextRenderer.x;
}
var isMultiline:Boolean = this.textEditor is IMultilineTextEditor && IMultilineTextEditor(this.textEditor).multiline;
if(isMultiline || this._verticalAlign == VerticalAlign.JUSTIFY)
{
//multiline is treated the same as justify
this.textEditor.height = this.actualHeight - this._paddingTop - this._paddingBottom;
}
else
{
//clear the height and auto-size instead
this.textEditor.height = NaN;
}
this.textEditor.validate();
if(this.promptTextRenderer)
{
this.promptTextRenderer.validate();
}
var biggerHeight:Number = this.textEditor.height;
var biggerBaseline:Number = this.textEditor.baseline;
if(this.promptTextRenderer)
{
var promptBaseline:Number = this.promptTextRenderer.baseline;
var promptHeight:Number = this.promptTextRenderer.height;
if(promptBaseline > biggerBaseline)
{
biggerBaseline = promptBaseline;
}
if(promptHeight > biggerHeight)
{
biggerHeight = promptHeight;
}
}
if(isMultiline)
{
this.textEditor.y = this._paddingTop + biggerBaseline - this.textEditor.baseline;
if(this.promptTextRenderer)
{
this.promptTextRenderer.y = this._paddingTop + biggerBaseline - promptBaseline;
this.promptTextRenderer.height = this.actualHeight - this.promptTextRenderer.y - this._paddingBottom;
}
if(this.currentIcon)
{
this.currentIcon.y = this._paddingTop;
}
}
else
{
switch(this._verticalAlign)
{
case VerticalAlign.JUSTIFY:
{
this.textEditor.y = this._paddingTop + biggerBaseline - this.textEditor.baseline;
if(this.promptTextRenderer)
{
this.promptTextRenderer.y = this._paddingTop + biggerBaseline - promptBaseline;
this.promptTextRenderer.height = this.actualHeight - this.promptTextRenderer.y - this._paddingBottom;
}
if(this.currentIcon)
{
this.currentIcon.y = this._paddingTop;
}
break;
}
case VerticalAlign.TOP:
{
this.textEditor.y = this._paddingTop + biggerBaseline - this.textEditor.baseline;
if(this.promptTextRenderer)
{
this.promptTextRenderer.y = this._paddingTop + biggerBaseline - promptBaseline;
}
if(this.currentIcon)
{
this.currentIcon.y = this._paddingTop;
}
break;
}
case VerticalAlign.BOTTOM:
{
this.textEditor.y = this.actualHeight - this._paddingBottom - biggerHeight + biggerBaseline - this.textEditor.baseline;
if(this.promptTextRenderer)
{
this.promptTextRenderer.y = this.actualHeight - this._paddingBottom - biggerHeight + biggerBaseline - promptBaseline;
}
if(this.currentIcon)
{
this.currentIcon.y = this.actualHeight - this._paddingBottom - this.currentIcon.height;
}
break;
}
default: //middle
{
this.textEditor.y = biggerBaseline - this.textEditor.baseline + this._paddingTop + Math.round((this.actualHeight - this._paddingTop - this._paddingBottom - biggerHeight) / 2);
if(this.promptTextRenderer)
{
this.promptTextRenderer.y = biggerBaseline - promptBaseline + this._paddingTop + Math.round((this.actualHeight - this._paddingTop - this._paddingBottom - biggerHeight) / 2);
}
if(this.currentIcon)
{
this.currentIcon.y = this._paddingTop + Math.round((this.actualHeight - this._paddingTop - this._paddingBottom - this.currentIcon.height) / 2);
}
}
}
}
}
/**
* @private
*/
protected function setFocusOnTextEditorWithTouch(touch:Touch):void
{
if(!this.isFocusEnabled)
{
return;
}
touch.getLocation(this.stage, HELPER_POINT);
var isInBounds:Boolean = this.contains(this.stage.hitTest(HELPER_POINT));
if(isInBounds && !this._textEditorHasFocus)
{
this.textEditor.globalToLocal(HELPER_POINT, HELPER_POINT);
this._isWaitingToSetFocus = false;
this.textEditor.setFocus(HELPER_POINT);
}
}
/**
* @private
*/
protected function refreshState():void
{
if(this._isEnabled)
{
if(this._textEditorHasFocus)
{
this.changeState(TextInputState.FOCUSED);
}
else if(this._errorString !== null)
{
this.changeState(TextInputState.ERROR);
}
else
{
this.changeState(TextInputState.ENABLED);
}
}
else
{
this.changeState(TextInputState.DISABLED);
}
}
/**
* @private
*/
protected function childProperties_onChange(proxy:PropertyProxy, name:Object):void
{
this.invalidate(INVALIDATION_FLAG_STYLES);
}
/**
* @private
*/
protected function textInput_removedFromStageHandler(event:Event):void
{
if(!this._focusManager && this._textEditorHasFocus)
{
this.clearFocus();
}
this._textEditorHasFocus = false;
this._isWaitingToSetFocus = false;
this._touchPointID = -1;
if(Mouse.supportsNativeCursor && this._oldMouseCursor)
{
Mouse.cursor = this._oldMouseCursor;
this._oldMouseCursor = null;
}
}
/**
* @private
*/
protected function textInput_touchHandler(event:TouchEvent):void
{
if(!this._isEnabled)
{
this._touchPointID = -1;
return;
}
if(this._touchPointID >= 0)
{
var touch:Touch = event.getTouch(this, TouchPhase.ENDED, this._touchPointID);
if(!touch)
{
return;
}
this._touchPointID = -1;
if(this.textEditor.setTouchFocusOnEndedPhase)
{
this.setFocusOnTextEditorWithTouch(touch);
}
}
else
{
touch = event.getTouch(this, TouchPhase.BEGAN);
if(touch)
{
this._touchPointID = touch.id;
if(!this.textEditor.setTouchFocusOnEndedPhase)
{
this.setFocusOnTextEditorWithTouch(touch);
}
return;
}
touch = event.getTouch(this, TouchPhase.HOVER);
if(touch)
{
if(Mouse.supportsNativeCursor && !this._oldMouseCursor)
{
this._oldMouseCursor = Mouse.cursor;
Mouse.cursor = MouseCursor.IBEAM;
}
return;
}
//end hover
if(Mouse.supportsNativeCursor && this._oldMouseCursor)
{
Mouse.cursor = this._oldMouseCursor;
this._oldMouseCursor = null;
}
}
}
/**
* @private
*/
override protected function focusInHandler(event:Event):void
{
if(!this._focusManager)
{
return;
}
super.focusInHandler(event);
this.setFocus();
}
/**
* @private
*/
override protected function focusOutHandler(event:Event):void
{
if(!this._focusManager)
{
return;
}
super.focusOutHandler(event);
this.textEditor.clearFocus();
}
/**
* @private
*/
protected function textEditor_changeHandler(event:Event):void
{
if(this._ignoreTextChanges)
{
return;
}
this.text = this.textEditor.text;
}
/**
* @private
*/
protected function textEditor_enterHandler(event:Event):void
{
this.dispatchEventWith(FeathersEventType.ENTER);
}
/**
* @private
*/
protected function textEditor_focusInHandler(event:Event):void
{
if(!this.visible)
{
this.textEditor.clearFocus();
return;
}
this._textEditorHasFocus = true;
this.refreshState();
if(this._errorString !== null && this._errorString.length > 0)
{
this.createErrorCallout();
}
if(this._focusManager && this.isFocusEnabled && this._focusManager.focus !== this)
{
//if setFocus() was called manually, we need to notify the focus
//manager (unless isFocusEnabled is false).
//if the focus manager already knows that we have focus, it will
//simply return without doing anything.
this._focusManager.focus = this;
}
else if(!this._focusManager)
{
this.dispatchEventWith(FeathersEventType.FOCUS_IN);
}
}
/**
* @private
*/
protected function textEditor_focusOutHandler(event:Event):void
{
this._textEditorHasFocus = false;
this.refreshState();
if(this.callout)
{
this.callout.removeFromParent(true);
this.callout = null;
}
if(this._focusManager && this._focusManager.focus === this)
{
//if clearFocus() was called manually, we need to notify the
//focus manager if it still thinks we have focus.
this._focusManager.focus = null;
}
else if(!this._focusManager)
{
this.dispatchEventWith(FeathersEventType.FOCUS_OUT);
}
}
}
}