control.ts 49 KB

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