@awayjs/scene
Version:
AwayJS scene classes
1,468 lines (1,262 loc) • 126 kB
text/typescript
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: /'/g,
replace: '\''
},
{
test: />/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
* &(ASCII ampersand) and €(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