babylon.gui.UIElement.ts 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960
  1. module BABYLON {
  2. export interface ICommand {
  3. canExecute(parameter: any): boolean;
  4. execute(parameter: any): void;
  5. canExecuteChanged: Observable<void>;
  6. }
  7. export class Command implements ICommand {
  8. constructor(execute: (p) => void, canExecute: (p) => boolean) {
  9. if (!execute) {
  10. throw Error("At least an execute lambda must be given at Command creation time");
  11. }
  12. this._canExecuteChanged = null;
  13. this._lastCanExecuteResult = null;
  14. this.execute = execute;
  15. this.canExecute = canExecute;
  16. }
  17. canExecute(parameter): boolean {
  18. let res = true;
  19. if (this._canExecute) {
  20. res = this._canExecute(parameter);
  21. }
  22. if (res !== this._lastCanExecuteResult) {
  23. if (this._canExecuteChanged && this._canExecuteChanged.hasObservers()) {
  24. this._canExecuteChanged.notifyObservers(null);
  25. }
  26. this._lastCanExecuteResult = res;
  27. }
  28. return res;
  29. }
  30. execute(parameter): void {
  31. this._execute(parameter);
  32. }
  33. get canExecuteChanged(): Observable<void> {
  34. if (!this._canExecuteChanged) {
  35. this._canExecuteChanged = new Observable<void>();
  36. }
  37. return this._canExecuteChanged;
  38. }
  39. private _lastCanExecuteResult: boolean;
  40. private _execute: (p) => void;
  41. private _canExecute: (p) => boolean;
  42. private _canExecuteChanged: Observable<void>;
  43. }
  44. export abstract class UIElement extends SmartPropertyBase {
  45. static get enabledState(): string {
  46. return UIElement._enableState;
  47. }
  48. static get disabledState(): string {
  49. return UIElement._disabledState;
  50. }
  51. static get mouseOverState(): string {
  52. return UIElement._mouseOverState;
  53. }
  54. static UIELEMENT_PROPCOUNT: number = 16;
  55. static parentProperty : Prim2DPropInfo;
  56. static widthProperty : Prim2DPropInfo;
  57. static heightProperty : Prim2DPropInfo;
  58. static minWidthProperty : Prim2DPropInfo;
  59. static minHeightProperty : Prim2DPropInfo;
  60. static maxWidthProperty : Prim2DPropInfo;
  61. static maxHeightProperty : Prim2DPropInfo;
  62. static actualWidthProperty : Prim2DPropInfo;
  63. static actualHeightProperty : Prim2DPropInfo;
  64. static marginProperty : Prim2DPropInfo;
  65. static paddingProperty : Prim2DPropInfo;
  66. static marginAlignmentProperty : Prim2DPropInfo;
  67. static paddingAlignmentProperty: Prim2DPropInfo;
  68. static isEnabledProperty : Prim2DPropInfo;
  69. static isFocusedProperty : Prim2DPropInfo;
  70. static isMouseOverProperty : Prim2DPropInfo;
  71. constructor(settings: {
  72. id ?: string,
  73. parent ?: UIElement,
  74. templateName ?: string,
  75. styleName ?: string,
  76. minWidth ?: number,
  77. minHeight ?: number,
  78. maxWidth ?: number,
  79. maxHeight ?: number,
  80. width ?: number,
  81. height ?: number,
  82. marginTop ?: number | string,
  83. marginLeft ?: number | string,
  84. marginRight ?: number | string,
  85. marginBottom ?: number | string,
  86. margin ?: number | string,
  87. marginHAlignment ?: number,
  88. marginVAlignment ?: number,
  89. marginAlignment ?: string,
  90. paddingTop ?: number | string,
  91. paddingLeft ?: number | string,
  92. paddingRight ?: number | string,
  93. paddingBottom ?: number | string,
  94. padding ?: string,
  95. paddingHAlignment?: number,
  96. paddingVAlignment?: number,
  97. paddingAlignment ?: string,
  98. }) {
  99. super();
  100. if (!settings) {
  101. throw Error("A settings object must be passed with at least either a parent or owner parameter");
  102. }
  103. let type = Tools.getFullClassName(this);
  104. this._ownerWindow = null;
  105. this._parent = null;
  106. this._visualPlaceholder = null;
  107. this._visualTemplateRoot = null;
  108. this._visualChildrenPlaceholder = null;
  109. this._hierarchyDepth = 0;
  110. this._renderingTemplateName = (settings.templateName != null) ? settings.templateName : GUIManager.DefaultTemplateName;
  111. this._style = (settings.styleName!=null) ? GUIManager.getStyle(type, settings.styleName) : null;
  112. this._flags = 0;
  113. this._id = (settings.id!=null) ? settings.id : null;
  114. this._uid = null;
  115. this._width = (settings.width != null) ? settings.width : null;
  116. this._height = (settings.height != null) ? settings.height : null;
  117. this._minWidth = (settings.minWidth != null) ? settings.minWidth : 0;
  118. this._minHeight = (settings.minHeight != null) ? settings.minHeight : 0;
  119. this._maxWidth = (settings.maxWidth != null) ? settings.maxWidth : Number.MAX_VALUE;
  120. this._maxHeight = (settings.maxHeight != null) ? settings.maxHeight : Number.MAX_VALUE;
  121. this._margin = null;
  122. this._padding = null;
  123. this._marginAlignment = null;
  124. this._setFlags(UIElement.flagIsVisible|UIElement.flagIsEnabled);
  125. // Default Margin Alignment for UIElement is stretch for horizontal/vertical and not left/bottom (which is the default for Canvas2D Primitives)
  126. //this.marginAlignment.horizontal = PrimitiveAlignment.AlignStretch;
  127. //this.marginAlignment.vertical = PrimitiveAlignment.AlignStretch;
  128. // Set the layout/margin stuffs
  129. if (settings.marginTop) {
  130. this.margin.setTop(settings.marginTop);
  131. }
  132. if (settings.marginLeft) {
  133. this.margin.setLeft(settings.marginLeft);
  134. }
  135. if (settings.marginRight) {
  136. this.margin.setRight(settings.marginRight);
  137. }
  138. if (settings.marginBottom) {
  139. this.margin.setBottom(settings.marginBottom);
  140. }
  141. if (settings.margin) {
  142. if (typeof settings.margin === "string") {
  143. this.margin.fromString(<string>settings.margin);
  144. } else {
  145. this.margin.fromUniformPixels(<number>settings.margin);
  146. }
  147. }
  148. if (settings.marginHAlignment) {
  149. this.marginAlignment.horizontal = settings.marginHAlignment;
  150. }
  151. if (settings.marginVAlignment) {
  152. this.marginAlignment.vertical = settings.marginVAlignment;
  153. }
  154. if (settings.marginAlignment) {
  155. this.marginAlignment.fromString(settings.marginAlignment);
  156. }
  157. if (settings.paddingTop) {
  158. this.padding.setTop(settings.paddingTop);
  159. }
  160. if (settings.paddingLeft) {
  161. this.padding.setLeft(settings.paddingLeft);
  162. }
  163. if (settings.paddingRight) {
  164. this.padding.setRight(settings.paddingRight);
  165. }
  166. if (settings.paddingBottom) {
  167. this.padding.setBottom(settings.paddingBottom);
  168. }
  169. if (settings.padding) {
  170. this.padding.fromString(settings.padding);
  171. }
  172. if (settings.paddingHAlignment) {
  173. this.paddingAlignment.horizontal = settings.paddingHAlignment;
  174. }
  175. if (settings.paddingVAlignment) {
  176. this.paddingAlignment.vertical = settings.paddingVAlignment;
  177. }
  178. if (settings.paddingAlignment) {
  179. this.paddingAlignment.fromString(settings.paddingAlignment);
  180. }
  181. if (settings.parent != null) {
  182. this._parent = settings.parent;
  183. this._hierarchyDepth = this._parent._hierarchyDepth + 1;
  184. }
  185. }
  186. public dispose(): boolean {
  187. if (this.isDisposed) {
  188. return false;
  189. }
  190. if (this._renderingTemplate) {
  191. this._renderingTemplate.detach();
  192. this._renderingTemplate = null;
  193. }
  194. super.dispose();
  195. // Don't set to null, it may upset somebody...
  196. this.animations.splice(0);
  197. return true;
  198. }
  199. /**
  200. * Animation array, more info: http://doc.babylonjs.com/tutorials/Animations
  201. */
  202. public animations: Animation[];
  203. /**
  204. * Returns as a new array populated with the Animatable used by the primitive. Must be overloaded by derived primitives.
  205. * Look at Sprite2D for more information
  206. */
  207. public getAnimatables(): IAnimatable[] {
  208. return new Array<IAnimatable>();
  209. }
  210. // TODO
  211. // PROPERTIES
  212. // Style
  213. // Id
  214. // Parent/Children
  215. // ActualWidth/Height, MinWidth/Height, MaxWidth/Height,
  216. // Alignment/Margin
  217. // Visibility, IsVisible
  218. // IsEnabled (is false, control is disabled, no interaction and a specific render state)
  219. // CacheMode of Visual Elements
  220. // Focusable/IsFocused
  221. // IsPointerCaptured, CapturePointer, IsPointerDirectlyOver, IsPointerOver. De-correlate mouse, stylus, touch?
  222. // ContextMenu
  223. // Cursor
  224. // DesiredSize
  225. // IsInputEnable ?
  226. // Opacity, OpacityMask ?
  227. // SnapToDevicePixels
  228. // Tag
  229. // ToolTip
  230. // METHODS
  231. // BringIntoView (for scrollable content, to move the scroll to bring the given element visible in the parent's area)
  232. // Capture/ReleaseCapture (mouse, touch, stylus)
  233. // Focus
  234. // PointFrom/ToScreen to translate coordinates
  235. // EVENTS
  236. // ContextMenuOpening/Closing/Changed
  237. // DragEnter/LeaveOver, Drop
  238. // Got/LostFocus
  239. // IsEnabledChanged
  240. // IsPointerOver/DirectlyOverChanged
  241. // IsVisibleChanged
  242. // KeyDown/Up
  243. // LayoutUpdated ?
  244. // Pointer related events
  245. // SizeChanged
  246. // ToolTipOpening/Closing
  247. public findById(id: string): UIElement {
  248. if (this._id === id) {
  249. return this;
  250. }
  251. let children = this._getChildren();
  252. for (let child of children) {
  253. let r = child.findById(id);
  254. if (r != null) {
  255. return r;
  256. }
  257. }
  258. }
  259. public get ownerWindow(): Window {
  260. return this._ownerWindow;
  261. }
  262. public get style(): string {
  263. if (!this.style) {
  264. return GUIManager.DefaultStyleName;
  265. }
  266. return this._style.name;
  267. }
  268. public set style(value: string) {
  269. if (this._style && (this._style.name === value)) {
  270. return;
  271. }
  272. let newStyle: UIElementStyle = null;
  273. if (value) {
  274. newStyle = GUIManager.getStyle(Tools.getFullClassName(this), value);
  275. if (!newStyle) {
  276. throw Error(`Couldn't find Style ${value} for UIElement ${Tools.getFullClassName(this)}`);
  277. }
  278. }
  279. if (this._style) {
  280. this._style.removeStyle(this);
  281. }
  282. if (newStyle) {
  283. newStyle.applyStyle(this);
  284. }
  285. this._style = newStyle;
  286. }
  287. /**
  288. * A string that identifies the UIElement.
  289. * The id is optional and there's possible collision with other UIElement's id as the uniqueness is not supported.
  290. */
  291. public get id(): string {
  292. return this._id;
  293. }
  294. public set id(value: string) {
  295. if (this._id === value) {
  296. return;
  297. }
  298. this._id = value;
  299. }
  300. /**
  301. * Return a unique id automatically generated.
  302. * This property is mainly used for serialization to ensure a perfect way of identifying a UIElement
  303. */
  304. public get uid(): string {
  305. if (!this._uid) {
  306. this._uid = Tools.RandomId();
  307. }
  308. return this._uid;
  309. }
  310. public get hierarchyDepth(): number {
  311. return this._hierarchyDepth;
  312. }
  313. @dependencyProperty(0, pi => UIElement.parentProperty = pi)
  314. public get parent(): UIElement {
  315. return this._parent;
  316. }
  317. public set parent(value: UIElement) {
  318. this._parent = value;
  319. }
  320. @dependencyProperty(1, pi => UIElement.widthProperty = pi)
  321. public get width(): number {
  322. return this._width;
  323. }
  324. public set width(value: number) {
  325. this._width = value;
  326. }
  327. @dependencyProperty(2, pi => UIElement.heightProperty = pi)
  328. public get height(): number {
  329. return this._height;
  330. }
  331. public set height(value: number) {
  332. this._height = value;
  333. }
  334. @dependencyProperty(3, pi => UIElement.minWidthProperty = pi)
  335. public get minWidth(): number {
  336. return this._minWidth;
  337. }
  338. public set minWidth(value: number) {
  339. this._minWidth = value;
  340. }
  341. @dependencyProperty(4, pi => UIElement.minHeightProperty = pi)
  342. public get minHheight(): number {
  343. return this._minHeight;
  344. }
  345. public set minHeight(value: number) {
  346. this._minHeight = value;
  347. }
  348. @dependencyProperty(5, pi => UIElement.maxWidthProperty = pi)
  349. public get maxWidth(): number {
  350. return this._maxWidth;
  351. }
  352. public set maxWidth(value: number) {
  353. this._maxWidth = value;
  354. }
  355. @dependencyProperty(6, pi => UIElement.maxHeightProperty = pi)
  356. public get maxHeight(): number {
  357. return this._maxHeight;
  358. }
  359. public set maxHeight(value: number) {
  360. this._maxHeight = value;
  361. }
  362. @dependencyProperty(7, pi => UIElement.actualWidthProperty = pi)
  363. public get actualWidth(): number {
  364. return this._actualWidth;
  365. }
  366. public set actualWidth(value: number) {
  367. this._actualWidth = value;
  368. }
  369. @dependencyProperty(8, pi => UIElement.actualHeightProperty = pi)
  370. public get actualHeight(): number {
  371. return this._actualHeight;
  372. }
  373. public set actualHeight(value: number) {
  374. this._actualHeight = value;
  375. }
  376. @dynamicLevelProperty(9, pi => UIElement.marginProperty = pi)
  377. /**
  378. * You can get/set a margin on the primitive through this property
  379. * @returns the margin object, if there was none, a default one is created and returned
  380. */
  381. public get margin(): PrimitiveThickness {
  382. if (!this._margin) {
  383. this._margin = new PrimitiveThickness(() => {
  384. if (!this.parent) {
  385. return null;
  386. }
  387. return this.parent.margin;
  388. });
  389. }
  390. return this._margin;
  391. }
  392. public set margin(value: PrimitiveThickness) {
  393. this.margin.copyFrom(value);
  394. }
  395. public get _hasMargin(): boolean {
  396. return (this._margin !== null && !this._margin.isDefault) || (this._marginAlignment !== null && !this._marginAlignment.isDefault);
  397. }
  398. @dynamicLevelProperty(10, pi => UIElement.paddingProperty = pi)
  399. /**
  400. * You can get/set a margin on the primitive through this property
  401. * @returns the margin object, if there was none, a default one is created and returned
  402. */
  403. public get padding(): PrimitiveThickness {
  404. if (!this._padding) {
  405. this._padding = new PrimitiveThickness(() => {
  406. if (!this.parent) {
  407. return null;
  408. }
  409. return this.parent.padding;
  410. });
  411. }
  412. return this._padding;
  413. }
  414. public set padding(value: PrimitiveThickness) {
  415. this.padding.copyFrom(value);
  416. }
  417. private get _hasPadding(): boolean {
  418. return this._padding !== null && !this._padding.isDefault;
  419. }
  420. @dynamicLevelProperty(11, pi => UIElement.marginAlignmentProperty = pi)
  421. /**
  422. * You can get/set the margin alignment through this property
  423. */
  424. public get marginAlignment(): PrimitiveAlignment {
  425. if (!this._marginAlignment) {
  426. this._marginAlignment = new PrimitiveAlignment();
  427. }
  428. return this._marginAlignment;
  429. }
  430. public set marginAlignment(value: PrimitiveAlignment) {
  431. this.marginAlignment.copyFrom(value);
  432. }
  433. /**
  434. * Check if there a marginAlignment specified (non null and not default)
  435. */
  436. public get _hasMarginAlignment(): boolean {
  437. return (this._marginAlignment !== null && !this._marginAlignment.isDefault);
  438. }
  439. @dynamicLevelProperty(12, pi => UIElement.paddingAlignmentProperty = pi)
  440. /**
  441. * You can get/set the margin alignment through this property
  442. */
  443. public get paddingAlignment(): PrimitiveAlignment {
  444. if (!this._paddingAlignment) {
  445. this._paddingAlignment = new PrimitiveAlignment();
  446. }
  447. return this._paddingAlignment;
  448. }
  449. public set paddingAlignment(value: PrimitiveAlignment) {
  450. this.paddingAlignment.copyFrom(value);
  451. }
  452. /**
  453. * Check if there a marginAlignment specified (non null and not default)
  454. */
  455. public get _hasPaddingAlignment(): boolean {
  456. return (this._paddingAlignment !== null && !this._paddingAlignment.isDefault);
  457. }
  458. public get isVisible(): boolean {
  459. return this._isFlagSet(UIElement.flagIsVisible);
  460. }
  461. public set isVisible(value: boolean) {
  462. if (this.isVisible === value) {
  463. return;
  464. }
  465. this._visualPlaceholder.levelVisible = value;
  466. this._changeFlags(UIElement.flagIsVisible, value);
  467. }
  468. @dynamicLevelProperty(13, pi => UIElement.isEnabledProperty = pi)
  469. /**
  470. * True if the UIElement is enabled, false if it's disabled.
  471. * User interaction is not possible if the UIElement is not enabled
  472. */
  473. public get isEnabled(): boolean {
  474. return this._isFlagSet(UIElement.flagIsEnabled);
  475. }
  476. public set isEnabled(value: boolean) {
  477. this._changeFlags(UIElement.flagIsEnabled, value);
  478. }
  479. @dynamicLevelProperty(14, pi => UIElement.isFocusedProperty = pi)
  480. /**
  481. * True if the UIElement has the focus, false if it doesn't
  482. */
  483. public get isFocused(): boolean {
  484. return this._isFlagSet(UIElement.flagIsFocus);
  485. }
  486. public set isFocused(value: boolean) {
  487. // If the UIElement doesn't accept focus, set it on its parent
  488. if (!this.isFocusable) {
  489. let p = this.parent;
  490. if (!p) {
  491. return;
  492. }
  493. p.isFocused = value;
  494. }
  495. // If the focus is being set, notify the Focus Manager
  496. if (value) {
  497. this.ownerWindow.focusManager.setFocusOn(this, this.getFocusScope());
  498. }
  499. this._changeFlags(UIElement.flagIsFocus, value);
  500. }
  501. @dynamicLevelProperty(15, pi => UIElement.isMouseOverProperty = pi)
  502. /**
  503. * True if the UIElement has the mouse over it
  504. */
  505. public get isMouseOver(): boolean {
  506. return this._isFlagSet(UIElement.flagIsMouseOver);
  507. }
  508. public set isMouseOver(value: boolean) {
  509. this._changeFlags(UIElement.flagIsMouseOver, value);
  510. }
  511. public get isFocusScope(): boolean {
  512. return this._isFlagSet(UIElement.flagIsFocusScope);
  513. }
  514. public set isFocusScope(value: boolean) {
  515. this._changeFlags(UIElement.flagIsFocusScope, value);
  516. }
  517. public get isFocusable(): boolean {
  518. return this._isFlagSet(UIElement.flagIsFocusable);
  519. }
  520. public set isFocusable(value: boolean) {
  521. this._changeFlags(UIElement.flagIsFocusable, value);
  522. }
  523. // Look for the nearest parent which is the focus scope. Should always return something as the Window UIElement which is the root of all UI Tree is focus scope (unless the user disable it)
  524. protected getFocusScope(): UIElement {
  525. if (this.isFocusScope) {
  526. return this;
  527. }
  528. let p = this.parent;
  529. if (!p) {
  530. return null;
  531. }
  532. return p.getFocusScope();
  533. }
  534. /**
  535. * Check if a given flag is set
  536. * @param flag the flag value
  537. * @return true if set, false otherwise
  538. */
  539. public _isFlagSet(flag: number): boolean {
  540. return (this._flags & flag) !== 0;
  541. }
  542. /**
  543. * Check if all given flags are set
  544. * @param flags the flags ORed
  545. * @return true if all the flags are set, false otherwise
  546. */
  547. public _areAllFlagsSet(flags: number): boolean {
  548. return (this._flags & flags) === flags;
  549. }
  550. /**
  551. * Check if at least one flag of the given flags is set
  552. * @param flags the flags ORed
  553. * @return true if at least one flag is set, false otherwise
  554. */
  555. public _areSomeFlagsSet(flags: number): boolean {
  556. return (this._flags & flags) !== 0;
  557. }
  558. /**
  559. * Clear the given flags
  560. * @param flags the flags to clear
  561. */
  562. public _clearFlags(flags: number) {
  563. this._flags &= ~flags;
  564. }
  565. /**
  566. * Set the given flags to true state
  567. * @param flags the flags ORed to set
  568. * @return the flags state before this call
  569. */
  570. public _setFlags(flags: number): number {
  571. let cur = this._flags;
  572. this._flags |= flags;
  573. return cur;
  574. }
  575. /**
  576. * Change the state of the given flags
  577. * @param flags the flags ORed to change
  578. * @param state true to set them, false to clear them
  579. */
  580. public _changeFlags(flags: number, state: boolean) {
  581. if (state) {
  582. this._flags |= flags;
  583. } else {
  584. this._flags &= ~flags;
  585. }
  586. }
  587. private _assignTemplate(templateName: string) {
  588. if (!templateName) {
  589. templateName = GUIManager.DefaultTemplateName;
  590. }
  591. let className = Tools.getFullClassName(this);
  592. if (!className) {
  593. throw Error("Couldn't access class name of this UIElement, you have to decorate the type with the className decorator");
  594. }
  595. let factory = GUIManager.getRenderingTemplate(className, templateName);
  596. if (!factory) {
  597. throw Error(`Couldn't get the renderingTemplate ${templateName} of class ${className}`);
  598. }
  599. this._renderingTemplateName = templateName;
  600. this._renderingTemplate = factory();
  601. this._renderingTemplate.attach(this);
  602. }
  603. public _createVisualTree() {
  604. let parentPrim: Prim2DBase = this.ownerWindow.canvas;
  605. if (this.parent) {
  606. parentPrim = this.parent.visualChildrenPlaceholder;
  607. }
  608. if (!this._renderingTemplate) {
  609. this._assignTemplate(this._renderingTemplateName);
  610. }
  611. this._visualPlaceholder = new Group2D({ parent: parentPrim, id: `GUI ${Tools.getClassName(this)} RootGroup of ${this.id}`});
  612. let p = this._visualPlaceholder;
  613. p.addExternalData<UIElement>("_GUIOwnerElement_", this);
  614. p.dataSource = this;
  615. p.createSimpleDataBinding(Prim2DBase.widthProperty , "width" , DataBinding.MODE_ONEWAY);
  616. p.createSimpleDataBinding(Prim2DBase.heightProperty , "height" , DataBinding.MODE_ONEWAY);
  617. p.createSimpleDataBinding(Prim2DBase.actualWidthProperty , "actualWidth" , DataBinding.MODE_ONEWAYTOSOURCE);
  618. p.createSimpleDataBinding(Prim2DBase.actualHeightProperty , "actualHeight" , DataBinding.MODE_ONEWAYTOSOURCE);
  619. p.createSimpleDataBinding(Prim2DBase.marginProperty , "margin" , DataBinding.MODE_ONEWAY);
  620. p.createSimpleDataBinding(Prim2DBase.marginAlignmentProperty, "marginAlignment", DataBinding.MODE_ONEWAY);
  621. this.createVisualTree();
  622. }
  623. public _patchUIElement(ownerWindow: Window, parent: UIElement) {
  624. if (ownerWindow) {
  625. if (!this._ownerWindow) {
  626. ownerWindow._registerVisualToBuild(this);
  627. }
  628. this._ownerWindow = ownerWindow;
  629. }
  630. this._parent = parent;
  631. if (parent) {
  632. this._hierarchyDepth = parent.hierarchyDepth + 1;
  633. }
  634. let children = this._getChildren();
  635. if (children) {
  636. for (let curChild of children) {
  637. curChild._patchUIElement(ownerWindow, this);
  638. }
  639. }
  640. }
  641. // Overload the SmartPropertyBase's method to provide the additional logic of returning the parent's dataSource if there's no dataSource specified at this level.
  642. protected _getDataSource(): IPropertyChanged {
  643. let levelDS = super._getDataSource();
  644. if (levelDS != null) {
  645. return levelDS;
  646. }
  647. let p = this.parent;
  648. if (p != null) {
  649. return p.dataSource;
  650. }
  651. return null;
  652. }
  653. protected createVisualTree() {
  654. let res = this._renderingTemplate.createVisualTree(this, this._visualPlaceholder);
  655. this._visualTemplateRoot = res.root;
  656. this._visualChildrenPlaceholder = res.contentPlaceholder;
  657. }
  658. protected get visualPlaceholder(): Prim2DBase {
  659. return this._visualPlaceholder;
  660. }
  661. protected get visualTemplateRoot(): Prim2DBase {
  662. return this._visualTemplateRoot;
  663. }
  664. protected get visualChildrenPlaceholder(): Prim2DBase {
  665. return this._visualChildrenPlaceholder;
  666. }
  667. protected get _position(): Vector2 { return null; } // TODO use abstract keyword when TS 2.0 will be approved
  668. protected abstract _getChildren(): Array<UIElement>;
  669. public static flagVisualToBuild = 0x0000001;
  670. public static flagIsVisible = 0x0000002;
  671. public static flagIsFocus = 0x0000004;
  672. public static flagIsFocusScope = 0x0000008;
  673. public static flagIsFocusable = 0x0000010;
  674. public static flagIsEnabled = 0x0000020;
  675. public static flagIsMouseOver = 0x0000040;
  676. protected _visualPlaceholder: Group2D;
  677. protected _visualTemplateRoot: Prim2DBase;
  678. protected _visualChildrenPlaceholder: Prim2DBase;
  679. private _renderingTemplateName: string;
  680. protected _renderingTemplate: UIElementRenderingTemplateBase;
  681. private _parent: UIElement;
  682. private _hierarchyDepth: number;
  683. private _flags: number;
  684. private _style: UIElementStyle;
  685. private _ownerWindow: Window;
  686. private _id: string;
  687. private _uid: string;
  688. private _actualWidth: number;
  689. private _actualHeight: number;
  690. private _minWidth: number;
  691. private _minHeight: number;
  692. private _maxWidth: number;
  693. private _maxHeight: number;
  694. private _width: number;
  695. private _height: number;
  696. private _margin: PrimitiveThickness;
  697. private _padding: PrimitiveThickness;
  698. private _marginAlignment: PrimitiveAlignment;
  699. private _paddingAlignment: PrimitiveAlignment;
  700. private static _enableState = "Enabled";
  701. private static _disabledState = "Disabled";
  702. private static _mouseOverState = "MouseOver";
  703. }
  704. export abstract class UIElementStyle {
  705. abstract removeStyle(uiel: UIElement);
  706. abstract applyStyle(uiel: UIElement);
  707. get name(): string { return null; } // TODO use abstract keyword when TS 2.0 will be approved
  708. }
  709. export class GUIManager {
  710. /////////////////////////////////////////////////////////////////////////////////////////////////////
  711. // DATA TEMPLATE MANAGER
  712. static registerDataTemplate(className: string, factory: (parent: UIElement, dataObject: any) => UIElement) {
  713. }
  714. // DATA TEMPLATE MANAGER
  715. /////////////////////////////////////////////////////////////////////////////////////////////////////
  716. /////////////////////////////////////////////////////////////////////////////////////////////////////
  717. // STYLE MANAGER
  718. static getStyle(uiElType: string, styleName: string): UIElementStyle {
  719. let styles = GUIManager.stylesByUIElement.get(uiElType);
  720. if (!styles) {
  721. throw Error(`The type ${uiElType} is unknown, no style were registered for it.`);
  722. }
  723. let style = styles.get(styleName);
  724. if (!style) {
  725. throw Error(`Couldn't find Template ${styleName} of UIElement type ${uiElType}`);
  726. }
  727. return style;
  728. }
  729. static registerStyle(uiElType: string, templateName: string, style: UIElementStyle) {
  730. let templates = GUIManager.stylesByUIElement.getOrAddWithFactory(uiElType, () => new StringDictionary<UIElementStyle>());
  731. if (templates.contains(templateName)) {
  732. templates[templateName] = style;
  733. } else {
  734. templates.add(templateName, style);
  735. }
  736. }
  737. static stylesByUIElement: StringDictionary<StringDictionary<UIElementStyle>> = new StringDictionary<StringDictionary<UIElementStyle>>();
  738. public static get DefaultStyleName(): string {
  739. return GUIManager._defaultStyleName;
  740. }
  741. public static set DefaultStyleName(value: string) {
  742. GUIManager._defaultStyleName = value;
  743. }
  744. // STYLE MANAGER
  745. /////////////////////////////////////////////////////////////////////////////////////////////////////
  746. /////////////////////////////////////////////////////////////////////////////////////////////////////
  747. // RENDERING TEMPLATE MANAGER
  748. static getRenderingTemplate(uiElType: string, templateName: string): () => UIElementRenderingTemplateBase {
  749. let templates = GUIManager.renderingTemplatesByUIElement.get(uiElType);
  750. if (!templates) {
  751. throw Error(`The type ${uiElType} is unknown, no Rendering Template were registered for it.`);
  752. }
  753. let templateFactory = templates.get(templateName);
  754. if (!templateFactory) {
  755. throw Error(`Couldn't find Template ${templateName} of UI Element type ${uiElType}`);
  756. }
  757. return templateFactory;
  758. }
  759. static registerRenderingTemplate(uiElType: string, templateName: string, factory: () => UIElementRenderingTemplateBase) {
  760. let templates = GUIManager.renderingTemplatesByUIElement.getOrAddWithFactory(uiElType, () => new StringDictionary<() => UIElementRenderingTemplateBase>());
  761. if (templates.contains(templateName)) {
  762. templates[templateName] = factory;
  763. } else {
  764. templates.add(templateName, factory);
  765. }
  766. }
  767. static renderingTemplatesByUIElement: StringDictionary<StringDictionary<() => UIElementRenderingTemplateBase>> = new StringDictionary<StringDictionary<() => UIElementRenderingTemplateBase>>();
  768. public static get DefaultTemplateName(): string {
  769. return GUIManager._defaultTemplateName;
  770. }
  771. public static set DefaultTemplateName(value: string) {
  772. GUIManager._defaultTemplateName = value;
  773. }
  774. // RENDERING TEMPLATE MANAGER
  775. /////////////////////////////////////////////////////////////////////////////////////////////////////
  776. private static _defaultTemplateName = "Default";
  777. private static _defaultStyleName = "Default";
  778. }
  779. export abstract class UIElementRenderingTemplateBase {
  780. attach(owner: UIElement) {
  781. this._owner = owner;
  782. }
  783. detach() {
  784. }
  785. public get owner(): UIElement {
  786. return this._owner;
  787. }
  788. abstract createVisualTree(owner: UIElement, visualPlaceholder: Group2D): { root: Prim2DBase, contentPlaceholder: Prim2DBase };
  789. private _owner: UIElement;
  790. }
  791. export function registerWindowRenderingTemplate(uiElType: string, templateName: string, factory: () => UIElementRenderingTemplateBase): (target: Object) => void {
  792. return () => {
  793. GUIManager.registerRenderingTemplate(uiElType, templateName, factory);
  794. }
  795. }
  796. }