control.ts 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509
  1. import { Container } from "./container";
  2. import { AdvancedDynamicTexture } from "../advancedDynamicTexture";
  3. import { ValueAndUnit } from "../valueAndUnit";
  4. import { Nullable, Observer, Vector2, AbstractMesh, Observable, Vector3, Scene, Tools, Matrix, PointerEventTypes } from "babylonjs";
  5. import { Measure } from "../measure";
  6. import { Style } from "../style";
  7. import { Matrix2D, Vector2WithInfo } from "../math2D";
  8. /**
  9. * Root class used for all 2D controls
  10. * @see http://doc.babylonjs.com/how_to/gui#controls
  11. */
  12. export class Control {
  13. private _alpha = 1;
  14. private _alphaSet = false;
  15. private _zIndex = 0;
  16. /** @hidden */
  17. public _root: Nullable<Container>;
  18. /** @hidden */
  19. public _host: AdvancedDynamicTexture;
  20. /** Gets or sets the control parent */
  21. public parent: Nullable<Container>;
  22. /** @hidden */
  23. public _currentMeasure = Measure.Empty();
  24. private _fontFamily = "Arial";
  25. private _fontStyle = "";
  26. private _fontWeight = "";
  27. private _fontSize = new ValueAndUnit(18, ValueAndUnit.UNITMODE_PIXEL, false);
  28. private _font: string;
  29. /** @hidden */
  30. public _width = new ValueAndUnit(1, ValueAndUnit.UNITMODE_PERCENTAGE, false);
  31. /** @hidden */
  32. public _height = new ValueAndUnit(1, ValueAndUnit.UNITMODE_PERCENTAGE, false);
  33. /** @hidden */
  34. protected _fontOffset: { ascent: number, height: number, descent: number };
  35. private _color = "";
  36. private _style: Nullable<Style> = null;
  37. private _styleObserver: Nullable<Observer<Style>>;
  38. /** @hidden */
  39. protected _horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
  40. /** @hidden */
  41. protected _verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
  42. private _isDirty = true;
  43. /** @hidden */
  44. public _tempParentMeasure = Measure.Empty();
  45. /** @hidden */
  46. protected _cachedParentMeasure = Measure.Empty();
  47. private _paddingLeft = new ValueAndUnit(0);
  48. private _paddingRight = new ValueAndUnit(0);
  49. private _paddingTop = new ValueAndUnit(0);
  50. private _paddingBottom = new ValueAndUnit(0);
  51. /** @hidden */
  52. public _left = new ValueAndUnit(0);
  53. /** @hidden */
  54. public _top = new ValueAndUnit(0);
  55. private _scaleX = 1.0;
  56. private _scaleY = 1.0;
  57. private _rotation = 0;
  58. private _transformCenterX = 0.5;
  59. private _transformCenterY = 0.5;
  60. private _transformMatrix = Matrix2D.Identity();
  61. /** @hidden */
  62. protected _invertTransformMatrix = Matrix2D.Identity();
  63. /** @hidden */
  64. protected _transformedPosition = Vector2.Zero();
  65. private _onlyMeasureMode = false;
  66. private _isMatrixDirty = true;
  67. private _cachedOffsetX: number;
  68. private _cachedOffsetY: number;
  69. private _isVisible = true;
  70. /** @hidden */
  71. public _linkedMesh: Nullable<AbstractMesh>;
  72. private _fontSet = false;
  73. private _dummyVector2 = Vector2.Zero();
  74. private _downCount = 0;
  75. private _enterCount = -1;
  76. private _doNotRender = false;
  77. private _downPointerIds: { [id: number]: boolean } = {};
  78. protected _isEnabled = true;
  79. protected _disabledColor = "#9a9a9a";
  80. /** @hidden */
  81. public _tag: any;
  82. /** Gets or sets a boolean indicating if the control can be hit with pointer events */
  83. public isHitTestVisible = true;
  84. /** Gets or sets a boolean indicating if the control can block pointer events */
  85. public isPointerBlocker = false;
  86. /** Gets or sets a boolean indicating if the control can be focusable */
  87. public isFocusInvisible = false;
  88. /** Gets or sets a value indicating the offset to apply on X axis to render the shadow */
  89. public shadowOffsetX = 0;
  90. /** Gets or sets a value indicating the offset to apply on Y axis to render the shadow */
  91. public shadowOffsetY = 0;
  92. /** Gets or sets a value indicating the amount of blur to use to render the shadow */
  93. public shadowBlur = 0;
  94. /** Gets or sets a value indicating the color of the shadow (black by default ie. "#000") */
  95. public shadowColor = '#000';
  96. /** Gets or sets the cursor to use when the control is hovered */
  97. public hoverCursor = "";
  98. /** @hidden */
  99. protected _linkOffsetX = new ValueAndUnit(0);
  100. /** @hidden */
  101. protected _linkOffsetY = new ValueAndUnit(0);
  102. // Properties
  103. /** Gets the control type name */
  104. public get typeName(): string {
  105. return this._getTypeName();
  106. }
  107. /**
  108. * An event triggered when the pointer move over the control.
  109. */
  110. public onPointerMoveObservable = new Observable<Vector2>();
  111. /**
  112. * An event triggered when the pointer move out of the control.
  113. */
  114. public onPointerOutObservable = new Observable<Control>();
  115. /**
  116. * An event triggered when the pointer taps the control
  117. */
  118. public onPointerDownObservable = new Observable<Vector2WithInfo>();
  119. /**
  120. * An event triggered when pointer up
  121. */
  122. public onPointerUpObservable = new Observable<Vector2WithInfo>();
  123. /**
  124. * An event triggered when a control is clicked on
  125. */
  126. public onPointerClickObservable = new Observable<Vector2WithInfo>();
  127. /**
  128. * An event triggered when pointer enters the control
  129. */
  130. public onPointerEnterObservable = new Observable<Control>();
  131. /**
  132. * An event triggered when the control is marked as dirty
  133. */
  134. public onDirtyObservable = new Observable<Control>();
  135. /**
  136. * An event triggered after the control is drawn
  137. */
  138. public onAfterDrawObservable = new Observable<Control>();
  139. /** Gets or set information about font offsets (used to render and align text) */
  140. public get fontOffset(): { ascent: number, height: number, descent: number } {
  141. return this._fontOffset;
  142. }
  143. public set fontOffset(offset: { ascent: number, height: number, descent: number }) {
  144. this._fontOffset = offset;
  145. }
  146. /** Gets or sets alpha value for the control (1 means opaque and 0 means entirely transparent) */
  147. public get alpha(): number {
  148. return this._alpha;
  149. }
  150. public set alpha(value: number) {
  151. if (this._alpha === value) {
  152. return;
  153. }
  154. this._alphaSet = true;
  155. this._alpha = value;
  156. this._markAsDirty();
  157. }
  158. /** Gets or sets a value indicating the scale factor on X axis (1 by default)
  159. * @see http://doc.babylonjs.com/how_to/gui#rotation-and-scaling
  160. */
  161. public get scaleX(): number {
  162. return this._scaleX;
  163. }
  164. public set scaleX(value: number) {
  165. if (this._scaleX === value) {
  166. return;
  167. }
  168. this._scaleX = value;
  169. this._markAsDirty();
  170. this._markMatrixAsDirty();
  171. }
  172. /** Gets or sets a value indicating the scale factor on Y axis (1 by default)
  173. * @see http://doc.babylonjs.com/how_to/gui#rotation-and-scaling
  174. */
  175. public get scaleY(): number {
  176. return this._scaleY;
  177. }
  178. public set scaleY(value: number) {
  179. if (this._scaleY === value) {
  180. return;
  181. }
  182. this._scaleY = value;
  183. this._markAsDirty();
  184. this._markMatrixAsDirty();
  185. }
  186. /** Gets or sets the rotation angle (0 by default)
  187. * @see http://doc.babylonjs.com/how_to/gui#rotation-and-scaling
  188. */
  189. public get rotation(): number {
  190. return this._rotation;
  191. }
  192. public set rotation(value: number) {
  193. if (this._rotation === value) {
  194. return;
  195. }
  196. this._rotation = value;
  197. this._markAsDirty();
  198. this._markMatrixAsDirty();
  199. }
  200. /** Gets or sets the transformation center on Y axis (0 by default)
  201. * @see http://doc.babylonjs.com/how_to/gui#rotation-and-scaling
  202. */
  203. public get transformCenterY(): number {
  204. return this._transformCenterY;
  205. }
  206. public set transformCenterY(value: number) {
  207. if (this._transformCenterY === value) {
  208. return;
  209. }
  210. this._transformCenterY = value;
  211. this._markAsDirty();
  212. this._markMatrixAsDirty();
  213. }
  214. /** Gets or sets the transformation center on X axis (0 by default)
  215. * @see http://doc.babylonjs.com/how_to/gui#rotation-and-scaling
  216. */
  217. public get transformCenterX(): number {
  218. return this._transformCenterX;
  219. }
  220. public set transformCenterX(value: number) {
  221. if (this._transformCenterX === value) {
  222. return;
  223. }
  224. this._transformCenterX = value;
  225. this._markAsDirty();
  226. this._markMatrixAsDirty();
  227. }
  228. /**
  229. * Gets or sets the horizontal alignment
  230. * @see http://doc.babylonjs.com/how_to/gui#alignments
  231. */
  232. public get horizontalAlignment(): number {
  233. return this._horizontalAlignment;
  234. }
  235. public set horizontalAlignment(value: number) {
  236. if (this._horizontalAlignment === value) {
  237. return;
  238. }
  239. this._horizontalAlignment = value;
  240. this._markAsDirty();
  241. }
  242. /**
  243. * Gets or sets the vertical alignment
  244. * @see http://doc.babylonjs.com/how_to/gui#alignments
  245. */
  246. public get verticalAlignment(): number {
  247. return this._verticalAlignment;
  248. }
  249. public set verticalAlignment(value: number) {
  250. if (this._verticalAlignment === value) {
  251. return;
  252. }
  253. this._verticalAlignment = value;
  254. this._markAsDirty();
  255. }
  256. /**
  257. * Gets or sets control width
  258. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  259. */
  260. public get width(): string | number {
  261. return this._width.toString(this._host);
  262. }
  263. /**
  264. * Gets control width in pixel
  265. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  266. */
  267. public get widthInPixels(): number {
  268. return this._width.getValueInPixel(this._host, this._cachedParentMeasure.width);
  269. }
  270. public set width(value: string | number) {
  271. if (this._width.toString(this._host) === value) {
  272. return;
  273. }
  274. if (this._width.fromString(value)) {
  275. this._markAsDirty();
  276. }
  277. }
  278. /**
  279. * Gets or sets control height
  280. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  281. */
  282. public get height(): string | number {
  283. return this._height.toString(this._host);
  284. }
  285. /**
  286. * Gets control height in pixel
  287. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  288. */
  289. public get heightInPixels(): number {
  290. return this._height.getValueInPixel(this._host, this._cachedParentMeasure.height);
  291. }
  292. public set height(value: string | number) {
  293. if (this._height.toString(this._host) === value) {
  294. return;
  295. }
  296. if (this._height.fromString(value)) {
  297. this._markAsDirty();
  298. }
  299. }
  300. /** Gets or set font family */
  301. public get fontFamily(): string {
  302. return this._fontFamily;
  303. }
  304. public set fontFamily(value: string) {
  305. if (this._fontFamily === value) {
  306. return;
  307. }
  308. this._fontFamily = value;
  309. this._resetFontCache();
  310. }
  311. /** Gets or sets font style */
  312. public get fontStyle(): string {
  313. return this._fontStyle;
  314. }
  315. public set fontStyle(value: string) {
  316. if (this._fontStyle === value) {
  317. return;
  318. }
  319. this._fontStyle = value;
  320. this._resetFontCache();
  321. }
  322. /** Gets or sets font weight */
  323. public get fontWeight(): string {
  324. return this._fontWeight;
  325. }
  326. public set fontWeight(value: string) {
  327. if (this._fontWeight === value) {
  328. return;
  329. }
  330. this._fontWeight = value;
  331. this._resetFontCache();
  332. }
  333. /**
  334. * Gets or sets style
  335. * @see http://doc.babylonjs.com/how_to/gui#styles
  336. */
  337. public get style(): Nullable<Style> {
  338. return this._style;
  339. }
  340. public set style(value: Nullable<Style>) {
  341. if (this._style) {
  342. this._style.onChangedObservable.remove(this._styleObserver);
  343. this._styleObserver = null;
  344. }
  345. this._style = value;
  346. if (this._style) {
  347. this._styleObserver = this._style.onChangedObservable.add(() => {
  348. this._markAsDirty();
  349. this._resetFontCache();
  350. });
  351. }
  352. this._markAsDirty();
  353. this._resetFontCache();
  354. }
  355. /** @hidden */
  356. public get _isFontSizeInPercentage(): boolean {
  357. return this._fontSize.isPercentage;
  358. }
  359. /** Gets font size in pixels */
  360. public get fontSizeInPixels(): number {
  361. let fontSizeToUse = this._style ? this._style._fontSize : this._fontSize;
  362. if (fontSizeToUse.isPixel) {
  363. return fontSizeToUse.getValue(this._host);
  364. }
  365. return fontSizeToUse.getValueInPixel(this._host, this._tempParentMeasure.height || this._cachedParentMeasure.height);
  366. }
  367. /** Gets or sets font size */
  368. public get fontSize(): string | number {
  369. return this._fontSize.toString(this._host);
  370. }
  371. public set fontSize(value: string | number) {
  372. if (this._fontSize.toString(this._host) === value) {
  373. return;
  374. }
  375. if (this._fontSize.fromString(value)) {
  376. this._markAsDirty();
  377. this._resetFontCache();
  378. }
  379. }
  380. /** Gets or sets foreground color */
  381. public get color(): string {
  382. return this._color;
  383. }
  384. public set color(value: string) {
  385. if (this._color === value) {
  386. return;
  387. }
  388. this._color = value;
  389. this._markAsDirty();
  390. }
  391. /** Gets or sets z index which is used to reorder controls on the z axis */
  392. public get zIndex(): number {
  393. return this._zIndex;
  394. }
  395. public set zIndex(value: number) {
  396. if (this.zIndex === value) {
  397. return;
  398. }
  399. this._zIndex = value;
  400. if (this._root) {
  401. this._root._reOrderControl(this);
  402. }
  403. }
  404. /** Gets or sets a boolean indicating if the control can be rendered */
  405. public get notRenderable(): boolean {
  406. return this._doNotRender;
  407. }
  408. public set notRenderable(value: boolean) {
  409. if (this._doNotRender === value) {
  410. return;
  411. }
  412. this._doNotRender = value;
  413. this._markAsDirty();
  414. }
  415. /** Gets or sets a boolean indicating if the control is visible */
  416. public get isVisible(): boolean {
  417. return this._isVisible;
  418. }
  419. public set isVisible(value: boolean) {
  420. if (this._isVisible === value) {
  421. return;
  422. }
  423. this._isVisible = value;
  424. this._markAsDirty(true);
  425. }
  426. /** Gets a boolean indicating that the control needs to update its rendering */
  427. public get isDirty(): boolean {
  428. return this._isDirty;
  429. }
  430. /**
  431. * Gets the current linked mesh (or null if none)
  432. */
  433. public get linkedMesh(): Nullable<AbstractMesh> {
  434. return this._linkedMesh;
  435. }
  436. /**
  437. * Gets or sets a value indicating the padding to use on the left of the control
  438. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  439. */
  440. public get paddingLeft(): string | number {
  441. return this._paddingLeft.toString(this._host);
  442. }
  443. /**
  444. * Gets a value indicating the padding in pixels to use on the left of the control
  445. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  446. */
  447. public get paddingLeftInPixels(): number {
  448. return this._paddingLeft.getValueInPixel(this._host, this._cachedParentMeasure.width);
  449. }
  450. public set paddingLeft(value: string | number) {
  451. if (this._paddingLeft.fromString(value)) {
  452. this._markAsDirty();
  453. }
  454. }
  455. /**
  456. * Gets or sets a value indicating the padding to use on the right of the control
  457. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  458. */
  459. public get paddingRight(): string | number {
  460. return this._paddingRight.toString(this._host);
  461. }
  462. /**
  463. * Gets a value indicating the padding in pixels to use on the right of the control
  464. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  465. */
  466. public get paddingRightInPixels(): number {
  467. return this._paddingRight.getValueInPixel(this._host, this._cachedParentMeasure.width);
  468. }
  469. public set paddingRight(value: string | number) {
  470. if (this._paddingRight.fromString(value)) {
  471. this._markAsDirty();
  472. }
  473. }
  474. /**
  475. * Gets or sets a value indicating the padding to use on the top of the control
  476. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  477. */
  478. public get paddingTop(): string | number {
  479. return this._paddingTop.toString(this._host);
  480. }
  481. /**
  482. * Gets a value indicating the padding in pixels to use on the top of the control
  483. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  484. */
  485. public get paddingTopInPixels(): number {
  486. return this._paddingTop.getValueInPixel(this._host, this._cachedParentMeasure.height);
  487. }
  488. public set paddingTop(value: string | number) {
  489. if (this._paddingTop.fromString(value)) {
  490. this._markAsDirty();
  491. }
  492. }
  493. /**
  494. * Gets or sets a value indicating the padding to use on the bottom of the control
  495. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  496. */
  497. public get paddingBottom(): string | number {
  498. return this._paddingBottom.toString(this._host);
  499. }
  500. /**
  501. * Gets a value indicating the padding in pixels to use on the bottom of the control
  502. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  503. */
  504. public get paddingBottomInPixels(): number {
  505. return this._paddingBottom.getValueInPixel(this._host, this._cachedParentMeasure.height);
  506. }
  507. public set paddingBottom(value: string | number) {
  508. if (this._paddingBottom.fromString(value)) {
  509. this._markAsDirty();
  510. }
  511. }
  512. /**
  513. * Gets or sets a value indicating the left coordinate of the control
  514. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  515. */
  516. public get left(): string | number {
  517. return this._left.toString(this._host);
  518. }
  519. /**
  520. * Gets a value indicating the left coordinate in pixels of the control
  521. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  522. */
  523. public get leftInPixels(): number {
  524. return this._left.getValueInPixel(this._host, this._cachedParentMeasure.width);
  525. }
  526. public set left(value: string | number) {
  527. if (this._left.fromString(value)) {
  528. this._markAsDirty();
  529. }
  530. }
  531. /**
  532. * Gets or sets a value indicating the top coordinate of the control
  533. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  534. */
  535. public get top(): string | number {
  536. return this._top.toString(this._host);
  537. }
  538. /**
  539. * Gets a value indicating the top coordinate in pixels of the control
  540. * @see http://doc.babylonjs.com/how_to/gui#position-and-size
  541. */
  542. public get topInPixels(): number {
  543. return this._top.getValueInPixel(this._host, this._cachedParentMeasure.height);
  544. }
  545. public set top(value: string | number) {
  546. if (this._top.fromString(value)) {
  547. this._markAsDirty();
  548. }
  549. }
  550. /**
  551. * Gets or sets a value indicating the offset on X axis to the linked mesh
  552. * @see http://doc.babylonjs.com/how_to/gui#tracking-positions
  553. */
  554. public get linkOffsetX(): string | number {
  555. return this._linkOffsetX.toString(this._host);
  556. }
  557. /**
  558. * Gets a value indicating the offset in pixels on X axis to the linked mesh
  559. * @see http://doc.babylonjs.com/how_to/gui#tracking-positions
  560. */
  561. public get linkOffsetXInPixels(): number {
  562. return this._linkOffsetX.getValueInPixel(this._host, this._cachedParentMeasure.width);
  563. }
  564. public set linkOffsetX(value: string | number) {
  565. if (this._linkOffsetX.fromString(value)) {
  566. this._markAsDirty();
  567. }
  568. }
  569. /**
  570. * Gets or sets a value indicating the offset on Y axis to the linked mesh
  571. * @see http://doc.babylonjs.com/how_to/gui#tracking-positions
  572. */
  573. public get linkOffsetY(): string | number {
  574. return this._linkOffsetY.toString(this._host);
  575. }
  576. /**
  577. * Gets a value indicating the offset in pixels on Y axis to the linked mesh
  578. * @see http://doc.babylonjs.com/how_to/gui#tracking-positions
  579. */
  580. public get linkOffsetYInPixels(): number {
  581. return this._linkOffsetY.getValueInPixel(this._host, this._cachedParentMeasure.height);
  582. }
  583. public set linkOffsetY(value: string | number) {
  584. if (this._linkOffsetY.fromString(value)) {
  585. this._markAsDirty();
  586. }
  587. }
  588. /** Gets the center coordinate on X axis */
  589. public get centerX(): number {
  590. return this._currentMeasure.left + this._currentMeasure.width / 2;
  591. }
  592. /** Gets the center coordinate on Y axis */
  593. public get centerY(): number {
  594. return this._currentMeasure.top + this._currentMeasure.height / 2;
  595. }
  596. /** Gets or sets if control is Enabled*/
  597. public get isEnabled(): boolean {
  598. return this._isEnabled;
  599. }
  600. public set isEnabled(value: boolean) {
  601. if(this._isEnabled === value){
  602. return;
  603. }
  604. this._isEnabled = value;
  605. this._markAsDirty();
  606. }
  607. /** Gets or sets background color of control if it's disabled*/
  608. public get disabledColor(): string {
  609. return this._disabledColor;
  610. }
  611. public set disabledColor(value: string) {
  612. if(this._disabledColor === value){
  613. return;
  614. }
  615. this._disabledColor = value;
  616. this._markAsDirty();
  617. }
  618. // Functions
  619. /**
  620. * Creates a new control
  621. * @param name defines the name of the control
  622. */
  623. constructor(
  624. /** defines the name of the control */
  625. public name?: string) {
  626. }
  627. /** @hidden */
  628. protected _getTypeName(): string {
  629. return "Control";
  630. }
  631. /** @hidden */
  632. public _resetFontCache(): void {
  633. this._fontSet = true;
  634. this._markAsDirty();
  635. }
  636. /**
  637. * Determines if a container is an ascendant of the current control
  638. * @param container defines the container to look for
  639. * @returns true if the container is one of the ascendant of the control
  640. */
  641. public isAscendant(container: Control): boolean {
  642. if (!this.parent) {
  643. return false;
  644. }
  645. if (this.parent === container) {
  646. return true;
  647. }
  648. return this.parent.isAscendant(container);
  649. }
  650. /**
  651. * Gets coordinates in local control space
  652. * @param globalCoordinates defines the coordinates to transform
  653. * @returns the new coordinates in local space
  654. */
  655. public getLocalCoordinates(globalCoordinates: Vector2): Vector2 {
  656. var result = Vector2.Zero();
  657. this.getLocalCoordinatesToRef(globalCoordinates, result);
  658. return result;
  659. }
  660. /**
  661. * Gets coordinates in local control space
  662. * @param globalCoordinates defines the coordinates to transform
  663. * @param result defines the target vector2 where to store the result
  664. * @returns the current control
  665. */
  666. public getLocalCoordinatesToRef(globalCoordinates: Vector2, result: Vector2): Control {
  667. result.x = globalCoordinates.x - this._currentMeasure.left;
  668. result.y = globalCoordinates.y - this._currentMeasure.top;
  669. return this;
  670. }
  671. /**
  672. * Gets coordinates in parent local control space
  673. * @param globalCoordinates defines the coordinates to transform
  674. * @returns the new coordinates in parent local space
  675. */
  676. public getParentLocalCoordinates(globalCoordinates: Vector2): Vector2 {
  677. var result = Vector2.Zero();
  678. result.x = globalCoordinates.x - this._cachedParentMeasure.left;
  679. result.y = globalCoordinates.y - this._cachedParentMeasure.top;
  680. return result;
  681. }
  682. /**
  683. * Move the current control to a vector3 position projected onto the screen.
  684. * @param position defines the target position
  685. * @param scene defines the hosting scene
  686. */
  687. public moveToVector3(position: Vector3, scene: Scene): void {
  688. if (!this._host || this._root !== this._host._rootContainer) {
  689. Tools.Error("Cannot move a control to a vector3 if the control is not at root level");
  690. return;
  691. }
  692. this.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
  693. this.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
  694. var globalViewport = this._host._getGlobalViewport(scene);
  695. var projectedPosition = Vector3.Project(position, Matrix.Identity(), scene.getTransformMatrix(), globalViewport);
  696. this._moveToProjectedPosition(projectedPosition);
  697. if (projectedPosition.z < 0 || projectedPosition.z > 1) {
  698. this.notRenderable = true;
  699. return;
  700. }
  701. this.notRenderable = false;
  702. }
  703. /**
  704. * Link current control with a target mesh
  705. * @param mesh defines the mesh to link with
  706. * @see http://doc.babylonjs.com/how_to/gui#tracking-positions
  707. */
  708. public linkWithMesh(mesh: Nullable<AbstractMesh>): void {
  709. if (!this._host || this._root && this._root !== this._host._rootContainer) {
  710. if (mesh) {
  711. Tools.Error("Cannot link a control to a mesh if the control is not at root level");
  712. }
  713. return;
  714. }
  715. var index = this._host._linkedControls.indexOf(this);
  716. if (index !== -1) {
  717. this._linkedMesh = mesh;
  718. if (!mesh) {
  719. this._host._linkedControls.splice(index, 1);
  720. }
  721. return;
  722. } else if (!mesh) {
  723. return;
  724. }
  725. this.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
  726. this.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
  727. this._linkedMesh = mesh;
  728. this._onlyMeasureMode = this._currentMeasure.width === 0 || this._currentMeasure.height === 0;
  729. this._host._linkedControls.push(this);
  730. }
  731. /** @hidden */
  732. public _moveToProjectedPosition(projectedPosition: Vector3): void {
  733. let oldLeft = this._left.getValue(this._host);
  734. let oldTop = this._top.getValue(this._host);
  735. var newLeft = ((projectedPosition.x + this._linkOffsetX.getValue(this._host)) - this._currentMeasure.width / 2);
  736. var newTop = ((projectedPosition.y + this._linkOffsetY.getValue(this._host)) - this._currentMeasure.height / 2);
  737. if (this._left.ignoreAdaptiveScaling && this._top.ignoreAdaptiveScaling) {
  738. if (Math.abs(newLeft - oldLeft) < 0.5) {
  739. newLeft = oldLeft;
  740. }
  741. if (Math.abs(newTop - oldTop) < 0.5) {
  742. newTop = oldTop;
  743. }
  744. }
  745. this.left = newLeft + "px";
  746. this.top = newTop + "px";
  747. this._left.ignoreAdaptiveScaling = true;
  748. this._top.ignoreAdaptiveScaling = true;
  749. }
  750. /** @hidden */
  751. public _markMatrixAsDirty(): void {
  752. this._isMatrixDirty = true;
  753. this._markAsDirty();
  754. }
  755. /** @hidden */
  756. public _markAsDirty(force = false): void {
  757. if (!this._isVisible && !force) {
  758. return;
  759. }
  760. this._isDirty = true;
  761. if (!this._host) {
  762. return; // Not yet connected
  763. }
  764. this._host.markAsDirty();
  765. }
  766. /** @hidden */
  767. public _markAllAsDirty(): void {
  768. this._markAsDirty();
  769. if (this._font) {
  770. this._prepareFont();
  771. }
  772. }
  773. /** @hidden */
  774. public _link(root: Nullable<Container>, host: AdvancedDynamicTexture): void {
  775. this._root = root;
  776. this._host = host;
  777. }
  778. /** @hidden */
  779. protected _transform(context: CanvasRenderingContext2D): void {
  780. if (!this._isMatrixDirty && this._scaleX === 1 && this._scaleY === 1 && this._rotation === 0) {
  781. return;
  782. }
  783. // postTranslate
  784. var offsetX = this._currentMeasure.width * this._transformCenterX + this._currentMeasure.left;
  785. var offsetY = this._currentMeasure.height * this._transformCenterY + this._currentMeasure.top;
  786. context.translate(offsetX, offsetY);
  787. // rotate
  788. context.rotate(this._rotation);
  789. // scale
  790. context.scale(this._scaleX, this._scaleY);
  791. // preTranslate
  792. context.translate(-offsetX, -offsetY);
  793. // Need to update matrices?
  794. if (this._isMatrixDirty || this._cachedOffsetX !== offsetX || this._cachedOffsetY !== offsetY) {
  795. this._cachedOffsetX = offsetX;
  796. this._cachedOffsetY = offsetY;
  797. this._isMatrixDirty = false;
  798. Matrix2D.ComposeToRef(-offsetX, -offsetY, this._rotation, this._scaleX, this._scaleY, this._root ? this._root._transformMatrix : null, this._transformMatrix);
  799. this._transformMatrix.invertToRef(this._invertTransformMatrix);
  800. }
  801. }
  802. /** @hidden */
  803. protected _applyStates(context: CanvasRenderingContext2D): void {
  804. if (this._isFontSizeInPercentage) {
  805. this._fontSet = true;
  806. }
  807. if (this._fontSet) {
  808. this._prepareFont();
  809. this._fontSet = false;
  810. }
  811. if (this._font) {
  812. context.font = this._font;
  813. }
  814. if (this._color) {
  815. context.fillStyle = this._color;
  816. }
  817. if (this._alphaSet) {
  818. context.globalAlpha = this.parent ? this.parent.alpha * this._alpha : this._alpha;
  819. }
  820. }
  821. /** @hidden */
  822. protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean {
  823. if (this._isDirty || !this._cachedParentMeasure.isEqualsTo(parentMeasure)) {
  824. this._isDirty = false;
  825. this._currentMeasure.copyFrom(parentMeasure);
  826. // Let children take some pre-measurement actions
  827. this._preMeasure(parentMeasure, context);
  828. this._measure();
  829. this._computeAlignment(parentMeasure, context);
  830. // Convert to int values
  831. this._currentMeasure.left = this._currentMeasure.left | 0;
  832. this._currentMeasure.top = this._currentMeasure.top | 0;
  833. this._currentMeasure.width = this._currentMeasure.width | 0;
  834. this._currentMeasure.height = this._currentMeasure.height | 0;
  835. // Let children add more features
  836. this._additionalProcessing(parentMeasure, context);
  837. this._cachedParentMeasure.copyFrom(parentMeasure);
  838. if (this.onDirtyObservable.hasObservers()) {
  839. this.onDirtyObservable.notifyObservers(this);
  840. }
  841. }
  842. if (this._currentMeasure.left > parentMeasure.left + parentMeasure.width) {
  843. return false;
  844. }
  845. if (this._currentMeasure.left + this._currentMeasure.width < parentMeasure.left) {
  846. return false;
  847. }
  848. if (this._currentMeasure.top > parentMeasure.top + parentMeasure.height) {
  849. return false;
  850. }
  851. if (this._currentMeasure.top + this._currentMeasure.height < parentMeasure.top) {
  852. return false;
  853. }
  854. // Transform
  855. this._transform(context);
  856. if (this._onlyMeasureMode) {
  857. this._onlyMeasureMode = false;
  858. return false; // We do not want rendering for this frame as they are measure dependant information that need to be gathered
  859. }
  860. // Clip
  861. this._clip(context);
  862. context.clip();
  863. return true;
  864. }
  865. /** @hidden */
  866. protected _clip(context: CanvasRenderingContext2D) {
  867. context.beginPath();
  868. if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
  869. var shadowOffsetX = this.shadowOffsetX;
  870. var shadowOffsetY = this.shadowOffsetY;
  871. var shadowBlur = this.shadowBlur;
  872. var leftShadowOffset = Math.min(Math.min(shadowOffsetX, 0) - shadowBlur * 2, 0);
  873. var rightShadowOffset = Math.max(Math.max(shadowOffsetX, 0) + shadowBlur * 2, 0);
  874. var topShadowOffset = Math.min(Math.min(shadowOffsetY, 0) - shadowBlur * 2, 0);
  875. var bottomShadowOffset = Math.max(Math.max(shadowOffsetY, 0) + shadowBlur * 2, 0);
  876. context.rect(this._currentMeasure.left + leftShadowOffset,
  877. this._currentMeasure.top + topShadowOffset,
  878. this._currentMeasure.width + rightShadowOffset - leftShadowOffset,
  879. this._currentMeasure.height + bottomShadowOffset - topShadowOffset);
  880. } else {
  881. context.rect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
  882. }
  883. }
  884. /** @hidden */
  885. public _measure(): void {
  886. // Width / Height
  887. if (this._width.isPixel) {
  888. this._currentMeasure.width = this._width.getValue(this._host);
  889. } else {
  890. this._currentMeasure.width *= this._width.getValue(this._host);
  891. }
  892. if (this._height.isPixel) {
  893. this._currentMeasure.height = this._height.getValue(this._host);
  894. } else {
  895. this._currentMeasure.height *= this._height.getValue(this._host);
  896. }
  897. }
  898. /** @hidden */
  899. protected _computeAlignment(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  900. var width = this._currentMeasure.width;
  901. var height = this._currentMeasure.height;
  902. var parentWidth = parentMeasure.width;
  903. var parentHeight = parentMeasure.height;
  904. // Left / top
  905. var x = 0;
  906. var y = 0;
  907. switch (this.horizontalAlignment) {
  908. case Control.HORIZONTAL_ALIGNMENT_LEFT:
  909. x = 0
  910. break;
  911. case Control.HORIZONTAL_ALIGNMENT_RIGHT:
  912. x = parentWidth - width;
  913. break;
  914. case Control.HORIZONTAL_ALIGNMENT_CENTER:
  915. x = (parentWidth - width) / 2;
  916. break;
  917. }
  918. switch (this.verticalAlignment) {
  919. case Control.VERTICAL_ALIGNMENT_TOP:
  920. y = 0;
  921. break;
  922. case Control.VERTICAL_ALIGNMENT_BOTTOM:
  923. y = parentHeight - height;
  924. break;
  925. case Control.VERTICAL_ALIGNMENT_CENTER:
  926. y = (parentHeight - height) / 2;
  927. break;
  928. }
  929. if (this._paddingLeft.isPixel) {
  930. this._currentMeasure.left += this._paddingLeft.getValue(this._host);
  931. this._currentMeasure.width -= this._paddingLeft.getValue(this._host);
  932. } else {
  933. this._currentMeasure.left += parentWidth * this._paddingLeft.getValue(this._host);
  934. this._currentMeasure.width -= parentWidth * this._paddingLeft.getValue(this._host);
  935. }
  936. if (this._paddingRight.isPixel) {
  937. this._currentMeasure.width -= this._paddingRight.getValue(this._host);
  938. } else {
  939. this._currentMeasure.width -= parentWidth * this._paddingRight.getValue(this._host);
  940. }
  941. if (this._paddingTop.isPixel) {
  942. this._currentMeasure.top += this._paddingTop.getValue(this._host);
  943. this._currentMeasure.height -= this._paddingTop.getValue(this._host);
  944. } else {
  945. this._currentMeasure.top += parentHeight * this._paddingTop.getValue(this._host);
  946. this._currentMeasure.height -= parentHeight * this._paddingTop.getValue(this._host);
  947. }
  948. if (this._paddingBottom.isPixel) {
  949. this._currentMeasure.height -= this._paddingBottom.getValue(this._host);
  950. } else {
  951. this._currentMeasure.height -= parentHeight * this._paddingBottom.getValue(this._host);
  952. }
  953. if (this._left.isPixel) {
  954. this._currentMeasure.left += this._left.getValue(this._host);
  955. } else {
  956. this._currentMeasure.left += parentWidth * this._left.getValue(this._host);
  957. }
  958. if (this._top.isPixel) {
  959. this._currentMeasure.top += this._top.getValue(this._host);
  960. } else {
  961. this._currentMeasure.top += parentHeight * this._top.getValue(this._host);
  962. }
  963. this._currentMeasure.left += x;
  964. this._currentMeasure.top += y;
  965. }
  966. /** @hidden */
  967. protected _preMeasure(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  968. // Do nothing
  969. }
  970. /** @hidden */
  971. protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  972. // Do nothing
  973. }
  974. /** @hidden */
  975. public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  976. // Do nothing
  977. }
  978. /**
  979. * Tests if a given coordinates belong to the current control
  980. * @param x defines x coordinate to test
  981. * @param y defines y coordinate to test
  982. * @returns true if the coordinates are inside the control
  983. */
  984. public contains(x: number, y: number): boolean {
  985. // Invert transform
  986. this._invertTransformMatrix.transformCoordinates(x, y, this._transformedPosition);
  987. x = this._transformedPosition.x;
  988. y = this._transformedPosition.y;
  989. // Check
  990. if (x < this._currentMeasure.left) {
  991. return false;
  992. }
  993. if (x > this._currentMeasure.left + this._currentMeasure.width) {
  994. return false;
  995. }
  996. if (y < this._currentMeasure.top) {
  997. return false;
  998. }
  999. if (y > this._currentMeasure.top + this._currentMeasure.height) {
  1000. return false;
  1001. }
  1002. if (this.isPointerBlocker) {
  1003. this._host._shouldBlockPointer = true;
  1004. }
  1005. return true;
  1006. }
  1007. /** @hidden */
  1008. public _processPicking(x: number, y: number, type: number, pointerId: number, buttonIndex: number): boolean {
  1009. if(!this._isEnabled){
  1010. return false;
  1011. }
  1012. if (!this.isHitTestVisible || !this.isVisible || this._doNotRender) {
  1013. return false;
  1014. }
  1015. if (!this.contains(x, y)) {
  1016. return false;
  1017. }
  1018. this._processObservables(type, x, y, pointerId, buttonIndex);
  1019. return true;
  1020. }
  1021. /** @hidden */
  1022. public _onPointerMove(target: Control, coordinates: Vector2): void {
  1023. var canNotify: boolean = this.onPointerMoveObservable.notifyObservers(coordinates, -1, target, this);
  1024. if (canNotify && this.parent != null) this.parent._onPointerMove(target, coordinates);
  1025. }
  1026. /** @hidden */
  1027. public _onPointerEnter(target: Control): boolean {
  1028. if(!this._isEnabled){
  1029. return false;
  1030. }
  1031. if (this._enterCount > 0) {
  1032. return false;
  1033. }
  1034. if (this._enterCount === -1) { // -1 is for touch input, we are now sure we are with a mouse or pencil
  1035. this._enterCount = 0;
  1036. }
  1037. this._enterCount++;
  1038. var canNotify: boolean = this.onPointerEnterObservable.notifyObservers(this, -1, target, this);
  1039. if (canNotify && this.parent != null) this.parent._onPointerEnter(target);
  1040. return true;
  1041. }
  1042. /** @hidden */
  1043. public _onPointerOut(target: Control): void {
  1044. if(!this._isEnabled){
  1045. return;
  1046. }
  1047. this._enterCount = 0;
  1048. var canNotify: boolean = this.onPointerOutObservable.notifyObservers(this, -1, target, this);
  1049. if (canNotify && this.parent != null) this.parent._onPointerOut(target);
  1050. }
  1051. /** @hidden */
  1052. public _onPointerDown(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number): boolean {
  1053. // Prevent pointerout to lose control context.
  1054. // Event redundancy is checked inside the function.
  1055. this._onPointerEnter(this);
  1056. if (this._downCount !== 0) {
  1057. return false;
  1058. }
  1059. this._downCount++;
  1060. this._downPointerIds[pointerId] = true;
  1061. var canNotify: boolean = this.onPointerDownObservable.notifyObservers(new Vector2WithInfo(coordinates, buttonIndex), -1, target, this);
  1062. if (canNotify && this.parent != null) this.parent._onPointerDown(target, coordinates, pointerId, buttonIndex);
  1063. return true;
  1064. }
  1065. /** @hidden */
  1066. public _onPointerUp(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void {
  1067. if(!this._isEnabled){
  1068. return;
  1069. }
  1070. this._downCount = 0;
  1071. delete this._downPointerIds[pointerId];
  1072. var canNotifyClick: boolean = notifyClick;
  1073. if (notifyClick && (this._enterCount > 0 || this._enterCount === -1)) {
  1074. canNotifyClick = this.onPointerClickObservable.notifyObservers(new Vector2WithInfo(coordinates, buttonIndex), -1, target, this);
  1075. }
  1076. var canNotify: boolean = this.onPointerUpObservable.notifyObservers(new Vector2WithInfo(coordinates, buttonIndex), -1, target, this);
  1077. if (canNotify && this.parent != null) this.parent._onPointerUp(target, coordinates, pointerId, buttonIndex, canNotifyClick);
  1078. }
  1079. /** @hidden */
  1080. public _forcePointerUp(pointerId: Nullable<number> = null) {
  1081. if (pointerId !== null) {
  1082. this._onPointerUp(this, Vector2.Zero(), pointerId, 0, true);
  1083. } else {
  1084. for (var key in this._downPointerIds) {
  1085. this._onPointerUp(this, Vector2.Zero(), +key as number, 0, true);
  1086. }
  1087. }
  1088. }
  1089. /** @hidden */
  1090. public _processObservables(type: number, x: number, y: number, pointerId: number, buttonIndex: number): boolean {
  1091. if(!this._isEnabled){
  1092. return false;
  1093. }
  1094. this._dummyVector2.copyFromFloats(x, y);
  1095. if (type === PointerEventTypes.POINTERMOVE) {
  1096. this._onPointerMove(this, this._dummyVector2);
  1097. var previousControlOver = this._host._lastControlOver[pointerId];
  1098. if (previousControlOver && previousControlOver !== this) {
  1099. previousControlOver._onPointerOut(this);
  1100. }
  1101. if (previousControlOver !== this) {
  1102. this._onPointerEnter(this);
  1103. }
  1104. this._host._lastControlOver[pointerId] = this;
  1105. return true;
  1106. }
  1107. if (type === PointerEventTypes.POINTERDOWN) {
  1108. this._onPointerDown(this, this._dummyVector2, pointerId, buttonIndex);
  1109. this._host._lastControlDown[pointerId] = this;
  1110. this._host._lastPickedControl = this;
  1111. return true;
  1112. }
  1113. if (type === PointerEventTypes.POINTERUP) {
  1114. if (this._host._lastControlDown[pointerId]) {
  1115. this._host._lastControlDown[pointerId]._onPointerUp(this, this._dummyVector2, pointerId, buttonIndex, true);
  1116. }
  1117. delete this._host._lastControlDown[pointerId];
  1118. return true;
  1119. }
  1120. return false;
  1121. }
  1122. private _prepareFont() {
  1123. if (!this._font && !this._fontSet) {
  1124. return;
  1125. }
  1126. if (this._style) {
  1127. this._font = this._style.fontStyle + " " + this._style.fontWeight + " " + this.fontSizeInPixels + "px " + this._style.fontFamily;
  1128. } else {
  1129. this._font = this._fontStyle + " " + this._fontWeight + " " + this.fontSizeInPixels + "px " + this._fontFamily;
  1130. }
  1131. this._fontOffset = Control._GetFontOffset(this._font);
  1132. }
  1133. /** Releases associated resources */
  1134. public dispose() {
  1135. this.onDirtyObservable.clear();
  1136. this.onAfterDrawObservable.clear();
  1137. this.onPointerDownObservable.clear();
  1138. this.onPointerEnterObservable.clear();
  1139. this.onPointerMoveObservable.clear();
  1140. this.onPointerOutObservable.clear();
  1141. this.onPointerUpObservable.clear();
  1142. this.onPointerClickObservable.clear();
  1143. if (this._styleObserver && this._style) {
  1144. this._style.onChangedObservable.remove(this._styleObserver);
  1145. this._styleObserver = null;
  1146. }
  1147. if (this._root) {
  1148. this._root.removeControl(this);
  1149. this._root = null;
  1150. }
  1151. if (this._host) {
  1152. var index = this._host._linkedControls.indexOf(this);
  1153. if (index > -1) {
  1154. this.linkWithMesh(null);
  1155. }
  1156. }
  1157. }
  1158. // Statics
  1159. private static _HORIZONTAL_ALIGNMENT_LEFT = 0;
  1160. private static _HORIZONTAL_ALIGNMENT_RIGHT = 1;
  1161. private static _HORIZONTAL_ALIGNMENT_CENTER = 2;
  1162. private static _VERTICAL_ALIGNMENT_TOP = 0;
  1163. private static _VERTICAL_ALIGNMENT_BOTTOM = 1;
  1164. private static _VERTICAL_ALIGNMENT_CENTER = 2;
  1165. /** HORIZONTAL_ALIGNMENT_LEFT */
  1166. public static get HORIZONTAL_ALIGNMENT_LEFT(): number {
  1167. return Control._HORIZONTAL_ALIGNMENT_LEFT;
  1168. }
  1169. /** HORIZONTAL_ALIGNMENT_RIGHT */
  1170. public static get HORIZONTAL_ALIGNMENT_RIGHT(): number {
  1171. return Control._HORIZONTAL_ALIGNMENT_RIGHT;
  1172. }
  1173. /** HORIZONTAL_ALIGNMENT_CENTER */
  1174. public static get HORIZONTAL_ALIGNMENT_CENTER(): number {
  1175. return Control._HORIZONTAL_ALIGNMENT_CENTER;
  1176. }
  1177. /** VERTICAL_ALIGNMENT_TOP */
  1178. public static get VERTICAL_ALIGNMENT_TOP(): number {
  1179. return Control._VERTICAL_ALIGNMENT_TOP;
  1180. }
  1181. /** VERTICAL_ALIGNMENT_BOTTOM */
  1182. public static get VERTICAL_ALIGNMENT_BOTTOM(): number {
  1183. return Control._VERTICAL_ALIGNMENT_BOTTOM;
  1184. }
  1185. /** VERTICAL_ALIGNMENT_CENTER */
  1186. public static get VERTICAL_ALIGNMENT_CENTER(): number {
  1187. return Control._VERTICAL_ALIGNMENT_CENTER;
  1188. }
  1189. private static _FontHeightSizes: { [key: string]: { ascent: number, height: number, descent: number } } = {};
  1190. /** @hidden */
  1191. public static _GetFontOffset(font: string): { ascent: number, height: number, descent: number } {
  1192. if (Control._FontHeightSizes[font]) {
  1193. return Control._FontHeightSizes[font];
  1194. }
  1195. var text = document.createElement("span");
  1196. text.innerHTML = "Hg";
  1197. text.style.font = font;
  1198. var block = document.createElement("div");
  1199. block.style.display = "inline-block";
  1200. block.style.width = "1px";
  1201. block.style.height = "0px";
  1202. block.style.verticalAlign = "bottom";
  1203. var div = document.createElement("div");
  1204. div.appendChild(text);
  1205. div.appendChild(block);
  1206. document.body.appendChild(div);
  1207. var fontAscent = 0;
  1208. var fontHeight = 0;
  1209. try {
  1210. fontHeight = block.getBoundingClientRect().top - text.getBoundingClientRect().top;
  1211. block.style.verticalAlign = "baseline";
  1212. fontAscent = block.getBoundingClientRect().top - text.getBoundingClientRect().top;
  1213. } finally {
  1214. document.body.removeChild(div);
  1215. }
  1216. var result = { ascent: fontAscent, height: fontHeight, descent: fontHeight - fontAscent };
  1217. Control._FontHeightSizes[font] = result;
  1218. return result;
  1219. };
  1220. /**
  1221. * Creates a stack panel that can be used to render headers
  1222. * @param control defines the control to associate with the header
  1223. * @param text defines the text of the header
  1224. * @param size defines the size of the header
  1225. * @param options defines options used to configure the header
  1226. * @returns a new StackPanel
  1227. * @ignore
  1228. * @hidden
  1229. */
  1230. public static AddHeader: (control: Control, text: string, size: string | number, options: { isHorizontal: boolean, controlFirst: boolean }) => any = () => { };
  1231. /** @hidden */
  1232. protected static drawEllipse(x: number, y: number, width: number, height: number, context: CanvasRenderingContext2D): void {
  1233. context.translate(x, y);
  1234. context.scale(width, height);
  1235. context.beginPath();
  1236. context.arc(0, 0, 1, 0, 2 * Math.PI);
  1237. context.closePath();
  1238. context.scale(1 / width, 1 / height);
  1239. context.translate(-x, -y);
  1240. }
  1241. }