textBlock.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /// <reference path="../../../dist/preview release/babylon.d.ts"/>
  2. module BABYLON.GUI {
  3. export class TextBlock extends Control {
  4. private _text = "";
  5. private _textWrapping = false;
  6. private _textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
  7. private _textVerticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
  8. private _lines: any[];
  9. private _resizeToFit: boolean = false;
  10. private _lineSpacing: ValueAndUnit = new ValueAndUnit(0);
  11. /**
  12. * An event triggered after the text is changed
  13. * @type {BABYLON.Observable}
  14. */
  15. public onTextChangedObservable = new Observable<TextBlock>();
  16. /**
  17. * An event triggered after the text was broken up into lines
  18. * @type {BABYLON.Observable}
  19. */
  20. public onLinesReadyObservable = new Observable<TextBlock>();
  21. /**
  22. * Return the line list (you may need to use the onLinesReadyObservable to make sure the list is ready)
  23. */
  24. public get lines(): any[] {
  25. return this._lines;
  26. }
  27. /**
  28. * Gets or sets an boolean indicating that the TextBlock will be resized to fit container
  29. */
  30. public get resizeToFit(): boolean {
  31. return this._resizeToFit;
  32. }
  33. /**
  34. * Gets or sets an boolean indicating that the TextBlock will be resized to fit container
  35. */
  36. public set resizeToFit(value: boolean) {
  37. this._resizeToFit = value;
  38. if (this._resizeToFit) {
  39. this._width.ignoreAdaptiveScaling = true;
  40. this._height.ignoreAdaptiveScaling = true;
  41. }
  42. }
  43. /**
  44. * Gets or sets a boolean indicating if text must be wrapped
  45. */
  46. public get textWrapping(): boolean {
  47. return this._textWrapping;
  48. }
  49. /**
  50. * Gets or sets a boolean indicating if text must be wrapped
  51. */
  52. public set textWrapping(value: boolean) {
  53. if (this._textWrapping === value) {
  54. return;
  55. }
  56. this._textWrapping = value;
  57. this._markAsDirty();
  58. }
  59. /**
  60. * Gets or sets text to display
  61. */
  62. public get text(): string {
  63. return this._text;
  64. }
  65. /**
  66. * Gets or sets text to display
  67. */
  68. public set text(value: string) {
  69. if (this._text === value) {
  70. return;
  71. }
  72. this._text = value;
  73. this._markAsDirty();
  74. this.onTextChangedObservable.notifyObservers(this);
  75. }
  76. /**
  77. * Gets or sets text horizontal alignment (BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER by default)
  78. */
  79. public get textHorizontalAlignment(): number {
  80. return this._textHorizontalAlignment;
  81. }
  82. /**
  83. * Gets or sets text horizontal alignment (BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER by default)
  84. */
  85. public set textHorizontalAlignment(value: number) {
  86. if (this._textHorizontalAlignment === value) {
  87. return;
  88. }
  89. this._textHorizontalAlignment = value;
  90. this._markAsDirty();
  91. }
  92. /**
  93. * Gets or sets text vertical alignment (BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER by default)
  94. */
  95. public get textVerticalAlignment(): number {
  96. return this._textVerticalAlignment;
  97. }
  98. /**
  99. * Gets or sets text vertical alignment (BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER by default)
  100. */
  101. public set textVerticalAlignment(value: number) {
  102. if (this._textVerticalAlignment === value) {
  103. return;
  104. }
  105. this._textVerticalAlignment = value;
  106. this._markAsDirty();
  107. }
  108. /**
  109. * Gets or sets line spacing value
  110. */
  111. public set lineSpacing(value: string | number) {
  112. if (this._lineSpacing.fromString(value)) {
  113. this._markAsDirty();
  114. }
  115. }
  116. /**
  117. * Gets or sets line spacing value
  118. */
  119. public get lineSpacing(): string | number {
  120. return this._lineSpacing.toString(this._host);
  121. }
  122. /**
  123. * Creates a new TextBlock object
  124. * @param name defines the name of the control
  125. * @param text defines the text to display (emptry string by default)
  126. */
  127. constructor(
  128. /**
  129. * Defines the name of the control
  130. */
  131. public name?: string,
  132. text: string = "") {
  133. super(name);
  134. this.text = text;
  135. }
  136. protected _getTypeName(): string {
  137. return "TextBlock";
  138. }
  139. private _drawText(text: string, textWidth: number, y: number, context: CanvasRenderingContext2D): void {
  140. var width = this._currentMeasure.width;
  141. var x = 0;
  142. switch (this._textHorizontalAlignment) {
  143. case Control.HORIZONTAL_ALIGNMENT_LEFT:
  144. x = 0
  145. break;
  146. case Control.HORIZONTAL_ALIGNMENT_RIGHT:
  147. x = width - textWidth;
  148. break;
  149. case Control.HORIZONTAL_ALIGNMENT_CENTER:
  150. x = (width - textWidth) / 2;
  151. break;
  152. }
  153. if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
  154. context.shadowColor = this.shadowColor;
  155. context.shadowBlur = this.shadowBlur;
  156. context.shadowOffsetX = this.shadowOffsetX;
  157. context.shadowOffsetY = this.shadowOffsetY;
  158. }
  159. context.fillText(text, this._currentMeasure.left + x, y);
  160. }
  161. /** @ignore */
  162. public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  163. context.save();
  164. this._applyStates(context);
  165. if (this._processMeasures(parentMeasure, context)) {
  166. // Render lines
  167. this._renderLines(context);
  168. }
  169. context.restore();
  170. }
  171. protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  172. this._lines = [];
  173. var _lines = this.text.split("\n");
  174. if (this._textWrapping && !this._resizeToFit) {
  175. for (var _line of _lines) {
  176. this._lines.push(this._parseLineWithTextWrapping(_line, context));
  177. }
  178. } else {
  179. for (var _line of _lines) {
  180. this._lines.push(this._parseLine(_line, context));
  181. }
  182. }
  183. this.onLinesReadyObservable.notifyObservers(this);
  184. }
  185. protected _parseLine(line: string = '', context: CanvasRenderingContext2D): object {
  186. return { text: line, width: context.measureText(line).width };
  187. }
  188. protected _parseLineWithTextWrapping(line: string = '', context: CanvasRenderingContext2D): object {
  189. var words = line.split(' ');
  190. var width = this._currentMeasure.width;
  191. var lineWidth = 0;
  192. for (var n = 0; n < words.length; n++) {
  193. var testLine = n > 0 ? line + " " + words[n] : words[0];
  194. var metrics = context.measureText(testLine);
  195. var testWidth = metrics.width;
  196. if (testWidth > width && n > 0) {
  197. this._lines.push({ text: line, width: lineWidth });
  198. line = words[n];
  199. lineWidth = context.measureText(line).width;
  200. }
  201. else {
  202. lineWidth = testWidth;
  203. line = testLine;
  204. }
  205. }
  206. return { text: line, width: lineWidth };
  207. }
  208. protected _renderLines(context: CanvasRenderingContext2D): void {
  209. var height = this._currentMeasure.height;
  210. if (!this._fontOffset) {
  211. this._fontOffset = Control._GetFontOffset(context.font);
  212. }
  213. var rootY = 0;
  214. switch (this._textVerticalAlignment) {
  215. case Control.VERTICAL_ALIGNMENT_TOP:
  216. rootY = this._fontOffset.ascent;
  217. break;
  218. case Control.VERTICAL_ALIGNMENT_BOTTOM:
  219. rootY = height - this._fontOffset.height * (this._lines.length - 1) - this._fontOffset.descent;
  220. break;
  221. case Control.VERTICAL_ALIGNMENT_CENTER:
  222. rootY = this._fontOffset.ascent + (height - this._fontOffset.height * this._lines.length) / 2;
  223. break;
  224. }
  225. rootY += this._currentMeasure.top;
  226. var maxLineWidth: number = 0;
  227. for (let i = 0; i < this._lines.length; i++) {
  228. const line = this._lines[i];
  229. if (i !== 0 && this._lineSpacing.internalValue !== 0) {
  230. if (this._lineSpacing.isPixel) {
  231. rootY += this._lineSpacing.getValue(this._host);
  232. } else {
  233. rootY = rootY + (this._lineSpacing.getValue(this._host) * this._height.getValueInPixel(this._host, this._cachedParentMeasure.height));
  234. }
  235. }
  236. this._drawText(line.text, line.width, rootY, context);
  237. rootY += this._fontOffset.height;
  238. if (line.width > maxLineWidth) maxLineWidth = line.width;
  239. }
  240. if (this._resizeToFit) {
  241. this.width = this.paddingLeftInPixels + this.paddingRightInPixels + maxLineWidth + 'px';
  242. this.height = this.paddingTopInPixels + this.paddingBottomInPixels + this._fontOffset.height * this._lines.length + 'px';
  243. }
  244. }
  245. dispose(): void {
  246. super.dispose();
  247. this.onTextChangedObservable.clear();
  248. }
  249. }
  250. }