-
-
Save Patbox/0efdf0c6684938dc85bd4e23f760bad2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Modified copy of class TextBlock from BABYLON.JS | |
* https://github.com/BabylonJS/Babylon.js/blob/master/gui/src/2D/controls/textBlock.ts | |
*/ | |
import { Observable } from '@babylonjs/core/Misc/observable'; | |
import { Measure } from '@babylonjs/gui/2D/measure'; | |
import { ValueAndUnit } from '@babylonjs/gui/2D/valueAndUnit'; | |
import { Control } from '@babylonjs/gui/2D/controls/control'; | |
import { _TypeStore } from '@babylonjs/core/Misc/typeStore'; | |
import { Nullable } from '@babylonjs/core/types'; | |
export interface IFormatedText { | |
text: string; | |
color?: string; | |
font?: string; | |
} | |
/** | |
* Class used to create text block control | |
*/ | |
export class FormTextBlock extends Control { | |
private _text: Array<IFormatedText> = []; | |
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'; | |
public shouldhide: boolean = false; | |
/** | |
* An event triggered after the text is changed | |
*/ | |
public onTextChangedObservable = new Observable<FormTextBlock>(); | |
/** | |
* An event triggered after the text was broken up into lines | |
*/ | |
public onLinesReadyObservable = new Observable<FormTextBlock>(); | |
/** | |
* Function used to split a string into words. By default, a string is split at each space character found | |
*/ | |
public wordSplittingFunction: Nullable<(line: string) => string[]>; | |
/** | |
* 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) { | |
if (this._resizeToFit === value) { | |
return; | |
} | |
this._resizeToFit = value; | |
if (this._resizeToFit) { | |
this._width.ignoreAdaptiveScaling = true; | |
this._height.ignoreAdaptiveScaling = true; | |
} | |
this._markAsDirty(); | |
} | |
/** | |
* Gets or sets text to display | |
*/ | |
public get text(): Array<IFormatedText> { | |
return this._text; | |
} | |
/** | |
* Gets or sets text to display | |
*/ | |
public set text(value: Array<IFormatedText>) { | |
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: Array<IFormatedText> = [] | |
) { | |
super(name); | |
this.text = text; | |
} | |
protected _getTypeName(): string { | |
return 'FormTextBlock'; | |
} | |
protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void { | |
if (!this._fontOffset) { | |
this._fontOffset = Control._GetFontOffset(context.font); | |
} | |
super._processMeasures(parentMeasure, context); | |
// Prepare lines | |
this._lines = this._breakLines(this._currentMeasure.width, context); | |
this.onLinesReadyObservable.notifyObservers(this); | |
let maxLineWidth: number = 0; | |
for (let i = 0; i < this._lines.length; i++) { | |
const line = this._lines[i]; | |
if (line.width > maxLineWidth) { | |
maxLineWidth = line.width; | |
} | |
} | |
if (this._resizeToFit) { | |
let newHeight = (this.paddingTopInPixels + this.paddingBottomInPixels + this._fontOffset.height * this._lines.length) | 0; | |
if (this._lines.length > 0 && this._lineSpacing.internalValue !== 0) { | |
let lineSpacing = 0; | |
if (this._lineSpacing.isPixel) { | |
lineSpacing = this._lineSpacing.getValue(this._host); | |
} else { | |
lineSpacing = this._lineSpacing.getValue(this._host) * this._height.getValueInPixel(this._host, this._cachedParentMeasure.height); | |
} | |
newHeight += (this._lines.length - 1) * lineSpacing; | |
} | |
if (newHeight !== this._height.internalValue) { | |
this._height.updateInPlace(newHeight, ValueAndUnit.UNITMODE_PIXEL); | |
this._rebuildLayout = true; | |
} | |
} | |
} | |
private _drawText(text: Array<IFormatedText>, 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); | |
} | |
fillMixedText(context, text, this._currentMeasure.left + x, y, this); | |
} | |
/** @hidden */ | |
public _draw(context: CanvasRenderingContext2D, invalidatedRectangle?: Nullable<Measure>): void { | |
context.save(); | |
this._applyStates(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 _breakLines(refWidth: number, context: CanvasRenderingContext2D): object[] { | |
var lines = []; | |
let textList: Array<Array<IFormatedText>> = [[]]; | |
this.text.forEach((val) => { | |
let x = val.text.split('\n'); | |
if (x.length > 1) { | |
textList[textList.length - 1].push({ text: x[0], color: val.color, font: val.font }); | |
for (let y = 1; y < x.length; y++) { | |
textList.push([{ text: x[y], color: val.color, font: val.font }]); | |
} | |
} else textList[textList.length - 1].push(val); | |
}); | |
for (var _line of textList) { | |
lines.push(...this._parseLineWordWrap(_line, refWidth, context)); | |
} | |
return lines; | |
} | |
protected _parseLine(line: string = '', context: CanvasRenderingContext2D): object { | |
return { text: line, width: context.measureText(line).width }; | |
} | |
protected _parseLineWordWrap(line: Array<IFormatedText> = [], width: number, context: CanvasRenderingContext2D): object[] { | |
var lines = []; | |
var words = []; | |
var textOnly = ''; | |
let defaultFont = this.fontFamily; | |
line.forEach((val) => { | |
if (val.font == undefined) val.font = defaultFont; | |
let localWords = val.text.split(' '); | |
localWords.forEach((x) => { | |
if (x.length == 0) return; | |
context.font = `${this.fontSize} ${val.font}` | |
let metrics = context.measureText(x); | |
context.font = defaultFont; | |
if (metrics.width < width) words.push({ text: x + ' ', font: val.font, color: val.color }); | |
else { | |
const s1 = x.substring(0, Math.ceil(x.length / 2)); | |
const s2 = x.substring(Math.floor(x.length / 2)); | |
context.font = `${this.fontSize} ${val.font}` | |
metrics = context.measureText(s1); | |
let metrics2 = context.measureText(s2); | |
context.font = defaultFont; | |
if (metrics.width < width) words.push({ text: s1 + ' ', font: val.font, color: val.color }); | |
else words.push({ text: ' [...] ', font: val.font, color: val.color }); | |
if (metrics2.width < width) words.push({ text: s2 + ' ', font: val.font, color: val.color }); | |
else words.push({ text: ' [...] ', font: val.font, color: val.color }); | |
} | |
}); | |
textOnly = textOnly + val.text; | |
}); | |
var lineWidth = 0; | |
context.font = `${this.fontSize} ${line[0].font}` | |
let metrics = context.measureText(textOnly); | |
context.font = defaultFont; | |
if (metrics.width < width) { | |
return [{ text: line, width: lineWidth }]; | |
} | |
let lastText = ''; | |
let lastWidth = 0; | |
let formatted: Array<IFormatedText> = []; | |
for (var n = 0; n < words.length; n++) { | |
let tempText = n == 0 ? words[0].text : lastText + words[n].text; | |
context.font = `${this.fontSize} ${ words[n].font}` | |
let metrics = context.measureText(tempText); | |
context.font = defaultFont; | |
if (metrics.width > width) { | |
lines.push({ text: [...formatted], width: lastWidth }); | |
formatted = []; | |
tempText = ''; | |
lastWidth = 0; | |
for (let x = 0; x < n; x++) words.shift(); | |
n = -1; | |
} else { | |
lastText = tempText; | |
lastWidth = metrics.width; | |
formatted.push(words[n]); | |
} | |
} | |
lines.push({ text: [...formatted], width: lastWidth }); | |
return lines; | |
} | |
protected _renderLines(context: CanvasRenderingContext2D): void { | |
var height = this._currentMeasure.height; | |
var rootY = 0; | |
var rootX = 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; | |
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, rootX, rootY, context); | |
rootY += this._fontOffset.height; | |
} | |
} | |
/** | |
* 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); | |
let newHeight = this.paddingTopInPixels + this.paddingBottomInPixels + this._fontOffset.height * lines.length; | |
if (lines.length > 0 && this._lineSpacing.internalValue !== 0) { | |
let lineSpacing = 0; | |
if (this._lineSpacing.isPixel) { | |
lineSpacing = this._lineSpacing.getValue(this._host); | |
} else { | |
lineSpacing = this._lineSpacing.getValue(this._host) * this._height.getValueInPixel(this._host, this._cachedParentMeasure.height); | |
} | |
newHeight += (lines.length - 1) * lineSpacing; | |
} | |
return newHeight; | |
} | |
} | |
return 0; | |
} | |
dispose(): void { | |
super.dispose(); | |
this.onTextChangedObservable.clear(); | |
} | |
} | |
const fillMixedText = (ctx: CanvasRenderingContext2D, arg: Array<IFormatedText>, x: number, y: number, self) => { | |
if (arg == undefined) return; | |
let defaultFillStyle = ctx.fillStyle; | |
let defaultFont = ctx.font; | |
ctx.save(); | |
arg.forEach((txt) => { | |
if (txt.text == undefined) return; | |
if (txt.font == undefined) txt.font = defaultFont; | |
ctx.font = `${self.fontSize} ${txt.font}` | |
ctx.fillStyle = txt.color || defaultFillStyle; | |
ctx.fillText(txt.text, x, y); | |
ctx.font = defaultFont; | |
x = x + ctx.measureText(txt.text).width; | |
}); | |
ctx.restore(); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment