control.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. /// <reference path="../../../dist/preview release/babylon.d.ts"/>
  2. module BABYLON.GUI {
  3. export class Control {
  4. private _zIndex = 0;
  5. public _root: Container;
  6. public _host: AdvancedDynamicTexture;
  7. public _currentMeasure = Measure.Empty();
  8. private _fontFamily: string;
  9. private _fontSize = 18;
  10. private _font: string;
  11. private _width = new ValueAndUnit(1, ValueAndUnit.UNITMODE_PERCENTAGE, false);
  12. private _height = new ValueAndUnit(1, ValueAndUnit.UNITMODE_PERCENTAGE, false);
  13. private _lastMeasuredFont: string;
  14. protected _fontOffset: {ascent: number, height: number, descent: number};
  15. private _color: string;
  16. private _horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
  17. private _verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
  18. private _isDirty = true;
  19. private _cachedParentMeasure = Measure.Empty();
  20. private _marginLeft = new ValueAndUnit(0);
  21. private _marginRight = new ValueAndUnit(0);
  22. private _marginTop = new ValueAndUnit(0);
  23. private _marginBottom = new ValueAndUnit(0);
  24. private _left = new ValueAndUnit(0);
  25. private _top = new ValueAndUnit(0);
  26. // Properties
  27. /**
  28. * An event triggered when the pointer move over the control.
  29. * @type {BABYLON.Observable}
  30. */
  31. public onPointerMoveObservable = new Observable<Control>();
  32. /**
  33. * An event triggered when the pointer move out of the control.
  34. * @type {BABYLON.Observable}
  35. */
  36. public onPointerOutObservable = new Observable<Control>();
  37. /**
  38. * An event triggered when the pointer taps the control
  39. * @type {BABYLON.Observable}
  40. */
  41. public onPointerDownObservable = new Observable<Control>();
  42. /**
  43. * An event triggered when pointer up
  44. * @type {BABYLON.Observable}
  45. */
  46. public onPointerUpObservable = new Observable<Control>();
  47. public get horizontalAlignment(): number {
  48. return this._horizontalAlignment;
  49. }
  50. public set horizontalAlignment(value: number) {
  51. if (this._horizontalAlignment === value) {
  52. return;
  53. }
  54. this._horizontalAlignment = value;
  55. this._markAsDirty();
  56. }
  57. public get verticalAlignment(): number {
  58. return this._verticalAlignment;
  59. }
  60. public set verticalAlignment(value: number) {
  61. if (this._verticalAlignment === value) {
  62. return;
  63. }
  64. this._verticalAlignment = value;
  65. this._markAsDirty();
  66. }
  67. public get width(): string {
  68. return this._width.toString();
  69. }
  70. public set width(value: string) {
  71. if (this._width.toString() === value) {
  72. return;
  73. }
  74. if (this._width.fromString(value)) {
  75. this._markAsDirty();
  76. }
  77. }
  78. public get height(): string {
  79. return this._height.toString();
  80. }
  81. public set height(value: string) {
  82. if (this._height.toString() === value) {
  83. return;
  84. }
  85. if (this._height.fromString(value)) {
  86. this._markAsDirty();
  87. }
  88. }
  89. public get fontFamily(): string {
  90. return this._fontFamily;
  91. }
  92. public set fontFamily(value: string) {
  93. if (this._fontFamily === value) {
  94. return;
  95. }
  96. this._fontFamily = value;
  97. this._prepareFont();
  98. }
  99. public get fontSize(): number {
  100. return this._fontSize;
  101. }
  102. public set fontSize(value: number) {
  103. if (this._fontSize === value) {
  104. return;
  105. }
  106. this._fontSize = value;
  107. this._prepareFont();
  108. }
  109. public get color(): string {
  110. return this._color;
  111. }
  112. public set color(value: string) {
  113. if (this._color === value) {
  114. return;
  115. }
  116. this._color = value;
  117. this._markAsDirty();
  118. }
  119. public get zIndex(): number {
  120. return this._zIndex;
  121. }
  122. public set zIndex(value: number) {
  123. if (this.zIndex === value) {
  124. return;
  125. }
  126. this._zIndex = value;
  127. this._root._reOrderControl(this);
  128. }
  129. public get isDirty(): boolean {
  130. return this._isDirty;
  131. }
  132. public get marginLeft(): string {
  133. return this._marginLeft.toString();
  134. }
  135. public set marginLeft(value: string) {
  136. if (this._marginLeft.fromString(value)) {
  137. this._markAsDirty();
  138. }
  139. }
  140. public get marginRight(): string {
  141. return this._marginRight.toString();
  142. }
  143. public set marginRight(value: string) {
  144. if (this._marginRight.fromString(value)) {
  145. this._markAsDirty();
  146. }
  147. }
  148. public get marginTop(): string {
  149. return this._marginTop.toString();
  150. }
  151. public set marginTop(value: string) {
  152. if (this._marginTop.fromString(value)) {
  153. this._markAsDirty();
  154. }
  155. }
  156. public get marginBottom(): string {
  157. return this._marginBottom.toString();
  158. }
  159. public set marginBottom(value: string) {
  160. if (this._marginBottom.fromString(value)) {
  161. this._markAsDirty();
  162. }
  163. }
  164. public get left(): string {
  165. return this._left.toString();
  166. }
  167. public set left(value: string) {
  168. if (this._left.fromString(value)) {
  169. this._markAsDirty();
  170. }
  171. }
  172. public get top(): string {
  173. return this._top.toString();
  174. }
  175. public set top(value: string) {
  176. if (this._top.fromString(value)) {
  177. this._markAsDirty();
  178. }
  179. }
  180. // Functions
  181. constructor(public name: string) {
  182. this.fontFamily = "Arial";
  183. }
  184. protected _markAsDirty(): void {
  185. this._isDirty = true;
  186. if (!this._host) {
  187. return; // Not yet connected
  188. }
  189. this._host.markAsDirty();
  190. }
  191. public _link(root: Container, host: AdvancedDynamicTexture): void {
  192. this._root = root;
  193. this._host = host;
  194. }
  195. protected applyStates(context: CanvasRenderingContext2D): void {
  196. if (this._font) {
  197. context.font = this._font;
  198. }
  199. if (this._color) {
  200. context.fillStyle = this._color;
  201. }
  202. }
  203. protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D) {
  204. if (this._isDirty || !this._cachedParentMeasure.isEqualsTo(parentMeasure)) {
  205. this._currentMeasure.copyFrom(parentMeasure);
  206. this._measure(parentMeasure, context);
  207. this._computeAlignment(parentMeasure, context);
  208. // Convert to int values
  209. this._currentMeasure.left = this._currentMeasure.left | 0;
  210. this._currentMeasure.top = this._currentMeasure.top | 0;
  211. this._currentMeasure.width = this._currentMeasure.width | 0;
  212. this._currentMeasure.height = this._currentMeasure.height | 0;
  213. // Let children add more features
  214. this._additionalProcessing(parentMeasure, context);
  215. this._isDirty = false;
  216. this._cachedParentMeasure.copyFrom(parentMeasure);
  217. }
  218. // Clip
  219. this._clip(context);
  220. context.clip();
  221. }
  222. protected _clip( context: CanvasRenderingContext2D) {
  223. context.beginPath();
  224. context.rect(this._currentMeasure.left ,this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
  225. }
  226. protected _measure(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  227. // Width / Height
  228. if (this._width.isPixel) {
  229. this._currentMeasure.width = this._width.value;
  230. } else {
  231. this._currentMeasure.width *= this._width.value;
  232. }
  233. if (this._height.isPixel) {
  234. this._currentMeasure.height = this._height.value;
  235. } else {
  236. this._currentMeasure.height *= this._height.value;
  237. }
  238. }
  239. protected _computeAlignment(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  240. var width = this._currentMeasure.width;
  241. var height = this._currentMeasure.height;
  242. var parentWidth = parentMeasure.width;
  243. var parentHeight = parentMeasure.height;
  244. // Left / top
  245. var x = 0;
  246. var y = 0;
  247. switch (this.horizontalAlignment) {
  248. case Control.HORIZONTAL_ALIGNMENT_LEFT:
  249. x = 0
  250. break;
  251. case Control.HORIZONTAL_ALIGNMENT_RIGHT:
  252. x = parentWidth - width;
  253. break;
  254. case Control.HORIZONTAL_ALIGNMENT_CENTER:
  255. x = (parentWidth - width) / 2;
  256. break;
  257. }
  258. switch (this.verticalAlignment) {
  259. case Control.VERTICAL_ALIGNMENT_TOP:
  260. y = 0;
  261. break;
  262. case Control.VERTICAL_ALIGNMENT_BOTTOM:
  263. y = parentHeight - height;
  264. break;
  265. case Control.VERTICAL_ALIGNMENT_CENTER:
  266. y = (parentHeight - height) / 2;
  267. break;
  268. }
  269. if (this._marginLeft.isPixel) {
  270. this._currentMeasure.left += this._marginLeft.value;
  271. this._currentMeasure.width -= this._marginRight.value;
  272. } else {
  273. this._currentMeasure.left += parentWidth * this._marginLeft.value;
  274. this._currentMeasure.width -= parentWidth * this._marginLeft.value;
  275. }
  276. if (this._marginRight.isPixel) {
  277. this._currentMeasure.width -= this._marginRight.value;
  278. } else {
  279. this._currentMeasure.width -= parentWidth * this._marginRight.value;
  280. }
  281. if (this._marginTop.isPixel) {
  282. this._currentMeasure.top += this._marginTop.value;
  283. this._currentMeasure.height -= this._marginTop.value;
  284. } else {
  285. this._currentMeasure.top += parentHeight * this._marginTop.value;
  286. this._currentMeasure.height -= parentHeight * this._marginTop.value;
  287. }
  288. if (this._marginBottom.isPixel) {
  289. this._currentMeasure.height -= this._marginBottom.value;
  290. } else {
  291. this._currentMeasure.height -= parentHeight * this._marginBottom.value;
  292. }
  293. if (this._left.isPixel) {
  294. this._currentMeasure.left += this._left.value;
  295. } else {
  296. this._currentMeasure.left += parentWidth * this._left.value;
  297. }
  298. if (this._top.isPixel) {
  299. this._currentMeasure.top += this._top.value;
  300. } else {
  301. this._currentMeasure.top += parentHeight * this._top.value;
  302. }
  303. this._currentMeasure.left += x;
  304. this._currentMeasure.top += y;
  305. }
  306. protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  307. // Do nothing
  308. }
  309. public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  310. // Do nothing
  311. }
  312. protected _contains(x: number, y: number) : boolean {
  313. if (x < this._currentMeasure.left) {
  314. return false;
  315. }
  316. if (x > this._currentMeasure.left + this._currentMeasure.width) {
  317. return false;
  318. }
  319. if (y < this._currentMeasure.top) {
  320. return false;
  321. }
  322. if (y > this._currentMeasure.top + this._currentMeasure.height) {
  323. return false;
  324. }
  325. return true;
  326. }
  327. public _processPicking(x: number, y: number, type: number): boolean {
  328. if (!this._contains(x, y)) {
  329. return false;
  330. }
  331. this._processObservables(type);
  332. return true;
  333. }
  334. protected _processObservables(type: number): boolean {
  335. if (type === BABYLON.PointerEventTypes.POINTERMOVE && this.onPointerMoveObservable.hasObservers()) {
  336. this.onPointerMoveObservable.notifyObservers(this);
  337. var previousControlOver = this._host._lastControlOver;
  338. if (previousControlOver && previousControlOver !== this && previousControlOver.onPointerOutObservable.hasObservers()) {
  339. previousControlOver.onPointerOutObservable.notifyObservers(previousControlOver);
  340. }
  341. this._host._lastControlOver = this;
  342. return true;
  343. }
  344. if (type === BABYLON.PointerEventTypes.POINTERDOWN && this.onPointerDownObservable.hasObservers()) {
  345. this.onPointerDownObservable.notifyObservers(this);
  346. return true;
  347. }
  348. if (type === BABYLON.PointerEventTypes.POINTERUP && this.onPointerUpObservable.hasObservers()) {
  349. this.onPointerUpObservable.notifyObservers(this);
  350. return true;
  351. }
  352. return false;
  353. }
  354. private _prepareFont() {
  355. if (!this._fontFamily) {
  356. return;
  357. }
  358. this._font = this._fontSize + "px " + this._fontFamily;
  359. this._fontOffset = Control._GetFontOffset(this._font);
  360. this._markAsDirty();
  361. }
  362. // Statics
  363. private static _HORIZONTAL_ALIGNMENT_LEFT = 0;
  364. private static _HORIZONTAL_ALIGNMENT_RIGHT = 1;
  365. private static _HORIZONTAL_ALIGNMENT_CENTER = 2;
  366. private static _VERTICAL_ALIGNMENT_TOP = 0;
  367. private static _VERTICAL_ALIGNMENT_BOTTOM = 1;
  368. private static _VERTICAL_ALIGNMENT_CENTER = 2;
  369. public static get HORIZONTAL_ALIGNMENT_LEFT(): number {
  370. return Control._HORIZONTAL_ALIGNMENT_LEFT;
  371. }
  372. public static get HORIZONTAL_ALIGNMENT_RIGHT(): number {
  373. return Control._HORIZONTAL_ALIGNMENT_RIGHT;
  374. }
  375. public static get HORIZONTAL_ALIGNMENT_CENTER(): number {
  376. return Control._HORIZONTAL_ALIGNMENT_CENTER;
  377. }
  378. public static get VERTICAL_ALIGNMENT_TOP(): number {
  379. return Control._VERTICAL_ALIGNMENT_TOP;
  380. }
  381. public static get VERTICAL_ALIGNMENT_BOTTOM(): number {
  382. return Control._VERTICAL_ALIGNMENT_BOTTOM;
  383. }
  384. public static get VERTICAL_ALIGNMENT_CENTER(): number {
  385. return Control._VERTICAL_ALIGNMENT_CENTER;
  386. }
  387. private static _FontHeightSizes = {};
  388. public static _GetFontOffset(font: string): {ascent: number, height: number, descent: number} {
  389. if (Control._FontHeightSizes[font]) {
  390. return Control._FontHeightSizes[font];
  391. }
  392. var text = document.createElement("span");
  393. text.innerHTML = "Hg";
  394. text.style.font = font;
  395. var block = document.createElement("div");
  396. block.style.display = "inline-block";
  397. block.style.width = "1px";
  398. block.style.height = "0px";
  399. block.style.verticalAlign = "bottom";
  400. var div = document.createElement("div");
  401. div.appendChild(text);
  402. div.appendChild(block);
  403. document.body.appendChild(div);
  404. var fontAscent = 0;
  405. var fontHeight = 0;
  406. try {
  407. fontHeight = block.getBoundingClientRect().top - text.getBoundingClientRect().top;
  408. block.style.verticalAlign = "baseline";
  409. fontAscent = block.getBoundingClientRect().top - text.getBoundingClientRect().top;
  410. } finally {
  411. div.remove();
  412. }
  413. var result = { ascent: fontAscent, height: fontHeight, descent: fontHeight - fontAscent };
  414. Control._FontHeightSizes[font] = result;
  415. return result;
  416. };
  417. }
  418. }