All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scaffold.libs_as.feathers.controls.text.TextFieldTextEditor.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.text
{
	import feathers.core.FeathersControl;
	import feathers.core.FocusManager;
	import feathers.core.INativeFocusOwner;
	import feathers.core.IStateContext;
	import feathers.core.IStateObserver;
	import feathers.core.ITextEditor;
	import feathers.events.FeathersEventType;
	import feathers.skins.IStyleProvider;
	import feathers.utils.geom.matrixToRotation;
	import feathers.utils.geom.matrixToScaleX;
	import feathers.utils.geom.matrixToScaleY;

	import flash.display.BitmapData;
	import flash.display.InteractiveObject;
	import flash.display.Stage;
	import flash.display3D.Context3DProfile;
	import flash.events.FocusEvent;
	import flash.events.KeyboardEvent;
	import flash.events.SoftKeyboardEvent;
	import flash.geom.Matrix;
	import flash.geom.Matrix3D;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.geom.Vector3D;
	import flash.text.AntiAliasType;
	import flash.text.GridFitType;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFieldType;
	import flash.text.TextFormat;
	import flash.ui.Keyboard;

	import starling.core.Starling;
	import starling.display.DisplayObject;
	import starling.display.Image;
	import starling.events.Event;
	import starling.events.Touch;
	import starling.events.TouchEvent;
	import starling.events.TouchPhase;
	import starling.rendering.Painter;
	import starling.textures.ConcreteTexture;
	import starling.textures.Texture;
	import starling.utils.MathUtil;
	import starling.utils.MatrixUtil;

	/**
	 * Dispatched when the text property changes.
	 *
	 * 

The properties of the event object have the following values:

* * * * * * *
PropertyValue
bubblesfalse
currentTargetThe 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.
datanull
targetThe 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.
*/ [Event(name="change",type="starling.events.Event")] /** * Dispatched when the user presses the Enter key while the editor has focus. * *

The properties of the event object have the following values:

* * * * * * *
PropertyValue
bubblesfalse
currentTargetThe 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.
datanull
targetThe 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 editor receives focus. * *

The properties of the event object have the following values:

* * * * * * *
PropertyValue
bubblesfalse
currentTargetThe 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.
datanull
targetThe 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 editor loses focus. * *

The properties of the event object have the following values:

* * * * * * *
PropertyValue
bubblesfalse
currentTargetThe 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.
datanull
targetThe 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. Not all text editors will * activate a soft keyboard. * *

The properties of the event object have the following values:

* * * * * * *
PropertyValue
bubblesfalse
currentTargetThe 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.
datanull
targetThe 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. Not all text editors * will activate a soft keyboard. * *

The properties of the event object have the following values:

* * * * * * *
PropertyValue
bubblesfalse
currentTargetThe 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.
datanull
targetThe 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")] /** * Text that may be edited at runtime by the user with the * TextInput component, using the native * flash.text.TextField class with its type * property set to flash.text.TextInputType.INPUT. When not in * focus, the TextField is drawn to BitmapData and * uploaded to a texture on the GPU. Textures are managed internally by this * component, and they will be automatically disposed when the component is * disposed. * *

For desktop apps, TextFieldTextEditor is recommended * instead of StageTextTextEditor. StageTextTextEditor * will still work in desktop apps, but it is more appropriate for mobile * apps.

* *

The following example shows how to use * TextFieldTextEditor with a TextInput:

* * * var input:TextInput = new TextInput(); * input.textEditorFactory = function():ITextEditor * { * return new TextFieldTextEditor(); * }; * this.addChild( input ); * * @see feathers.controls.TextInput * @see ../../../../help/text-editors.html Introduction to Feathers text editors * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html flash.text.TextField */ public class TextFieldTextEditor extends FeathersControl implements ITextEditor, INativeFocusOwner, IStateObserver { /** * @private */ private static var HELPER_MATRIX3D:Matrix3D; /** * @private */ private static var HELPER_POINT3D:Vector3D; /** * @private */ private static const HELPER_MATRIX:Matrix = new Matrix(); /** * @private */ private static const HELPER_POINT:Point = new Point(); /** * The default IStyleProvider for all TextFieldTextEditor * components. * * @default null * @see feathers.core.FeathersControl#styleProvider */ public static var globalStyleProvider:IStyleProvider; /** * Constructor. */ public function TextFieldTextEditor() { this.isQuickHitAreaEnabled = true; this.addEventListener(Event.ADDED_TO_STAGE, textEditor_addedToStageHandler); this.addEventListener(Event.REMOVED_FROM_STAGE, textEditor_removedFromStageHandler); } /** * @private */ override protected function get defaultStyleProvider():IStyleProvider { return globalStyleProvider; } /** * The text field sub-component. */ protected var textField:TextField; /** * @copy feathers.core.INativeFocusOwner#nativeFocus */ public function get nativeFocus():InteractiveObject { return this.textField; } /** * An image that displays a snapshot of the native TextField * in the Starling display list when the editor doesn't have focus. */ protected var textSnapshot:Image; /** * The separate text field sub-component used for measurement. * Typically, the main text field often doesn't report correct values * for a full frame if its dimensions are changed too often. */ protected var measureTextField:TextField; /** * @private */ protected var _snapshotWidth:int = 0; /** * @private */ protected var _snapshotHeight:int = 0; /** * @private */ protected var _textFieldSnapshotClipRect:Rectangle = new Rectangle(); /** * @private */ protected var _textFieldOffsetX:Number = 0; /** * @private */ protected var _textFieldOffsetY:Number = 0; /** * @private */ protected var _lastGlobalScaleX:Number = 0; /** * @private */ protected var _lastGlobalScaleY:Number = 0; /** * @private */ protected var _needsNewTexture:Boolean = false; /** * @private */ protected var _text:String = ""; /** * @inheritDoc * *

In the following example, the text is changed:

* * * textEditor.text = "Lorem ipsum"; * * @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); } /** * @inheritDoc */ public function get baseline():Number { if(!this.textField) { return 0; } var gutterDimensionsOffset:Number = 0; if(this._useGutter) { gutterDimensionsOffset = 2; } return gutterDimensionsOffset + this.textField.getLineMetrics(0).ascent; } /** * @private */ protected var _previousTextFormat:TextFormat; /** * @private */ protected var currentTextFormat:TextFormat; /** * @private */ protected var _textFormatForState:Object; /** * @private */ protected var _textFormat:TextFormat; /** * The format of the text, such as font and styles. * *

In the following example, the text format is changed:

* * * textEditor.textFormat = new TextFormat( "Source Sans Pro" );; * * @default null * * @see #setTextFormatForState() * @see #disabledTextFormat * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextFormat.html flash.text.TextFormat */ public function get textFormat():TextFormat { return this._textFormat; } /** * @private */ public function set textFormat(value:TextFormat):void { if(this._textFormat == value) { return; } this._textFormat = value; //since the text format has changed, the comparison will return //false whether we use the real previous format or null. might as //well remove the reference to an object we don't need anymore. this._previousTextFormat = null; this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ protected var _disabledTextFormat:TextFormat; /** * The font and styles used to draw the text when the component is * disabled. * *

In the following example, the disabled text format is changed:

* * * textEditor.isEnabled = false; * textEditor.disabledTextFormat = new TextFormat( "Source Sans Pro" ); * * @default null * * @see #textFormat * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextFormat.html flash.text.TextFormat */ public function get disabledTextFormat():TextFormat { return this._disabledTextFormat; } /** * @private */ public function set disabledTextFormat(value:TextFormat):void { if(this._disabledTextFormat == value) { return; } this._disabledTextFormat = value; this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ protected var _embedFonts:Boolean = false; /** * Determines if the TextField should use an embedded font or not. If * the specified font is not embedded, the text is not displayed. * *

In the following example, the font is embedded:

* * * textEditor.embedFonts = true; * * @default false * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#embedFonts Full description of flash.text.TextField.embedFonts in Adobe's Flash Platform API Reference */ public function get embedFonts():Boolean { return this._embedFonts; } /** * @private */ public function set embedFonts(value:Boolean):void { if(this._embedFonts == value) { return; } this._embedFonts = value; this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ protected var _wordWrap:Boolean = false; /** * Determines if the TextField wraps text to the next line. * *

In the following example, word wrap is enabled:

* * * textEditor.wordWrap = true; * * @default false * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#wordWrap Full description of flash.text.TextField.wordWrap in Adobe's Flash Platform API Reference */ public function get wordWrap():Boolean { return this._wordWrap; } /** * @private */ public function set wordWrap(value:Boolean):void { if(this._wordWrap == value) { return; } this._wordWrap = value; this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ protected var _multiline:Boolean = false; /** * Indicates whether field is a multiline text field. * *

In the following example, multiline is enabled:

* * * textEditor.multiline = true; * * @default false * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#multiline Full description of flash.text.TextField.multiline in Adobe's Flash Platform API Reference */ public function get multiline():Boolean { return this._multiline; } /** * @private */ public function set multiline(value:Boolean):void { if(this._multiline == value) { return; } this._multiline = value; this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ protected var _isHTML:Boolean = false; /** * Determines if the TextField should display the value of the * text property as HTML or not. * *

In the following example, the text is displayed as HTML:

* * * textEditor.isHTML = true; * * @default false * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#htmlText flash.text.TextField.htmlText */ public function get isHTML():Boolean { return this._isHTML; } /** * @private */ public function set isHTML(value:Boolean):void { if(this._isHTML == value) { return; } this._isHTML = value; this.invalidate(INVALIDATION_FLAG_DATA); } /** * @private */ protected var _alwaysShowSelection:Boolean = false; /** * When set to true and the text field is not in focus, * Flash Player highlights the selection in the text field in gray. When * set to false and the text field is not in focus, Flash * Player does not highlight the selection in the text field. * *

In the following example, the selection is always shown:

* * * textEditor.alwaysShowSelection = true; * * @default false * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#alwaysShowSelection Full description of flash.text.TextField.alwaysShowSelection in Adobe's Flash Platform API Reference */ public function get alwaysShowSelection():Boolean { return this._alwaysShowSelection; } /** * @private */ public function set alwaysShowSelection(value:Boolean):void { if(this._alwaysShowSelection == value) { return; } this._alwaysShowSelection = value; this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ protected var _displayAsPassword:Boolean = false; /** *

This property is managed by the TextInput.

* * Specifies whether the text field is a password text field that hides * the input characters using asterisks instead of the actual * characters. * * @see feathers.controls.TextInput#displayAsPassword * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#displayAsPassword Full description of flash.text.TextField.displayAsPassword in Adobe's Flash Platform API Reference */ 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 _maxChars:int = 0; /** *

This property is managed by the TextInput.

* * @copy feathers.controls.TextInput#maxChars * * @see feathers.controls.TextInput#maxChars * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#maxChars Full description of flash.text.TextField.maxChars in Adobe's Flash Platform API Reference */ 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; /** *

This property is managed by the TextInput.

* * @copy feathers.controls.TextInput#restrict * * @see feathers.controls.TextInput#restrict * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#restrict Full description of flash.text.TextField.restrict in Adobe's Flash Platform API Reference */ 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 _isEditable:Boolean = true; /** *

This property is managed by the TextInput.

* * @copy feathers.controls.TextInput#isEditable * * @see feathers.controls.TextInput#isEditable */ 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; /** *

This property is managed by the TextInput.

* * @copy feathers.controls.TextInput#isSelectable * * @see feathers.controls.TextInput#isSelectable */ 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 */ private var _antiAliasType:String = AntiAliasType.ADVANCED; /** * The type of anti-aliasing used for this text field, defined as * constants in the flash.text.AntiAliasType class. You can * control this setting only if the font is embedded (with the * embedFonts property set to true). * *

In the following example, the anti-alias type is changed:

* * * textRenderer.antiAliasType = AntiAliasType.NORMAL; * * @default flash.text.AntiAliasType.ADVANCED * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#antiAliasType Full description of flash.text.TextField.antiAliasType in Adobe's Flash Platform API Reference * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/AntiAliasType.html flash.text.AntiAliasType * @see #embedFonts */ public function get antiAliasType():String { return this._antiAliasType; } /** * @private */ public function set antiAliasType(value:String):void { if(this._antiAliasType == value) { return; } this._antiAliasType = value; this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ private var _gridFitType:String = GridFitType.PIXEL; /** * Determines whether Flash Player forces strong horizontal and vertical * lines to fit to a pixel or subpixel grid, or not at all using the * constants defined in the flash.text.GridFitType class. * This property applies only if the antiAliasType property * of the text field is set to flash.text.AntiAliasType.ADVANCED. * *

In the following example, the grid fit type is changed:

* * * textRenderer.gridFitType = GridFitType.SUBPIXEL; * * @default flash.text.GridFitType.PIXEL * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#gridFitType Full description of flash.text.TextField.gridFitType in Adobe's Flash Platform API Reference * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/GridFitType.html flash.text.GridFitType * @see #antiAliasType */ public function get gridFitType():String { return this._gridFitType; } /** * @private */ public function set gridFitType(value:String):void { if(this._gridFitType == value) { return; } this._gridFitType = value; this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ private var _sharpness:Number = 0; /** * The sharpness of the glyph edges in this text field. This property * applies only if the antiAliasType property of the text * field is set to flash.text.AntiAliasType.ADVANCED. The * range for sharpness is a number from -400 * to 400. * *

In the following example, the sharpness is changed:

* * * textRenderer.sharpness = 200; * * @default 0 * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#sharpness Full description of flash.text.TextField.sharpness in Adobe's Flash Platform API Reference * @see #antiAliasType */ public function get sharpness():Number { return this._sharpness; } /** * @private */ public function set sharpness(value:Number):void { if(this._sharpness == value) { return; } this._sharpness = value; this.invalidate(INVALIDATION_FLAG_DATA); } /** * @private */ private var _thickness:Number = 0; /** * The thickness of the glyph edges in this text field. This property * applies only if the antiAliasType property is set to * flash.text.AntiAliasType.ADVANCED. The range for * thickness is a number from -200 to * 200. * *

In the following example, the thickness is changed:

* * * textRenderer.thickness = 100; * * @default 0 * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#thickness Full description of flash.text.TextField.thickness in Adobe's Flash Platform API Reference * @see #antiAliasType */ public function get thickness():Number { return this._thickness; } /** * @private */ public function set thickness(value:Number):void { if(this._thickness == value) { return; } this._thickness = value; this.invalidate(INVALIDATION_FLAG_DATA); } /** * @private */ private var _background:Boolean = false; /** * Specifies whether the text field has a background fill. Use the * backgroundColor property to set the background color of * a text field. * *

In the following example, the background is enabled:

* * * textRenderer.background = true; * textRenderer.backgroundColor = 0xff0000; * * @default false * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#background Full description of flash.text.TextField.background in Adobe's Flash Platform API Reference * @see #backgroundColor */ public function get background():Boolean { return this._background; } /** * @private */ public function set background(value:Boolean):void { if(this._background == value) { return; } this._background = value; this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ private var _backgroundColor:uint = 0xffffff; /** * The color of the text field background that is displayed if the * background property is set to true. * *

In the following example, the background color is changed:

* * * textRenderer.background = true; * textRenderer.backgroundColor = 0xff000ff; * * @default 0xffffff * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#backgroundColor Full description of flash.text.TextField.backgroundColor in Adobe's Flash Platform API Reference * @see #background */ public function get backgroundColor():uint { return this._backgroundColor; } /** * @private */ public function set backgroundColor(value:uint):void { if(this._backgroundColor == value) { return; } this._backgroundColor = value; this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ private var _border:Boolean = false; /** * Specifies whether the text field has a border. Use the * borderColor property to set the border color. * *

Note: this property cannot be used when the useGutter * property is set to false (the default value!).

* *

In the following example, the border is enabled:

* * * textRenderer.border = true; * textRenderer.borderColor = 0xff0000; * * @default false * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#border Full description of flash.text.TextField.border in Adobe's Flash Platform API Reference * @see #borderColor */ public function get border():Boolean { return this._border; } /** * @private */ public function set border(value:Boolean):void { if(this._border == value) { return; } this._border = value; this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ private var _borderColor:uint = 0x000000; /** * The color of the text field border that is displayed if the * border property is set to true. * *

In the following example, the border color is changed:

* * * textRenderer.border = true; * textRenderer.borderColor = 0xff00ff; * * @default 0x000000 * * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html#borderColor Full description of flash.text.TextField.borderColor in Adobe's Flash Platform API Reference * @see #border */ public function get borderColor():uint { return this._borderColor; } /** * @private */ public function set borderColor(value:uint):void { if(this._borderColor == value) { return; } this._borderColor = value; this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @private */ protected var _useGutter:Boolean = false; /** * Determines if the 2-pixel gutter around the edges of the * flash.text.TextField will be used in measurement and * layout. To visually align with other text renderers and text editors, * it is often best to leave the gutter disabled. * *

In the following example, the gutter is enabled:

* * * textEditor.useGutter = true; * * @default false */ public function get useGutter():Boolean { return this._useGutter; } /** * @private */ public function set useGutter(value:Boolean):void { if(this._useGutter == value) { return; } this._useGutter = value; this.invalidate(INVALIDATION_FLAG_STYLES); } /** * @inheritDoc */ public function get setTouchFocusOnEndedPhase():Boolean { return false; } /** * @private */ protected var _textFieldHasFocus:Boolean = false; /** * @private */ protected var _isWaitingToSetFocus:Boolean = false; /** * @private */ protected var _pendingSelectionBeginIndex:int = -1; /** * @inheritDoc */ public function get selectionBeginIndex():int { if(this._pendingSelectionBeginIndex >= 0) { return this._pendingSelectionBeginIndex; } if(this.textField) { return this.textField.selectionBeginIndex; } return 0; } /** * @private */ protected var _pendingSelectionEndIndex:int = -1; /** * @inheritDoc */ public function get selectionEndIndex():int { if(this._pendingSelectionEndIndex >= 0) { return this._pendingSelectionEndIndex; } if(this.textField) { return this.textField.selectionEndIndex; } return 0; } /** * @private */ protected var _stateContext:IStateContext; /** * When the text renderer observes a state context, the text renderer * may change its TextFormat based on the current state of * that context. Typically, a relevant component will automatically * assign itself as the state context of a text renderer, so this * property is typically meant for internal use only. * * @default null * * @see #setTextFormatForState() */ public function get stateContext():IStateContext { return this._stateContext; } /** * @private */ public function set stateContext(value:IStateContext):void { if(this._stateContext === value) { return; } if(this._stateContext) { this._stateContext.removeEventListener(FeathersEventType.STATE_CHANGE, stateContext_stateChangeHandler); } this._stateContext = value; if(this._stateContext) { this._stateContext.addEventListener(FeathersEventType.STATE_CHANGE, stateContext_stateChangeHandler); } this.invalidate(INVALIDATION_FLAG_STATE); } /** * @private */ protected var _updateSnapshotOnScaleChange:Boolean = false; /** * Refreshes the texture snapshot every time that the text editor is * scaled. Based on the scale in global coordinates, so scaling the * parent will require a new snapshot. * *

Warning: setting this property to true may result in reduced * performance because every change of the scale requires uploading a * new texture to the GPU. Use with caution. Consider setting this * property to false temporarily during animations that modify the * scale.

* *

In the following example, the snapshot will be updated when the * text editor is scaled:

* * * textEditor.updateSnapshotOnScaleChange = true; * * @default false */ public function get updateSnapshotOnScaleChange():Boolean { return this._updateSnapshotOnScaleChange; } /** * @private */ public function set updateSnapshotOnScaleChange(value:Boolean):void { if(this._updateSnapshotOnScaleChange == value) { return; } this._updateSnapshotOnScaleChange = value; this.invalidate(INVALIDATION_FLAG_DATA); } /** * @private */ protected var _useSnapshotDelayWorkaround:Boolean = false; /** * Fixes an issue where flash.text.TextField renders * incorrectly when drawn to BitmapData by waiting one * frame. * *

Warning: enabling this workaround may cause slight flickering * after the text property is changed.

* *

In the following example, the workaround is enabled:

* * * textEditor.useSnapshotDelayWorkaround = true; * * @default false */ public function get useSnapshotDelayWorkaround():Boolean { return this._useSnapshotDelayWorkaround; } /** * @private */ public function set useSnapshotDelayWorkaround(value:Boolean):void { if(this._useSnapshotDelayWorkaround == value) { return; } this._useSnapshotDelayWorkaround = value; this.invalidate(INVALIDATION_FLAG_DATA); } /** * @private */ protected var resetScrollOnFocusOut:Boolean = true; /** * @private */ override public function dispose():void { if(this.textSnapshot) { //avoid the need to call dispose(). we'll create a new snapshot //when the renderer is added to stage again. this.textSnapshot.texture.dispose(); this.removeChild(this.textSnapshot, true); this.textSnapshot = null; } if(this.textField) { if(this.textField.parent) { this.textField.parent.removeChild(this.textField); } this.textField.removeEventListener(flash.events.Event.CHANGE, textField_changeHandler); this.textField.removeEventListener(FocusEvent.FOCUS_IN, textField_focusInHandler); this.textField.removeEventListener(FocusEvent.FOCUS_OUT, textField_focusOutHandler); this.textField.removeEventListener(KeyboardEvent.KEY_DOWN, textField_keyDownHandler); this.textField.removeEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, textField_softKeyboardActivateHandler); this.textField.removeEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, textField_softKeyboardDeactivateHandler); } //this isn't necessary, but if a memory leak keeps the text renderer //from being garbage collected, freeing up the text field may help //ease major memory pressure from native filters this.textField = null; this.measureTextField = null; this.stateContext = null; super.dispose(); } /** * @private */ override public function render(painter:Painter):void { if(this.textSnapshot) { if(this._updateSnapshotOnScaleChange) { this.getTransformationMatrix(this.stage, HELPER_MATRIX); if(matrixToScaleX(HELPER_MATRIX) != this._lastGlobalScaleX || matrixToScaleY(HELPER_MATRIX) != this._lastGlobalScaleY) { //the snapshot needs to be updated because the scale has //changed since the last snapshot was taken. this.invalidate(INVALIDATION_FLAG_SIZE); this.validate(); } } this.positionSnapshot(); } //we'll skip this if the text field isn't visible to avoid running //that code every frame. if(this.textField && this.textField.visible) { this.transformTextField(); } super.render(painter); } /** * @inheritDoc */ public function setFocus(position:Point = null):void { if(this.textField) { if(!this.textField.parent) { Starling.current.nativeStage.addChild(this.textField); } if(position) { var gutterPositionOffset:Number = 2; if(this._useGutter) { gutterPositionOffset = 0; } var positionX:Number = position.x - this.textSnapshot.x + gutterPositionOffset; var positionY:Number = position.y - this.textSnapshot.y + gutterPositionOffset; if(positionX < 0) { this._pendingSelectionBeginIndex = this._pendingSelectionEndIndex = 0; } else { this._pendingSelectionBeginIndex = this.getSelectionIndexAtPoint(positionX, positionY); if(this._pendingSelectionBeginIndex < 0) { if(this._multiline) { var lineIndex:int = int(positionY / this.textField.getLineMetrics(0).height) + (this.textField.scrollV - 1); try { this._pendingSelectionBeginIndex = this.textField.getLineOffset(lineIndex) + this.textField.getLineLength(lineIndex); if(this._pendingSelectionBeginIndex != this._text.length) { this._pendingSelectionBeginIndex--; } } catch(error:Error) { //we may be checking for a line beyond the //end that doesn't exist this._pendingSelectionBeginIndex = this._text.length; } } else { this._pendingSelectionBeginIndex = this.getSelectionIndexAtPoint(positionX, this.textField.getLineMetrics(0).ascent / 2); if(this._pendingSelectionBeginIndex < 0) { this._pendingSelectionBeginIndex = this._text.length; } } } else { var bounds:Rectangle = this.textField.getCharBoundaries(this._pendingSelectionBeginIndex); //bounds should never be null because the character //index passed to getCharBoundaries() comes from a //call to getCharIndexAtPoint(). however, a user //reported that a null reference error happened //here! I couldn't reproduce, but I might as well //assume that the runtime has a bug. won't hurt. if(bounds) { var boundsX:Number = bounds.x; if(bounds && (boundsX + bounds.width - positionX) < (positionX - boundsX)) { this._pendingSelectionBeginIndex++; } } } this._pendingSelectionEndIndex = this._pendingSelectionBeginIndex; } } else { this._pendingSelectionBeginIndex = this._pendingSelectionEndIndex = -1; } if(!FocusManager.isEnabledForStage(this.stage)) { Starling.current.nativeStage.focus = this.textField; } this.textField.requestSoftKeyboard(); if(this._textFieldHasFocus) { this.invalidate(INVALIDATION_FLAG_SELECTED); } } else { this._isWaitingToSetFocus = true; } } /** * @inheritDoc */ public function clearFocus():void { if(!this._textFieldHasFocus) { return; } var nativeStage:Stage = Starling.current.nativeStage; if(nativeStage.focus === this.textField) { //only clear the native focus when our native target has focus //because otherwise another component may lose focus. //setting the focus to Starling.current.nativeStage doesn't work //here, so we need to use null. on Android, if we give focus to the //nativeStage, focus will be removed from the StageText, but the //soft keyboard will incorrectly remain open. nativeStage.focus = null; //previously, there was a comment here that said that the native //stage focus should not be set to null. this was due to an //issue in focus manager where focus would be restored //incorrectly if the stage focus became null. this issue was //fixed, and it is now considered safe to use null. } } /** * @inheritDoc */ public function selectRange(beginIndex:int, endIndex:int):void { if(!this._isEditable && !this._isSelectable) { return; } if(this.textField) { if(!this._isValidating) { this.validate(); } this.textField.setSelection(beginIndex, endIndex); } else { this._pendingSelectionBeginIndex = beginIndex; this._pendingSelectionEndIndex = endIndex; } } /** * @inheritDoc */ public function measureText(result:Point = null):Point { if(!result) { result = new Point(); } var needsWidth:Boolean = this._explicitWidth !== this._explicitWidth; //isNaN var needsHeight:Boolean = this._explicitHeight !== this._explicitHeight; //isNaN if(!needsWidth && !needsHeight) { result.x = this._explicitWidth; result.y = this._explicitHeight; return result; } //if a parent component validates before we're added to the stage, //measureText() may be called before initialization, so we need to //force it. if(!this._isInitialized) { this.initializeInternal(); } this.commit(); result = this.measure(result); return result; } /** * Sets the TextFormat to be used by the text renderer when * the currentState property of the * stateContext matches the specified state value. * *

If an TextFormat is not defined for a specific * state, the value of the textFormat property will be * used instead.

* *

If the disabledTextFormat property is not * null and the isEnabled property is * false, all other text formats will be ignored.

* * @see #stateContext * @see #textFormat */ public function setTextFormatForState(state:String, textFormat:TextFormat):void { if(textFormat) { if(!this._textFormatForState) { this._textFormatForState = {}; } this._textFormatForState[state] = textFormat; } else { delete this._textFormatForState[state]; } //if the context's current state is the state that we're modifying, //we need to use the new value immediately. if(this._stateContext && this._stateContext.currentState === state) { this.invalidate(INVALIDATION_FLAG_STATE); } } /** * @private */ override protected function initialize():void { this.textField = new TextField(); //let's ensure that the text field can only get keyboard focus //through code. no need to set mouseEnabled to false since the text //field won't be visible until it needs to be interactive, so it //can't receive focus with mouse/touch anyway. this.textField.tabEnabled = false; this.textField.visible = false; this.textField.needsSoftKeyboard = true; this.textField.addEventListener(flash.events.Event.CHANGE, textField_changeHandler); this.textField.addEventListener(FocusEvent.FOCUS_IN, textField_focusInHandler); this.textField.addEventListener(FocusEvent.FOCUS_OUT, textField_focusOutHandler); this.textField.addEventListener(KeyboardEvent.KEY_DOWN, textField_keyDownHandler); this.textField.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_ACTIVATE, textField_softKeyboardActivateHandler); this.textField.addEventListener(SoftKeyboardEvent.SOFT_KEYBOARD_DEACTIVATE, textField_softKeyboardDeactivateHandler); //when adding more events here, don't forget to remove them when the //text editor is disposed this.measureTextField = new TextField(); this.measureTextField.autoSize = TextFieldAutoSize.LEFT; this.measureTextField.selectable = false; this.measureTextField.tabEnabled = false; this.measureTextField.mouseWheelEnabled = false; this.measureTextField.mouseEnabled = false; } /** * @private */ override protected function draw():void { var sizeInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_SIZE); this.commit(); sizeInvalid = this.autoSizeIfNeeded() || sizeInvalid; this.layout(sizeInvalid); } /** * @private */ protected function commit():void { var stylesInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STYLES); var dataInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_DATA); var stateInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STATE); if(dataInvalid || stylesInvalid || stateInvalid) { this.refreshTextFormat(); this.commitStylesAndData(this.textField); } } /** * 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 if(!needsWidth && !needsHeight) { return false; } this.measure(HELPER_POINT); return this.setSizeInternal(HELPER_POINT.x, HELPER_POINT.y, false); } /** * @private */ protected function measure(result:Point = null):Point { if(!result) { result = new Point(); } var needsWidth:Boolean = this._explicitWidth !== this._explicitWidth; //isNaN var needsHeight:Boolean = this._explicitHeight !== this._explicitHeight; //isNaN if(!needsWidth && !needsHeight) { result.x = this._explicitWidth; result.y = this._explicitHeight; return result; } this.commitStylesAndData(this.measureTextField); var gutterDimensionsOffset:Number = 4; if(this._useGutter) { gutterDimensionsOffset = 0; } var newWidth:Number = this._explicitWidth; if(needsWidth) { this.measureTextField.wordWrap = false; newWidth = this.measureTextField.width - gutterDimensionsOffset; if(newWidth < this._explicitMinWidth) { newWidth = this._explicitMinWidth; } else if(newWidth > this._maxWidth) { newWidth = this._maxWidth; } } var newHeight:Number = this._explicitHeight; if(needsHeight) { this.measureTextField.wordWrap = this._wordWrap; this.measureTextField.width = newWidth + gutterDimensionsOffset; newHeight = this.measureTextField.height - gutterDimensionsOffset; if(this._useGutter) { newHeight += 4; } if(newHeight < this._explicitMinHeight) { newHeight = this._explicitMinHeight; } else if(newHeight > this._maxHeight) { newHeight = this._maxHeight; } } result.x = newWidth; result.y = newHeight; return result; } /** * @private */ protected function commitStylesAndData(textField:TextField):void { textField.antiAliasType = this._antiAliasType; textField.background = this._background; textField.backgroundColor = this._backgroundColor; textField.border = this._border; textField.borderColor = this._borderColor; textField.gridFitType = this._gridFitType; textField.sharpness = this._sharpness; textField.thickness = this._thickness; textField.maxChars = this._maxChars; textField.restrict = this._restrict; textField.alwaysShowSelection = this._alwaysShowSelection; textField.displayAsPassword = this._displayAsPassword; textField.wordWrap = this._wordWrap; textField.multiline = this._multiline; textField.embedFonts = this._embedFonts; textField.type = this._isEditable ? TextFieldType.INPUT : TextFieldType.DYNAMIC; textField.selectable = this._isEnabled && (this._isEditable || this._isSelectable); if(textField === this.textField) { //for some reason, textField.defaultTextFormat always fails //comparison against currentTextFormat. if we save to a member //variable and compare against that instead, it works. //I guess text field creates a different TextFormat object. var isFormatDifferent:Boolean = this._previousTextFormat != this.currentTextFormat; this._previousTextFormat = this.currentTextFormat; } textField.defaultTextFormat = this.currentTextFormat; if(this._isHTML) { if(isFormatDifferent || textField.htmlText != this._text) { if(textField == this.textField && this._pendingSelectionBeginIndex < 0) { //if the TextFormat has changed from the last commit, //the selection range may be lost when we set the text //so we need to save it to restore later. this._pendingSelectionBeginIndex = this.textField.selectionBeginIndex; this._pendingSelectionEndIndex = this.textField.selectionEndIndex; } //the TextField's text should be updated after a TextFormat //change because otherwise it will keep using the old one. textField.htmlText = this._text; } } else { if(isFormatDifferent || textField.text != this._text) { if(textField == this.textField && this._pendingSelectionBeginIndex < 0) { this._pendingSelectionBeginIndex = this.textField.selectionBeginIndex; this._pendingSelectionEndIndex = this.textField.selectionEndIndex; } textField.text = this._text; } } } /** * @private */ protected function refreshTextFormat():void { var textFormat:TextFormat; if(this._stateContext && this._textFormatForState) { var currentState:String = this._stateContext.currentState; if(currentState in this._textFormatForState) { textFormat = TextFormat(this._textFormatForState[currentState]); } } if(!textFormat && !this._isEnabled && this._disabledTextFormat) { textFormat = this._disabledTextFormat; } if(!textFormat) { //let's fall back to using Starling's embedded mini font if no //text format has been specified if(!this._textFormat) { this._textFormat = new TextFormat(); } textFormat = this._textFormat; } this.currentTextFormat = textFormat; } /** * @private */ protected function layout(sizeInvalid:Boolean):void { var stylesInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STYLES); var dataInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_DATA); var stateInvalid:Boolean = this.isInvalid(INVALIDATION_FLAG_STATE); if(sizeInvalid) { this.refreshSnapshotParameters(); this.refreshTextFieldSize(); this.transformTextField(); this.positionSnapshot(); } this.checkIfNewSnapshotIsNeeded(); if(!this._textFieldHasFocus && (sizeInvalid || stylesInvalid || dataInvalid || stateInvalid || this._needsNewTexture)) { if(this._useSnapshotDelayWorkaround) { //sometimes, we need to wait a frame for flash.text.TextField //to render properly when drawing to BitmapData. this.addEventListener(Event.ENTER_FRAME, refreshSnapshot_enterFrameHandler); } else { this.refreshSnapshot(); } } this.doPendingActions(); } /** * @private */ protected function getSelectionIndexAtPoint(pointX:Number, pointY:Number):int { return this.textField.getCharIndexAtPoint(pointX, pointY); } /** * @private */ protected function refreshTextFieldSize():void { var gutterDimensionsOffset:Number = 4; if(this._useGutter) { gutterDimensionsOffset = 0; } this.textField.width = this.actualWidth + gutterDimensionsOffset; this.textField.height = this.actualHeight + gutterDimensionsOffset; } /** * @private */ protected function refreshSnapshotParameters():void { this._textFieldOffsetX = 0; this._textFieldOffsetY = 0; this._textFieldSnapshotClipRect.x = 0; this._textFieldSnapshotClipRect.y = 0; var scaleFactor:Number = Starling.contentScaleFactor; var clipWidth:Number = this.actualWidth * scaleFactor; if(this._updateSnapshotOnScaleChange) { this.getTransformationMatrix(this.stage, HELPER_MATRIX); clipWidth *= matrixToScaleX(HELPER_MATRIX); } if(clipWidth < 0) { clipWidth = 0; } var clipHeight:Number = this.actualHeight * scaleFactor; if(this._updateSnapshotOnScaleChange) { clipHeight *= matrixToScaleY(HELPER_MATRIX); } if(clipHeight < 0) { clipHeight = 0; } this._textFieldSnapshotClipRect.width = clipWidth; this._textFieldSnapshotClipRect.height = clipHeight; } /** * @private */ protected function transformTextField():void { //there used to be some code here that returned immediately if the //TextField wasn't visible. some mobile devices displayed the text //at the wrong scale if the TextField weren't transformed before //being made visible, so I had to remove it. I moved the visible //check into render(), since it can still benefit from the //optimization there. see issue #1104. HELPER_POINT.x = HELPER_POINT.y = 0; this.getTransformationMatrix(this.stage, HELPER_MATRIX); var globalScaleX:Number = matrixToScaleX(HELPER_MATRIX); var globalScaleY:Number = matrixToScaleY(HELPER_MATRIX); var smallerGlobalScale:Number = globalScaleX; if(globalScaleY < smallerGlobalScale) { smallerGlobalScale = globalScaleY; } var nativeScaleFactor:Number = 1; if(Starling.current.supportHighResolutions) { nativeScaleFactor = Starling.current.nativeStage.contentsScaleFactor; } var scaleFactor:Number = Starling.contentScaleFactor / nativeScaleFactor; var gutterPositionOffset:Number = 0; if(!this._useGutter) { gutterPositionOffset = 2 * smallerGlobalScale; } if(this.is3D) { HELPER_MATRIX3D = this.getTransformationMatrix3D(this.stage, HELPER_MATRIX3D); HELPER_POINT3D = MatrixUtil.transformCoords3D(HELPER_MATRIX3D, -gutterPositionOffset, -gutterPositionOffset, 0, HELPER_POINT3D); HELPER_POINT.setTo(HELPER_POINT3D.x, HELPER_POINT3D.y); } else { MatrixUtil.transformCoords(HELPER_MATRIX, -gutterPositionOffset, -gutterPositionOffset, HELPER_POINT); } var starlingViewPort:Rectangle = Starling.current.viewPort; this.textField.x = Math.round(starlingViewPort.x + (HELPER_POINT.x * scaleFactor)); this.textField.y = Math.round(starlingViewPort.y + (HELPER_POINT.y * scaleFactor)); this.textField.rotation = matrixToRotation(HELPER_MATRIX) * 180 / Math.PI; this.textField.scaleX = matrixToScaleX(HELPER_MATRIX) * scaleFactor; this.textField.scaleY = matrixToScaleY(HELPER_MATRIX) * scaleFactor; } /** * @private */ protected function positionSnapshot():void { if(!this.textSnapshot) { return; } this.getTransformationMatrix(this.stage, HELPER_MATRIX); this.textSnapshot.x = Math.round(HELPER_MATRIX.tx) - HELPER_MATRIX.tx; this.textSnapshot.y = Math.round(HELPER_MATRIX.ty) - HELPER_MATRIX.ty; } /** * @private */ protected function checkIfNewSnapshotIsNeeded():void { var canUseRectangleTexture:Boolean = Starling.current.profile != Context3DProfile.BASELINE_CONSTRAINED; if(canUseRectangleTexture) { this._snapshotWidth = this._textFieldSnapshotClipRect.width; this._snapshotHeight = this._textFieldSnapshotClipRect.height; } else { this._snapshotWidth = MathUtil.getNextPowerOfTwo(this._textFieldSnapshotClipRect.width); this._snapshotHeight = MathUtil.getNextPowerOfTwo(this._textFieldSnapshotClipRect.height); } var textureRoot:ConcreteTexture = this.textSnapshot ? this.textSnapshot.texture.root : null; this._needsNewTexture = this._needsNewTexture || !this.textSnapshot || textureRoot.scale != Starling.contentScaleFactor || this._snapshotWidth != textureRoot.width || this._snapshotHeight != textureRoot.height; } /** * @private */ protected function doPendingActions():void { if(this._isWaitingToSetFocus) { this._isWaitingToSetFocus = false; this.setFocus(); } if(this._pendingSelectionBeginIndex >= 0) { var startIndex:int = this._pendingSelectionBeginIndex; var endIndex:int = this._pendingSelectionEndIndex; this._pendingSelectionBeginIndex = -1; this._pendingSelectionEndIndex = -1; this.selectRange(startIndex, endIndex); } } /** * @private */ protected function texture_onRestore():void { if(this.textSnapshot && this.textSnapshot.texture && this.textSnapshot.texture.scale != Starling.contentScaleFactor) { //if we've changed between scale factors, we need to recreate //the texture to match the new scale factor. this.invalidate(INVALIDATION_FLAG_SIZE); } else { this.refreshSnapshot(); } } /** * @private */ protected function refreshSnapshot():void { if(this._snapshotWidth <= 0 || this._snapshotHeight <= 0) { return; } var gutterPositionOffset:Number = 2; if(this._useGutter) { gutterPositionOffset = 0; } var scaleFactor:Number = Starling.contentScaleFactor; if(this._updateSnapshotOnScaleChange) { this.getTransformationMatrix(this.stage, HELPER_MATRIX); var globalScaleX:Number = matrixToScaleX(HELPER_MATRIX); var globalScaleY:Number = matrixToScaleY(HELPER_MATRIX); } HELPER_MATRIX.identity(); HELPER_MATRIX.translate(this._textFieldOffsetX - gutterPositionOffset, this._textFieldOffsetY - gutterPositionOffset); HELPER_MATRIX.scale(scaleFactor, scaleFactor); if(this._updateSnapshotOnScaleChange) { HELPER_MATRIX.scale(globalScaleX, globalScaleY); } var bitmapData:BitmapData = new BitmapData(this._snapshotWidth, this._snapshotHeight, true, 0x00ff00ff); bitmapData.draw(this.textField, HELPER_MATRIX, null, null, this._textFieldSnapshotClipRect); var newTexture:Texture; if(!this.textSnapshot || this._needsNewTexture) { //skip Texture.fromBitmapData() because we don't want //it to create an onRestore function that will be //immediately discarded for garbage collection. newTexture = Texture.empty(bitmapData.width / scaleFactor, bitmapData.height / scaleFactor, true, false, false, scaleFactor); newTexture.root.uploadBitmapData(bitmapData); newTexture.root.onRestore = texture_onRestore; } if(!this.textSnapshot) { this.textSnapshot = new Image(newTexture); this.addChild(this.textSnapshot); } else { if(this._needsNewTexture) { this.textSnapshot.texture.dispose(); this.textSnapshot.texture = newTexture; this.textSnapshot.readjustSize(); } else { //this is faster, if we haven't resized the bitmapdata var existingTexture:Texture = this.textSnapshot.texture; existingTexture.root.uploadBitmapData(bitmapData); } } if(this._updateSnapshotOnScaleChange) { this.textSnapshot.scaleX = 1 / globalScaleX; this.textSnapshot.scaleY = 1 / globalScaleY; this._lastGlobalScaleX = globalScaleX; this._lastGlobalScaleY = globalScaleY; } this.textSnapshot.alpha = this._text.length > 0 ? 1 : 0; bitmapData.dispose(); this._needsNewTexture = false; } /** * @private */ protected function textEditor_addedToStageHandler(event:Event):void { if(!this.textField.parent) { //the text field needs to be on the native stage to measure properly Starling.current.nativeStage.addChild(this.textField); } } /** * @private */ protected function textEditor_removedFromStageHandler(event:Event):void { if(this.textField.parent) { //remove this from the stage, if needed //it will be added back next time we receive focus this.textField.parent.removeChild(this.textField); } } /** * @private */ protected function hasFocus_enterFrameHandler(event:Event):void { if(this.textSnapshot) { this.textSnapshot.visible = !this._textFieldHasFocus; } this.textField.visible = this._textFieldHasFocus; if(this._textFieldHasFocus) { var target:DisplayObject = this; do { if(!target.visible) { this.clearFocus(); break; } target = target.parent; } while(target) } else { this.removeEventListener(Event.ENTER_FRAME, hasFocus_enterFrameHandler); } } /** * @private */ protected function refreshSnapshot_enterFrameHandler(event:Event):void { this.removeEventListener(Event.ENTER_FRAME, refreshSnapshot_enterFrameHandler); this.refreshSnapshot(); } /** * @private */ protected function stage_touchHandler(event:TouchEvent):void { var touch:Touch = event.getTouch(this.stage, TouchPhase.BEGAN); if(!touch) //we only care about began touches { return; } touch.getLocation(this.stage, HELPER_POINT); var isInBounds:Boolean = this.contains(this.stage.hitTest(HELPER_POINT)); if(isInBounds) //if the touch is in the text editor, it's all good { return; } //if the touch begins anywhere else, it's a focus out! this.clearFocus(); } /** * @private */ protected function textField_changeHandler(event:flash.events.Event):void { if(this._isHTML) { this.text = this.textField.htmlText; } else { this.text = this.textField.text; } } /** * @private */ protected function textField_focusInHandler(event:FocusEvent):void { this._textFieldHasFocus = true; this.stage.addEventListener(TouchEvent.TOUCH, stage_touchHandler); this.addEventListener(Event.ENTER_FRAME, hasFocus_enterFrameHandler); this.dispatchEventWith(FeathersEventType.FOCUS_IN); } /** * @private */ protected function textField_focusOutHandler(event:FocusEvent):void { this._textFieldHasFocus = false; this.stage.removeEventListener(TouchEvent.TOUCH, stage_touchHandler); if(this.resetScrollOnFocusOut) { this.textField.scrollH = this.textField.scrollV = 0; } //the text may have changed, so we invalidate the data flag this.invalidate(INVALIDATION_FLAG_DATA); this.dispatchEventWith(FeathersEventType.FOCUS_OUT); } /** * @private */ protected function textField_keyDownHandler(event:KeyboardEvent):void { if(event.keyCode == Keyboard.ENTER) { this.dispatchEventWith(FeathersEventType.ENTER); } else if(!FocusManager.isEnabledForStage(this.stage) && event.keyCode == Keyboard.TAB) { this.clearFocus(); } } /** * @private */ protected function textField_softKeyboardActivateHandler(event:SoftKeyboardEvent):void { this.dispatchEventWith(FeathersEventType.SOFT_KEYBOARD_ACTIVATE, true); } /** * @private */ protected function textField_softKeyboardDeactivateHandler(event:SoftKeyboardEvent):void { this.dispatchEventWith(FeathersEventType.SOFT_KEYBOARD_DEACTIVATE, true); } /** * @private */ protected function stateContext_stateChangeHandler(event:Event):void { this.invalidate(INVALIDATION_FLAG_STATE); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy