/// module BABYLON.GUI { /** * Enum that determines the text-wrapping mode to use. */ export enum TextWrapping { /** * Clip the text when it's larger than Control.width; this is the default mode. */ Clip = 0, /** * Wrap the text word-wise, i.e. try to add line-breaks at word boundary to fit within Control.width. */ WordWrap = 1, /** * Ellipsize the text, i.e. shrink with trailing … when text is larger than Control.width. */ Ellipsis, } /** * Class used to create text block control */ export class TextBlock extends Control { private _text = ""; private _textWrapping = TextWrapping.Clip; private _textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER; private _textVerticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER; private _lines: any[]; private _resizeToFit: boolean = false; private _lineSpacing: ValueAndUnit = new ValueAndUnit(0); private _outlineWidth: number = 0; private _outlineColor: string = "white"; /** * An event triggered after the text is changed */ public onTextChangedObservable = new Observable(); /** * An event triggered after the text was broken up into lines */ public onLinesReadyObservable = new Observable(); /** * Return the line list (you may need to use the onLinesReadyObservable to make sure the list is ready) */ public get lines(): any[] { return this._lines; } /** * Gets or sets an boolean indicating that the TextBlock will be resized to fit container */ public get resizeToFit(): boolean { return this._resizeToFit; } /** * Gets or sets an boolean indicating that the TextBlock will be resized to fit container */ public set resizeToFit(value: boolean) { this._resizeToFit = value; if (this._resizeToFit) { this._width.ignoreAdaptiveScaling = true; this._height.ignoreAdaptiveScaling = true; } } /** * Gets or sets a boolean indicating if text must be wrapped */ public get textWrapping(): TextWrapping | boolean { return this._textWrapping; } /** * Gets or sets a boolean indicating if text must be wrapped */ public set textWrapping(value: TextWrapping | boolean) { if (this._textWrapping === value) { return; } this._textWrapping = +value; this._markAsDirty(); } /** * Gets or sets text to display */ public get text(): string { return this._text; } /** * Gets or sets text to display */ public set text(value: string) { if (this._text === value) { return; } this._text = value; this._markAsDirty(); this.onTextChangedObservable.notifyObservers(this); } /** * Gets or sets text horizontal alignment (BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER by default) */ public get textHorizontalAlignment(): number { return this._textHorizontalAlignment; } /** * Gets or sets text horizontal alignment (BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER by default) */ public set textHorizontalAlignment(value: number) { if (this._textHorizontalAlignment === value) { return; } this._textHorizontalAlignment = value; this._markAsDirty(); } /** * Gets or sets text vertical alignment (BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER by default) */ public get textVerticalAlignment(): number { return this._textVerticalAlignment; } /** * Gets or sets text vertical alignment (BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER by default) */ public set textVerticalAlignment(value: number) { if (this._textVerticalAlignment === value) { return; } this._textVerticalAlignment = value; this._markAsDirty(); } /** * Gets or sets line spacing value */ public set lineSpacing(value: string | number) { if (this._lineSpacing.fromString(value)) { this._markAsDirty(); } } /** * Gets or sets line spacing value */ public get lineSpacing(): string | number { return this._lineSpacing.toString(this._host); } /** * Gets or sets outlineWidth of the text to display */ public get outlineWidth(): number { return this._outlineWidth; } /** * Gets or sets outlineWidth of the text to display */ public set outlineWidth(value: number) { if (this._outlineWidth === value) { return; } this._outlineWidth = value; this._markAsDirty(); } /** * Gets or sets outlineColor of the text to display */ public get outlineColor(): string { return this._outlineColor; } /** * Gets or sets outlineColor of the text to display */ public set outlineColor(value: string) { if (this._outlineColor === value) { return; } this._outlineColor = value; this._markAsDirty(); } /** * Creates a new TextBlock object * @param name defines the name of the control * @param text defines the text to display (emptry string by default) */ constructor( /** * Defines the name of the control */ public name?: string, text: string = "") { super(name); this.text = text; } protected _getTypeName(): string { return "TextBlock"; } private _drawText(text: string, textWidth: number, y: number, context: CanvasRenderingContext2D): void { var width = this._currentMeasure.width; var x = 0; switch (this._textHorizontalAlignment) { case Control.HORIZONTAL_ALIGNMENT_LEFT: x = 0 break; case Control.HORIZONTAL_ALIGNMENT_RIGHT: x = width - textWidth; break; case Control.HORIZONTAL_ALIGNMENT_CENTER: x = (width - textWidth) / 2; break; } if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) { context.shadowColor = this.shadowColor; context.shadowBlur = this.shadowBlur; context.shadowOffsetX = this.shadowOffsetX; context.shadowOffsetY = this.shadowOffsetY; } if (this.outlineWidth) { context.strokeText(text, this._currentMeasure.left + x, y); } context.fillText(text, this._currentMeasure.left + x, y); } /** @hidden */ public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void { context.save(); this._applyStates(context); if (this._processMeasures(parentMeasure, context)) { // Render lines this._renderLines(context); } context.restore(); } protected _applyStates(context: CanvasRenderingContext2D): void { super._applyStates(context); if (this.outlineWidth) { context.lineWidth = this.outlineWidth; context.strokeStyle = this.outlineColor; } } protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void { this._lines = this._breakLines(this._currentMeasure.width, context); this.onLinesReadyObservable.notifyObservers(this); } protected _breakLines(refWidth: number, context: CanvasRenderingContext2D): object[] { var lines = []; var _lines = this.text.split("\n"); if (this._textWrapping === TextWrapping.Ellipsis && !this._resizeToFit) { for (var _line of _lines) { lines.push(this._parseLineEllipsis(_line, refWidth, context)); } } else if (this._textWrapping === TextWrapping.WordWrap && !this._resizeToFit) { for (var _line of _lines) { lines.push(...this._parseLineWordWrap(_line, refWidth, context)); } } else { for (var _line of _lines) { lines.push(this._parseLine(_line, context)); } } return lines; } protected _parseLine(line: string = '', context: CanvasRenderingContext2D): object { return { text: line, width: context.measureText(line).width }; } protected _parseLineEllipsis(line: string = '', width: number, context: CanvasRenderingContext2D): object { var lineWidth = context.measureText(line).width; if (lineWidth > width) { line += '…'; } while (line.length > 2 && lineWidth > width) { line = line.slice(0, -2) + '…'; lineWidth = context.measureText(line).width; } return { text: line, width: lineWidth }; } protected _parseLineWordWrap(line: string = '', width: number, context: CanvasRenderingContext2D): object[] { var lines = []; var words = line.split(' '); var lineWidth = 0; for (var n = 0; n < words.length; n++) { var testLine = n > 0 ? line + " " + words[n] : words[0]; var metrics = context.measureText(testLine); var testWidth = metrics.width; if (testWidth > width && n > 0) { lines.push({ text: line, width: lineWidth }); line = words[n]; lineWidth = context.measureText(line).width; } else { lineWidth = testWidth; line = testLine; } } lines.push({ text: line, width: lineWidth }); return lines; } protected _renderLines(context: CanvasRenderingContext2D): void { var height = this._currentMeasure.height; if (!this._fontOffset) { this._fontOffset = Control._GetFontOffset(context.font); } var rootY = 0; switch (this._textVerticalAlignment) { case Control.VERTICAL_ALIGNMENT_TOP: rootY = this._fontOffset.ascent; break; case Control.VERTICAL_ALIGNMENT_BOTTOM: rootY = height - this._fontOffset.height * (this._lines.length - 1) - this._fontOffset.descent; break; case Control.VERTICAL_ALIGNMENT_CENTER: rootY = this._fontOffset.ascent + (height - this._fontOffset.height * this._lines.length) / 2; break; } rootY += this._currentMeasure.top; var maxLineWidth: number = 0; for (let i = 0; i < this._lines.length; i++) { const line = this._lines[i]; if (i !== 0 && this._lineSpacing.internalValue !== 0) { if (this._lineSpacing.isPixel) { rootY += this._lineSpacing.getValue(this._host); } else { rootY = rootY + (this._lineSpacing.getValue(this._host) * this._height.getValueInPixel(this._host, this._cachedParentMeasure.height)); } } this._drawText(line.text, line.width, rootY, context); rootY += this._fontOffset.height; if (line.width > maxLineWidth) maxLineWidth = line.width; } if (this._resizeToFit) { this.width = this.paddingLeftInPixels + this.paddingRightInPixels + maxLineWidth + 'px'; this.height = this.paddingTopInPixels + this.paddingBottomInPixels + this._fontOffset.height * this._lines.length + 'px'; } } /** * Given a width constraint applied on the text block, find the expected height * @returns expected height */ public computeExpectedHeight(): number { if (this.text && this.widthInPixels) { const context = document.createElement('canvas').getContext('2d'); if (context) { this._applyStates(context); if (!this._fontOffset) { this._fontOffset = Control._GetFontOffset(context.font); } const lines = this._lines ? this._lines : this._breakLines( this.widthInPixels - this.paddingLeftInPixels - this.paddingRightInPixels, context); return this.paddingTopInPixels + this.paddingBottomInPixels + this._fontOffset.height * lines.length; } } return 0; } dispose(): void { super.dispose(); this.onTextChangedObservable.clear(); } } }