UNPKG

@awayjs/scene

Version:
1,468 lines (1,262 loc) 126 kB
import { ColorUtils, Matrix, Rectangle, Point, Vector3D } from '@awayjs/core'; import { ImageSampler, Float2Attributes } from '@awayjs/stage'; import { IEntity } from '@awayjs/view'; import { Style, TriangleElements } from '@awayjs/renderer'; import { MaterialBase } from '@awayjs/materials'; import { Graphics, Shape, GraphicsFactoryHelper, MaterialManager } from '@awayjs/graphics'; import { TesselatedFontTable } from '../text/TesselatedFontTable'; import { AntiAliasType } from '../text/AntiAliasType'; import { GridFitType } from '../text/GridFitType'; import { TextFieldAutoSize } from '../text/TextFieldAutoSize'; import { TextFieldType } from '../text/TextFieldType'; import { TextFormat } from '../text/TextFormat'; import { TextInteractionMode } from '../text/TextInteractionMode'; import { TextLineMetrics } from '../text/TextLineMetrics'; import { KeyboardEvent } from '../events/KeyboardEvent'; import { TextfieldEvent } from '../events/TextfieldEvent'; import { DisplayObject } from './DisplayObject'; import { DisplayObjectContainer } from './DisplayObjectContainer'; import { Sprite } from './Sprite'; import { TextSprite } from './TextSprite'; import { TextShape } from '../text/TextShape'; import { ITextfieldAdapter } from '../adapters/ITextfieldAdapter'; import { HTMLTextProcessor } from '../text/HTMLTextProcessor'; import { TextFormatAlign } from '../text/TextFormatAlign'; import { MouseEvent } from '../events/MouseEvent'; import { Settings } from '../Settings'; interface IWord { start: number; x: number; y: number; width: number; len: number; } interface IRunEntry { start: number, count: number, width: number, space: number } class WordStore { store: Array<IWord>; index: number = -1; constructor(size = 40) { this.store = Array.from({ length: size }, e => ({ x: Infinity, y: Infinity, start: 0, width: 0, len: 0 })); } public put ( start: number, x: number, y: number, width: number, len: number ): IWord { this.index++; const word = this.store [this.index] || (this.store [this.index] = {} as IWord); word.start = start; word.x = x; word.y = y; word.width = width; word.len = len; return word; } public get last(): IWord { return this.store[this.index]; } public get length () { return this.index + 1; } public set length (v: number) { this.index = v - 1; } public get (index: number) { return this.store[index]; } public free() { this.index = -1; } public dispose() { this.store = null; this.index = 0; } } const enum CHAR_CODES { TAB = 9, LF = 10, CR = 13, SPACE = 32, BS = 92, N = 110, R = 114, } const MNEMOS = [ { test: /&apos;/g, replace: '\'' }, { test: /&gt;/g, replace: '>' } ]; /** * The TextField class is used to create display objects for text display and * input. <ph outputclass="flexonly">You can use the TextField class to * perform low-level text rendering. However, in Flex, you typically use the * Label, Text, TextArea, and TextInput controls to process text. <ph * outputclass="flashonly">You can give a text field an instance name in the * Property inspector and use the methods and properties of the TextField * class to manipulate it with ActionScript. TextField instance names are * displayed in the Movie Explorer and in the Insert Target Path dialog box in * the Actions panel. * * <p>To create a text field dynamically, use the <code>TextField()</code> * constructor.</p> * * <p>The methods of the TextField class let you set, select, and manipulate * text in a dynamic or input text field that you create during authoring or * at runtime. </p> * * <p>ActionScript provides several ways to format your text at runtime. The * TextFormat class lets you set character and paragraph formatting for * TextField objects. You can apply Cascading Style Sheets(CSS) styles to * text fields by using the <code>TextField.styleSheet</code> property and the * StyleSheet class. You can use CSS to style built-in HTML tags, define new * formatting tags, or apply styles. You can assign HTML formatted text, which * optionally uses CSS styles, directly to a text field. HTML text that you * assign to a text field can contain embedded media(movie clips, SWF files, * GIF files, PNG files, and JPEG files). The text wraps around the embedded * media in the same way that a web browser wraps text around media embedded * in an HTML document. </p> * * <p>Flash Player supports a subset of HTML tags that you can use to format * text. See the list of supported HTML tags in the description of the * <code>htmlText</code> property.</p> * * @event change Dispatched after a control value is * modified, unlike the * <code>textInput</code> event, which is * dispatched before the value is modified. * Unlike the W3C DOM Event Model version of * the <code>change</code> event, which * dispatches the event only after the * control loses focus, the ActionScript 3.0 * version of the <code>change</code> event * is dispatched any time the control * changes. For example, if a user types text * into a text field, a <code>change</code> * event is dispatched after every keystroke. * @event link Dispatched when a user clicks a hyperlink * in an HTML-enabled text field, where the * URL begins with "event:". The remainder of * the URL after "event:" is placed in the * text property of the LINK event. * * <p><b>Note:</b> The default behavior, * adding the text to the text field, occurs * only when Flash Player generates the * event, which in this case happens when a * user attempts to input text. You cannot * put text into a text field by sending it * <code>textInput</code> events.</p> * @event scroll Dispatched by a TextField object * <i>after</i> the user scrolls. * @event textInput Flash Player dispatches the * <code>textInput</code> event when a user * enters one or more characters of text. * Various text input methods can generate * this event, including standard keyboards, * input method editors(IMEs), voice or * speech recognition systems, and even the * act of pasting plain text with no * formatting or style information. * @event textInteractionModeChange Flash Player dispatches the * <code>textInteractionModeChange</code> * event when a user changes the interaction * mode of a text field. for example on * Android, one can toggle from NORMAL mode * to SELECTION mode using context menu * options */ export class TextField extends DisplayObjectContainer { private _onClipboardPasteDelegate: (event: ClipboardEvent) => void; private static _textFields: Array<TextField> = []; public static assetType: string = '[asset TextField]'; public static getNewTextField(): TextField { return (TextField._textFields.length) ? TextField._textFields.pop() : new TextField(); } public static clearPool() { TextField._textFields = []; } private static _onChangedEvent = new TextfieldEvent(TextfieldEvent.CHANGED); public textOffsetX: number = 0; public textOffsetY: number = 0; private _width: number; private _height: number; private _graphics: Graphics; private _bottomScrollV: number; private _caretIndex: number; private _maxScrollH: number; private _maxScrollV: number; private _numLines: number; private _selectionBeginIndex: number = 0; private _selectionEndIndex: number = 0; private _biggestLine: number=0; /** * Renderable text, used for computing a glyphs * @private */ private _iText: string = ''; /** * Place where is diff is begun to end * @private */ private _iTextDiffStart = 0; /** * Original text passed to field * @private */ private _text: string = ''; private _iTextWoLineBreaks: string = ''; // _iText without line breaks private _textInteractionMode: TextInteractionMode; private _textWidth: number; private _textHeight: number; private _firstCharInParagraph: number; private _imageReference: DisplayObject private _paragraphLength: number; public _newTextFormat: TextFormat = new TextFormat(); public _textFormats: TextFormat[]; public _textFormatsIdx: number[]; public textShapes: StringMap<TextShape>; private inMaskMode: boolean; private maskChild: Sprite; private textChild: TextSprite; private targetGraphics: Graphics; private cursorShape: Shape; private bgShapeSelect: Shape; private cursorIntervalID: number = -1; public cursorBlinking: boolean; public showSelection: boolean; private _textDirty: boolean; private _positionsDirty: boolean; private _glyphsDirty: boolean; private _shapesDirty: boolean; private _textShapesDirty: boolean; private _newFormatDirty: boolean; public chars_codes: number[] = []; public chars_width: number[] = []; public tf_per_char: TextFormat[] = []; // stores offset and length and width for each word public words: WordStore = new WordStore(10); // Amount of words that was before call reconstuct /*internal*/ _lastWordsCount: number; private _textRuns_formats: TextFormat[]=[]; // stores textFormat for each textrun // stores words-offset, word-count and width for each textrun private _textRuns_words: Array<IRunEntry> = []; private _paragraph_textRuns_indices: number[]=[]; // stores textFormat for each textrun private _maxWidthLine: number=0; private _labelData: any=null; public html: boolean; private lines_wordStartIndices: number[] = []; private lines_wordEndIndices: number[] = []; private lines_start_y: number[] = []; private lines_start_x: number[] = []; private lines_charIdx_start: number[] = []; private lines_charIdx_end: number[] = []; private lines_width: number[] = []; private lines_height: number[] = []; private lines_numSpacesPerline: number[] = []; private char_positions_x: number[] = []; private char_positions_y: number[] = []; // keeping track of the original textfield that was used for cloning this one. public sourceTextField: TextField=null; private _maskWidth: number=0; private _maskHeight: number=0; private _maskTextOffsetX: number=0; private _maskTextOffsetY: number=0; public bgShape: Shape; public isStatic: boolean=false; public updateMaskMode() { // mask needed if (this.inMaskMode) { if (this._maskWidth != this._width || this._maskHeight != this._height || this._maskTextOffsetX != this.textOffsetX || this._maskTextOffsetY != this.textOffsetY) { this._maskWidth = this._width; this._maskHeight = this._height; this._maskTextOffsetX = this.textOffsetX; this._maskTextOffsetY = this.textOffsetY; this.maskChild.graphics.clear(); this.maskChild.graphics.beginFill(0xffffff); this.maskChild.graphics.drawRect(this.textOffsetX, this.textOffsetY, this._width, this._height); this.maskChild.graphics.endFill(); } this._graphics.clear(); } if (!this.inMaskMode) { // masking already setup // just make sure the mask has correct size this.inMaskMode = true; if (!this.maskChild) this.maskChild = new Sprite(); if (!this.textChild) this.textChild = new TextSprite(); this.textChild.mouseEnabled = false; this.textChild.parentTextField = this; this.maskChild.mouseEnabled = false; this.maskChild.graphics.beginFill(0xffffff); this.maskChild.graphics.drawRect(this.textOffsetX, this.textOffsetY, this._width, this._height); this.maskChild.graphics.endFill(); this.addChild(this.maskChild); this.addChild(this.textChild); this.maskChild.visible = false; this._graphics.clear(); this.targetGraphics = this.textChild.graphics; } // only use masking if needed: if (this._textWidth > this._width || this._textHeight > this._height) { this.textChild.scriptMask = this.maskChild; } else { this.textChild.scriptMask = null; } return; } public getMouseCursor(): string { return this.cursorType; } public get isInFocus(): boolean { return this._isInFocus; } public set isInFocus(value: boolean) { } public setFocus(value: boolean, fromMouseDown: boolean = false, sendSoftKeyEvent: boolean = true) { if (this._isInFocus == value) { return; } super.setFocus(value, fromMouseDown, sendSoftKeyEvent); this.enableInput(value); if (!this._selectable) { return; } // if (value) { // this.setSelection(0, this._iText.length); // // check if a adapter exists // if (sendSoftKeyEvent && this.adapter != this) { // // todo: create a ITextFieldAdapter, so we can use selectText() without casting to any // (<any> this.adapter).selectTextField(fromMouseDown); // } // } else { // this.setSelection(0, 0); // } this.setSelection(0, 0); this._glyphsDirty = true; this.invalidate(); } private enableInput(enable: boolean = true) { if (this.cursorIntervalID >= 0) { window.clearInterval(this.cursorIntervalID); this.cursorIntervalID = -1; } if (enable && this._isInFocus && this.selectable) { this.drawSelectionGraphics(); const myThis = this; this.cursorIntervalID = window.setInterval(function() { myThis.cursorBlinking = !myThis.cursorBlinking; if (!myThis.selectable) { myThis.cursorBlinking = true; } myThis._shapesDirty = true; myThis.invalidate(); }, 500); } // FFUUU, this not working because we prevent events, and Ctrl + V/C not bubbled to document // will use mannual handling a Ctrl + V/C /* if (enable) { document.addEventListener('paste', this._onClipboardPasteDelegate); } else { document.removeEventListener('paste', this._onClipboardPasteDelegate); }*/ } public findCharIdxForMouse(event: MouseEvent): number { const myPoint = new Point(event.position.x, event.position.y); let lineIdx = this.getLineIndexAtPoint(myPoint.x, myPoint.y); let charIdx = this.getCharIndexAtPoint(myPoint.x, myPoint.y, lineIdx); if (lineIdx >= this.lines_start_x.length) { lineIdx = this.lines_start_x.length - 1; } if (lineIdx < 0) { lineIdx = 0; } if (lineIdx >= 0 && charIdx < 0 && this.lines_start_x[lineIdx] !== undefined) { if (myPoint.x <= this.lines_start_x[lineIdx]) { charIdx = this.lines_charIdx_start[lineIdx]; } else { charIdx = this.lines_charIdx_end[lineIdx]; } } if (lineIdx < 0 || charIdx < 0) { charIdx = 0; } return charIdx; } private startSelectionByMouseDelegate: (event) => void; private startSelectionByMouse(event) { this._selectionBeginIndex = this.findCharIdxForMouse(event); this._selectionEndIndex = this._selectionBeginIndex; if (this.cursorShape) this.cursorShape.invalidate(); this.cursorShape = undefined; if (this.bgShapeSelect) this.bgShapeSelect.invalidate(); this.bgShapeSelect = undefined; this._glyphsDirty = true; this._shapesDirty = true; this._textShapesDirty = true; this.cursorBlinking = false; this.drawSelectionGraphics(); } private stopSelectionByMouseDelegate: (event) => void; private stopSelectionByMouse(event) { this._selectionEndIndex = this.findCharIdxForMouse(event); //console.log("stopSelectionByMouse", this._selectionBeginIndex, this._selectionEndIndex); this._glyphsDirty = true; this.reConstruct(); this.drawSelectionGraphics(); } private updateSelectionByMouseDelegate: (event) => void; private updateSelectionByMouse(event) { this._selectionEndIndex = this.findCharIdxForMouse(event); if (this.bgShapeSelect) this.bgShapeSelect.invalidate(); this.bgShapeSelect = undefined; //console.log("updateSelectionByMouse", this._selectionBeginIndex, this._selectionEndIndex); this._glyphsDirty = true; this.reConstruct(); this.drawSelectionGraphics(); } private drawSelectionGraphics() { if (this._selectionBeginIndex < 0) { this._selectionBeginIndex = 0; } if (this._selectionBeginIndex > this.char_positions_x.length) { this._selectionBeginIndex = this.char_positions_x.length; } if (this._selectionEndIndex < 0) { this._selectionEndIndex = 0; } if (this._selectionEndIndex > this.char_positions_x.length) { this._selectionEndIndex = this.char_positions_x.length; } if (this._selectionBeginIndex === this._selectionEndIndex) { this.showSelection = false; this.drawCursor(); } else { this.showSelection = true; this.cursorBlinking = true; // disable cursor if text select mode this.drawSelectedBG(); } } private scrollToCursor(x, y) { // if(!this.textChild){ // return; // } // if(x>this._width){ // this.textChild.x-=10; // } // if(x<Math.abs(this.textChild.x)){ // this.textChild.x=this.textChild.x+x+2; // } // if(this.textChild.x<(this._width-this.textChild.width)){ // this.textChild.x=this._width-this.textChild.width; // } // if(this.textChild.x>0){ // this.textChild.x=0; // } } private drawCursor() { this._shapesDirty = true; if (this.cursorBlinking || !this.selectable || this.selectionBeginIndex !== this.selectionEndIndex) { return; } let x: number = 0; let y: number = 0; let tf: TextFormat = this._newTextFormat; if (this.char_positions_x.length == 0) { x = this.textOffsetX + (this._width / 2) + this._textWidth / 2; if (tf.align == 'justify') { // do nothing } else if (tf.align == 'center') { // do nothing } else if (tf.align == 'right') { x = this.textOffsetX + this._width - 2; } else if (tf.align == 'left') { x = this.textOffsetX + 4 + this._textWidth; } } else if (this._selectionBeginIndex == this.char_positions_x.length) { x = this.char_positions_x[this._selectionBeginIndex - 1] + this.chars_width[this._selectionBeginIndex - 1]; y = this.char_positions_y[this._selectionBeginIndex - 1]; tf = this.tf_per_char[this._selectionBeginIndex - 1]; } else { x = this.char_positions_x[this._selectionBeginIndex]; y = this.char_positions_y[this._selectionBeginIndex]; tf = this.tf_per_char[this._selectionBeginIndex]; } tf.font_table.initFontSize(tf.size); const height: number = tf.font_table.getLineHeight(); const color = this.getTextColorForTextFormat(tf); let cursorScale: number = this.internalScale.x; if (cursorScale <= 0) { cursorScale = 1; } const cursorRect = [x - (0.5 * cursorScale),y,cursorScale,height]; if (!this.cursorShape) { this.cursorShape = GraphicsFactoryHelper.drawRectangles(cursorRect,color,1); this.cursorShape.usages++;//TODO: get rid of this memory lea } else { GraphicsFactoryHelper.updateRectanglesShape(this.cursorShape, cursorRect); } if (this.cursorShape.style.color !== color) { const alpha = ColorUtils.float32ColorToARGB(color)[0]; const obj = MaterialManager.getMaterialForColor(color, (alpha / 255) || 1); if (obj.colorPos) { this.cursorShape.style = new Style(); const sampler: ImageSampler = new ImageSampler(); obj.material.animateUVs = true; this.cursorShape.style.color = color; this.cursorShape.style.addSamplerAt(sampler, obj.material.getTextureAt(0)); this.cursorShape.style.uvMatrix = new Matrix(0, 0, 0, 0, obj.colorPos.x, obj.colorPos.y); } } this.scrollToCursor(x,y); } private drawSelectedBG() { this._shapesDirty = true; this._textShapesDirty = true; if (this._selectionBeginIndex < 0) { this._selectionBeginIndex = 0; } if (this._selectionBeginIndex > this.char_positions_x.length) { this._selectionBeginIndex = this.char_positions_x.length; } let select_start: number = this._selectionBeginIndex; let select_end: number = this._selectionEndIndex; if (this._selectionEndIndex < this._selectionBeginIndex) { select_start = this._selectionEndIndex; select_end = this._selectionBeginIndex; } let x: number = 0; let y: number = 0; let oldy: number = -1; let tf: TextFormat = null; let startx: number = -1; let width: number = 0; let height: number = 0; const rectangles: number[] = []; if (this.char_positions_x.length != 0 && this._selectionEndIndex != this._selectionBeginIndex) { const len: number = (select_end > this.char_positions_x.length) ? this.char_positions_x.length : select_end; //console.log(select_start, select_end); for (let i: number = select_start; i < len; i++) { if (i == this.char_positions_x.length) { x = this.char_positions_x[i - 1] + this.chars_width[i - 1]; y = this.char_positions_y[i - 1]; tf = this.tf_per_char[i - 1]; } else { x = this.char_positions_x[i]; y = this.char_positions_y[i]; tf = this.tf_per_char[i]; } if (startx < 0) { startx = x; } if (oldy >= 0 && oldy != y) { // new line rectangles.push(startx, oldy, width, height); width = 0; startx = x; } width += this.chars_width[i]; oldy = y; tf.font_table.initFontSize(tf.size); height = Math.max(height, tf.font_table.getLineHeight()); } } // if (this.bgShapeSelect) { // this.bgShapeSelect.dispose(); // this.bgShapeSelect=null; // } if (width > 0) { rectangles.push(startx, oldy, width, height); if (!this.bgShapeSelect) { this.bgShapeSelect = GraphicsFactoryHelper.drawRectangles(rectangles,0x0,1); this.bgShapeSelect.usages++; //TODO: get rid of this memory leak } else { GraphicsFactoryHelper.updateRectanglesShape(this.bgShapeSelect,rectangles); } return; } this.scrollToCursor(startx + width,oldy + height); } public drawBG(): void { //TODO this fixes masking using static textfields, but dynamic textfields still have problems. (see addChild(this.maskChild)) if (this._background) { this._graphics.beginFill(this.backgroundColor, 1); this._graphics.drawRect(this.textOffsetX, this.textOffsetY, this.width, this.height); this._graphics.endFill(); } //create a hitArea for the textfield let pickObject: Sprite = <Sprite> this.pickObject; if (!pickObject) { this.pickObject = pickObject || (pickObject = new Sprite()); pickObject.pickObjectFromTimeline = true; } const graphics: Graphics = pickObject.graphics; graphics.clear(); graphics.beginFill(0x000000, 1); graphics.drawRect(this.textOffsetX, this.textOffsetY, this.width, this.height); graphics.endFill(); } public drawBorder(): void { const half_thickness_x: number = this.border ? 0.25 * this.internalScale.x : 0; const half_thickness_y: number = this.border ? 0.25 * this.internalScale.y : 0; this._graphics.beginFill(this._borderColor, 1); this._graphics.drawRect(this.textOffsetX, this.textOffsetY, this._width, half_thickness_y * 2); this._graphics.drawRect( this.textOffsetX, this.textOffsetY + this._height - half_thickness_y * 2, this._width, half_thickness_y * 2); this._graphics.drawRect( this.textOffsetX, this.textOffsetY + half_thickness_y * 2, half_thickness_x * 2, this._height - half_thickness_y * 2); this._graphics.drawRect( this.textOffsetX + this._width - half_thickness_x * 2, this.textOffsetY + half_thickness_y * 2, half_thickness_x * 2, this._height - half_thickness_y * 2); this._graphics.endFill(); } public getTextShapeForIdentifierAndFormat(id: string, format: TextFormat) { if (this.textShapes[id]) { return this.textShapes[id]; } return (this.textShapes[id] = new TextShape(format, id)); } /** * When set to <code>true</code> and the text field is not in focus, Flash * Player highlights the selection in the text field in gray. When set to * <code>false</code> and the text field is not in focus, Flash Player does * not highlight the selection in the text field. * * @default false */ public alwaysShowSelection: boolean; /** * The type of anti-aliasing used for this text field. Use * <code>flash.text.AntiAliasType</code> constants for this property. You can * control this setting only if the font is embedded(with the * <code>embedFonts</code> property set to <code>true</code>). The default * setting is <code>flash.text.AntiAliasType.NORMAL</code>. * * <p>To set values for this property, use the following string values:</p> */ public antiAliasType: AntiAliasType; /** * Controls automatic sizing and alignment of text fields. Acceptable values * for the <code>TextFieldAutoSize</code> constants: * <code>TextFieldAutoSize.NONE</code>(the default), * <code>TextFieldAutoSize.LEFT</code>, <code>TextFieldAutoSize.RIGHT</code>, * and <code>TextFieldAutoSize.CENTER</code>. * * <p>If <code>autoSize</code> is set to <code>TextFieldAutoSize.NONE</code> * (the default) no resizing occurs.</p> * * <p>If <code>autoSize</code> is set to <code>TextFieldAutoSize.LEFT</code>, * the text is treated as left-justified text, meaning that the left margin * of the text field remains fixed and any resizing of a single line of the * text field is on the right margin. If the text includes a line break(for * example, <code>"\n"</code> or <code>"\r"</code>), the bottom is also * resized to fit the next line of text. If <code>wordWrap</code> is also set * to <code>true</code>, only the bottom of the text field is resized and the * right side remains fixed.</p> * * <p>If <code>autoSize</code> is set to * <code>TextFieldAutoSize.RIGHT</code>, the text is treated as * right-justified text, meaning that the right margin of the text field * remains fixed and any resizing of a single line of the text field is on * the left margin. If the text includes a line break(for example, * <code>"\n" or "\r")</code>, the bottom is also resized to fit the next * line of text. If <code>wordWrap</code> is also set to <code>true</code>, * only the bottom of the text field is resized and the left side remains * fixed.</p> * * <p>If <code>autoSize</code> is set to * <code>TextFieldAutoSize.CENTER</code>, the text is treated as * center-justified text, meaning that any resizing of a single line of the * text field is equally distributed to both the right and left margins. If * the text includes a line break(for example, <code>"\n"</code> or * <code>"\r"</code>), the bottom is also resized to fit the next line of * text. If <code>wordWrap</code> is also set to <code>true</code>, only the * bottom of the text field is resized and the left and right sides remain * fixed.</p> * * @throws ArgumentError The <code>autoSize</code> specified is not a member * of flash.text.TextFieldAutoSize. */ private _autoSize: string; public get autoSize(): string { return this._autoSize; } public set autoSize(value: string) { if (this._autoSize == value) return; if (typeof value === 'string') { if (value != TextFieldAutoSize.CENTER && value != TextFieldAutoSize.NONE && value != TextFieldAutoSize.LEFT && value != TextFieldAutoSize.RIGHT) { return; } } else { if (typeof value === 'boolean') { if (value) value = TextFieldAutoSize.LEFT; else value = TextFieldAutoSize.NONE; } if (typeof value === 'number') { if (value > 0) value = TextFieldAutoSize.LEFT; else value = TextFieldAutoSize.NONE; } } this._autoSize = value; this._positionsDirty = true; if (this._autoSize != TextFieldAutoSize.NONE) this.invalidate(); } private _internalScale: Vector3D = new Vector3D(1,1,1); public get internalScale(): Vector3D { return this._internalScale; } /** * * @returns {string} */ public get assetType(): string { return TextField.assetType; } /** * Specifies whether the text field has a background fill. If * <code>true</code>, the text field has a background fill. If * <code>false</code>, the text field has no background fill. Use the * <code>backgroundColor</code> property to set the background color of a * text field. * * @default false */ private _background: boolean; public get background(): boolean { return this._background; } public set background(value: boolean) { if (this._background == value) return; this._background = value; this._shapesDirty = true; } /** * The color of the text field background. The default value is * <code>0xFFFFFF</code>(white). This property can be retrieved or set, even * if there currently is no background, but the color is visible only if the * text field has the <code>background</code> property set to * <code>true</code>. */ private _backgroundColor: number /*int*/; public get backgroundColor(): number { return this._backgroundColor; } public set backgroundColor(value: number) { this._backgroundColor = value; this._shapesDirty = true; } /** * Specifies whether the text field has a border. If <code>true</code>, the * text field has a border. If <code>false</code>, the text field has no * border. Use the <code>borderColor</code> property to set the border color. * * @default false */ private _border: boolean; public get border(): boolean { return this._border; } public set border(value: boolean) { if (value == this._border) return; this._border = value; this._shapesDirty = true; } /** * The color of the text field border. The default value is * <code>0x000000</code>(black). This property can be retrieved or set, even * if there currently is no border, but the color is visible only if the text * field has the <code>border</code> property set to <code>true</code>. */ private _borderColor: number /*int*/; public get borderColor(): number { return this._borderColor; } public set borderColor(value: number) { if (value == this.borderColor) return; this._borderColor = value; this._shapesDirty = true; } /** * An integer(1-based index) that indicates the bottommost line that is * currently visible in the specified text field. Think of the text field as * a window onto a block of text. The <code>scrollV</code> property is the * 1-based index of the topmost visible line in the window. * * <p>All the text between the lines indicated by <code>scrollV</code> and * <code>bottomScrollV</code> is currently visible in the text field.</p> */ public get bottomScrollV(): number /*int*/ { return this._bottomScrollV; } public set bottomScrollV(value: number) /*int*/ { if (value == this._bottomScrollV) return; this._bottomScrollV = value; } /** * The index of the insertion point(caret) position. If no insertion point * is displayed, the value is the position the insertion point would be if * you restored focus to the field(typically where the insertion point last * was, or 0 if the field has not had focus). * * <p>Selection span indexes are zero-based(for example, the first position * is 0, the second position is 1, and so on).</p> */ public get caretIndex(): number /*int*/ { return this._caretIndex; } /** * A Boolean value that specifies whether extra white space(spaces, line * breaks, and so on) in a text field with HTML text is removed. The default * value is <code>false</code>. The <code>condenseWhite</code> property only * affects text set with the <code>htmlText</code> property, not the * <code>text</code> property. If you set text with the <code>text</code> * property, <code>condenseWhite</code> is ignored. * * <p>If <code>condenseWhite</code> is set to <code>true</code>, use standard * HTML commands such as <code><BR></code> and <code><P></code> to place line * breaks in the text field.</p> * * <p>Set the <code>condenseWhite</code> property before setting the * <code>htmlText</code> property.</p> */ public condenseWhite: boolean; /** * Specifies the format applied to newly inserted text, such as text entered * by a user or text inserted with the <code>replaceSelectedText()</code> * method. * * <p><b>Note:</b> When selecting characters to be replaced with * <code>setSelection()</code> and <code>replaceSelectedText()</code>, the * <code>defaultTextFormat</code> will be applied only if the text has been * selected up to and including the last character. Here is an example:</p> * <pre xml:space="preserve"> public my_txt:TextField new TextField(); * my_txt.text = "Flash Macintosh version"; public my_fmt:TextFormat = new * TextFormat(); my_fmt.color = 0xFF0000; my_txt.defaultTextFormat = my_fmt; * my_txt.setSelection(6,15); // partial text selected - defaultTextFormat * not applied my_txt.setSelection(6,23); // text selected to end - * defaultTextFormat applied my_txt.replaceSelectedText("Windows version"); * </pre> * * <p>When you access the <code>defaultTextFormat</code> property, the * returned TextFormat object has all of its properties defined. No property * is <code>null</code>.</p> * * <p><b>Note:</b> You can't set this property if a style sheet is applied to * the text field.</p> * * @throws Error This method cannot be used on a text field with a style * sheet. */ // CODE BELOW IS NOT IN USE! Instead of defaultTextFormat away only use textFormat. // Take a look into get/set defaultTextFormat in playerglobal/lib/text/TextField.ts // public _defaultTextFormat: TextFormat; // public get defaultTextFormat(): TextFormat { // } // public set defaultTextFormat(value: TextFormat) { // } /** * Specifies whether the text field is a password text field. If the value of * this property is <code>true</code>, the text field is treated as a * password text field and hides the input characters using asterisks instead * of the actual characters. If <code>false</code>, the text field is not * treated as a password text field. When password mode is enabled, the Cut * and Copy commands and their corresponding keyboard shortcuts will not * function. This security mechanism prevents an unscrupulous user from using * the shortcuts to discover a password on an unattended computer. * * @default false */ public displayAsPassword: boolean; /** * Specifies whether to render by using embedded font outlines. If * <code>false</code>, Flash Player renders the text field by using device * fonts. * * <p>If you set the <code>embedFonts</code> property to <code>true</code> * for a text field, you must specify a font for that text by using the * <code>font</code> property of a TextFormat object applied to the text * field. If the specified font is not embedded in the SWF file, the text is * not displayed.</p> * * @default false */ public embedFonts: boolean; /** * The type of grid fitting used for this text field. This property applies * only if the <code>flash.text.AntiAliasType</code> property of the text * field is set to <code>flash.text.AntiAliasType.ADVANCED</code>. * * <p>The type of grid fitting used determines whether Flash Player forces * strong horizontal and vertical lines to fit to a pixel or subpixel grid, * or not at all.</p> * * <p>For the <code>flash.text.GridFitType</code> property, you can use the * following string values:</p> * * @default pixel */ public gridFitType: GridFitType; /** * */ public get height(): number { if (this._autoSize != TextFieldAutoSize.NONE) this.reConstruct(); return this._height; } public set height(val: number) { if (this._height == val) return; if (this._autoSize != TextFieldAutoSize.NONE) return; this._height = val; this._positionsDirty = true; this.invalidate(); } /** * Contains the HTML representation of the text field contents. * * <p>Flash Player supports the following HTML tags:</p> * * <p>Flash Player and AIR also support explicit character codes, such as * &#38;(ASCII ampersand) and &#x20AC;(Unicode € symbol). </p> */ private _htmlText: string; public get htmlText(): string { return this._htmlText; } public set htmlText(value: string) { value = (value == undefined) ? '' : value.toString(); if (value == this._htmlText) return; this._htmlText = value; const processedText = HTMLTextProcessor.get().processHTML(this, value); // text might be the same, // we still need to set textDirty, because formatting might have changed //console.log("html out", textProps.text); this._labelData = null; this._text = processedText; this._iText = processedText; this._iTextWoLineBreaks = processedText.replace(/(\r\n|\n|\\n|\r)/gm,''); this._textDirty = true; //console.log("set text", value, "on" , this); this.invalidate(); } /** * The number of characters in a text field. A character such as tab * (<code>\t</code>) counts as one character. */ public get length(): number /*int*/ { return this._iText.length; } /** * The maximum number of characters that the text field can contain, as * entered by a user. A script can insert more text than * <code>maxChars</code> allows; the <code>maxChars</code> property indicates * only how much text a user can enter. If the value of this property is * <code>0</code>, a user can enter an unlimited amount of text. * * @default 0 */ public maxChars: number /*int*/; /** * The maximum value of <code>scrollH</code>. */ public get maxScrollH(): number /*int*/ { this.reConstruct(); return this._maxScrollH; } /** * The maximum value of <code>scrollV</code>. */ public get maxScrollV(): number /*int*/ { this.reConstruct(); return this._maxScrollV; } /** * A Boolean value that indicates whether Flash Player automatically scrolls * multiline text fields when the user clicks a text field and rolls the * mouse wheel. By default, this value is <code>true</code>. This property is * useful if you want to prevent mouse wheel scrolling of text fields, or * implement your own text field scrolling. */ public mouseWheelEnabled: boolean; /** * Indicates whether field is a multiline text field. If the value is * <code>true</code>, the text field is multiline; if the value is * <code>false</code>, the text field is a single-line text field. In a field * of type <code>TextFieldType.INPUT</code>, the <code>multiline</code> value * determines whether the <code>Enter</code> key creates a new line(a value * of <code>false</code>, and the <code>Enter</code> key is ignored). If you * paste text into a <code>TextField</code> with a <code>multiline</code> * value of <code>false</code>, newlines are stripped out of the text. * * @default false */ public multiline: boolean; /** * Defines the number of text lines in a multiline text field. If * <code>wordWrap</code> property is set to <code>true</code>, the number of * lines increases when text wraps. */ public get numLines(): number /*int*/ { this.reConstruct(); return this._numLines; } /** * Indicates the set of characters that a user can enter into the text field. * If the value of the <code>restrict</code> property is <code>null</code>, * you can enter any character. If the value of the <code>restrict</code> * property is an empty string, you cannot enter any character. If the value * of the <code>restrict</code> property is a string of characters, you can * enter only characters in the string into the text field. The string is * scanned from left to right. You can specify a range by using the hyphen * (-) character. Only user interaction is restricted; a script can put any * text into the text field. <ph outputclass="flashonly">This property does * not synchronize with the Embed font options in the Property inspector. * * <p>If the string begins with a caret(^) character, all characters are * initially accepted and succeeding characters in the string are excluded * from the set of accepted characters. If the string does not begin with a * caret(^) character, no characters are initially accepted and succeeding * characters in the string are included in the set of accepted * characters.</p> * * <p>The following example allows only uppercase characters, spaces, and * numbers to be entered into a text field:</p> * <pre xml:space="preserve"> my_txt.restrict = "A-Z 0-9"; </pre> * * <p>The following example includes all characters, but excludes lowercase * letters:</p> * <pre xml:space="preserve"> my_txt.restrict = "^a-z"; </pre> * * <p>You can use a backslash to enter a ^ or - verbatim. The accepted * backslash sequences are \-, \^ or \\. The backslash must be an actual * character in the string, so when specified in ActionScript, a double * backslash must be used. For example, the following code includes only the * dash(-) and caret(^):</p> * <pre xml:space="preserve"> my_txt.restrict = "\\-\\^"; </pre> * * <p>The ^ can be used anywhere in the string to toggle between including * characters and excluding characters. The following code includes only * uppercase letters, but excludes the uppercase letter Q:</p> * <pre xml:space="preserve"> my_txt.restrict = "A-Z^Q"; </pre> * * <p>You can use the <code>\u</code> escape sequence to construct * <code>restrict</code> strings. The following code includes only the * characters from ASCII 32(space) to ASCII 126(tilde).</p> * <pre xml:space="preserve"> my_txt.restrict = "\u0020-\u007E"; </pre> * * @default null */ public _restrict: string; public _restrictRegex: RegExp; public get restrict(): string { return this._restrict; } public set restrict(value: string) { if (value == this._restrict) return; this._restrict = value; this._restrictRegex = null; if (typeof value == 'undefined') return; value = value.toString(); // flash allows something like -9 to be used instaed 0-9. fix this here: if (value.length >= 2 && value[0] == '-' && !isNaN(parseInt(value[1]))) value = '0' + value; // remove all backslashes. flash does not allow to use backslash as allowed char value = value.replace(/\\/g, ''); // remove all ^. flash does not allow to use ^ as allowed char value = value.replace(/\^/g, ''); // make sure all "-" are escaped if they are not used to define a range // eslint-disable-next-line no-useless-escape value = value.replace(/([^a-zA-Z0-9])\-/g, '$1\\-'); // escape all special chars so that regex will be valid //todo: should be able to do the following with a single regex: value = value.replace(/\./g, '\\.'); // eslint-disable-next-line no-useless-escape value = value.replace(/\</g, '\\<'); // eslint-disable-next-line no-useless-escape value = value.replace(/\>/g, '\\>'); value = value.replace(/\+/g, '\\+'); value = value.replace(/\*/g, '\\*'); value = value.replace(/\?/g, '\\?'); value = value.replace(/\[/g, '\\['); value = value.replace(/\]/g, '\\]'); value = value.replace(/\$/g, '\\$'); value = value.replace(/\(/g, '\\('); value = value.replace(/\)/g, '\\)'); value = value.replace(/\{/g, '\\{'); value = value.replace(/\}/g, '\\}'); // eslint-disable-next-line no-useless-escape value = value.replace(/\=/g, '\\='); // eslint-disable-next-line no-useless-escape value = value.replace(/\!/g, '\\!'); // eslint-disable-next-line no-useless-escape value = value.replace(/\:/g, '\\:'); value = value.replace(/\|/g, '\\|'); value = value.replace(/\//g, '\\/'); // eslint-disable-next-line no-useless-escape value = value.replace(/\%/g, '\\%'); this._restrictRegex = new RegExp('[^' + value + ']', 'g'); } /** * The current horizontal scrolling position. If the <code>scrollH</code> * property is 0, the text is not horizontally scrolled. This property value * is an integer that represents the horizontal position in pixels. * * <p>The units of horizontal scrolling are pixels, whereas the units of * vertical scrolling are lines. Horizontal scrolling is measured in pixels * because most fonts you typically use are proportionally spaced; that is, * the characters can have different widths. Flash Player performs vertical * scrolling by line because users usually want to see a complete line of * text rather than a partial line. Even if a line uses multiple fonts, the * height of the line adjusts to fit the largest font in use.</p> * * <p><b>Note: </b>The <code>scrollH</code> property is zero-based, not * 1-based like the <code>scrollV</code> vertical scrolling property.</p> */ private _scrollH: number; public get scrollH(): number /*int*/ { return this._scrollH; } public set scrollH(value: number) /*int*/ { if (value == this._scrollH) return; this._scrollH = value; } /** * The vertical position of text in a text field. The <code>scrollV</code> * property is useful for directing users to a specific paragraph in a long * passage, or creating scrolling text fields. * * <p>The units of vertical scrolling are lines, whereas the units of * horizontal scrolling are pixels. If the first line displayed is the first * line in the text field, scrollV is set to 1(not 0). Horizontal scrolling * is measured in pixels because most fonts are proportionally spaced; that * is, the characters can have different widths. Flash performs vertical * scrolling by line because users usually want to see a complete line of * text rather than a partial line. Even if there are multiple fonts on a * line, the height of the line adjusts to fit the largest font in use.</p> */ public _scrollV: number; public get scrollV(): number /*int*/ { return this._scrollV; } public set scrollV(value: number) /*int*/ { const rounded = Math.round(value); if (rounded === this._scrollV) return; this._scrollV = rounded; if (this._scrollV > this._maxScrollV) this._scrollV = this._maxScrollV; if (this._scrollV <= 0) { this._scrollV = 0; } if (!this.textChild) { return; } // unsafe this.textChild.y = -this.lines_start_y[this._scrollV]; } /** * A Boolean value that indicates whether the text field is selectable. The * value <code>true</code> indicates that the text is selectable. The * <code>selectable</code> property controls whether a text field is * selectable, not whether a text field is editable. A dynamic text field can * be selectable even if it is not editable. If a dynamic text field is not * selectable, the user cannot select its text. * * <p>If <code>selectable</code> is set to <code>false</code>, the text in * the text field does not respond to selection commands from the mouse or * keyboard, and the text cannot be copied with the Copy command. If * <code>selectable</code> is set to <code>true</code>, the text in the text * field can be selected with the mouse or keyboard, and the text can be * copied with the Copy command. You can select text this way even if the * text field is a dynamic text field instead of an input text field. </p> * * @default true */ private _selectable: boolean; public get selectable(): boolean { return this._selectable; } public set selectable(value: boolean) { if (this.selectable == value) { return; } this._selectable = value; this.mouseEnabled = value; this.cursorType = value ? 'text' : ''; if (value) { this.addEventListener(MouseEvent.DRAG_START, this.startSelectionByMouseDelegate); this.addEventListener(MouseEvent.DRAG_STOP, this.stopSelectionByMouseDelegate); this.addEventListener(MouseEvent.DRAG_MOVE, this.updateSelectionByMouseDelegate); } else { this.removeEventListener(MouseEvent.DRAG_START, this.startSelectionByMouseDelegate); this.removeEventListener(MouseEvent.DRAG_STOP, this.stopSelectionByMouseDelegate); this.removeEventListener(MouseEvent.DRAG_MOVE, this.updateSelectionByMouseDelegate); } } /** * The zero-based character index value of the first character in the current * selection. For example, the first character is 0, the second character is * 1, and so on. If no text is selected, this property is the value of * <code>caretIndex</code>. */ public get selectionBeginIndex(): number /*int*/ { return this._selectionBeginIndex; } /** * The zero-based character index value of the last character in the current * selection. For example, the first character is 0, the second character is * 1, and so on. If no text is selected, this property is the value of * <code>caretIndex</code>. */ public get selectionEndIndex(): number /*int*/ { return this._selectionEndIndex; } /** * The sharpness of the glyph edges in this text field. This property applies * only if the <code>flash.text.AntiAliasType</code> property of the text * field is set to <code>flash.text.AntiAliasType.ADVANCED</code>. The range * for <code>sharpness</code> is a number from -400 to 400. If you attempt to * set <code>sharpness</code> to a value outside that range, Flash sets the * property to the nearest value in the range(either -400 or 400). * * @default 0 */ public sharpness: n