babylon.gui.window.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. module BABYLON {
  2. class FocusScopeData {
  3. constructor(focusScope: UIElement) {
  4. this.focusScope = focusScope;
  5. this.focusedElement = null;
  6. }
  7. focusScope: UIElement;
  8. focusedElement: UIElement;
  9. }
  10. export class FocusManager {
  11. constructor() {
  12. this._focusScopes = new StringDictionary<FocusScopeData>();
  13. this._rootScope = new FocusScopeData(null);
  14. this._activeScope = null;
  15. }
  16. public setFocusOn(el: UIElement, focusScope: UIElement) {
  17. let fsd = (focusScope != null) ? this._focusScopes.getOrAddWithFactory(focusScope.uid, k => new FocusScopeData(focusScope)) : this._rootScope;
  18. if (fsd.focusedElement !== el) {
  19. // Remove focus from current
  20. if (fsd.focusedElement) {
  21. fsd.focusedElement.isFocused = false;
  22. }
  23. fsd.focusedElement = el;
  24. }
  25. if (this._activeScope !== fsd) {
  26. this._activeScope = fsd;
  27. }
  28. }
  29. private _rootScope: FocusScopeData;
  30. private _focusScopes: StringDictionary<FocusScopeData>;
  31. private _activeScope: FocusScopeData;
  32. }
  33. class GUISceneData {
  34. constructor(scene: Scene) {
  35. this.scene = scene;
  36. this.screenSpaceCanvas = new ScreenSpaceCanvas2D(scene, { id: "GUI Canvas", cachingStrategy: Canvas2D.CACHESTRATEGY_DONTCACHE });
  37. this.focusManager = new FocusManager();
  38. }
  39. screenSpaceCanvas: ScreenSpaceCanvas2D;
  40. scene: Scene;
  41. focusManager: FocusManager;
  42. }
  43. @className("Window", "BABYLON")
  44. export class Window extends ContentControl {
  45. static WINDOW_PROPCOUNT = ContentControl.CONTENTCONTROL_PROPCOUNT + 4;
  46. static leftProperty: Prim2DPropInfo;
  47. static bottomProperty: Prim2DPropInfo;
  48. static positionProperty: Prim2DPropInfo;
  49. static isActiveProperty: Prim2DPropInfo;
  50. constructor(scene: Scene, settings?: {
  51. id ?: string,
  52. templateName ?: string,
  53. styleName ?: string,
  54. content ?: any,
  55. left ?: number,
  56. bottom ?: number,
  57. minWidth ?: number,
  58. minHeight ?: number,
  59. maxWidth ?: number,
  60. maxHeight ?: number,
  61. width ?: number,
  62. height ?: number,
  63. worldPosition ?: Vector3,
  64. worldRotation ?: Quaternion,
  65. marginTop ?: number | string,
  66. marginLeft ?: number | string,
  67. marginRight ?: number | string,
  68. marginBottom ?: number | string,
  69. margin ?: number | string,
  70. marginHAlignment ?: number,
  71. marginVAlignment ?: number,
  72. marginAlignment ?: string,
  73. paddingTop ?: number | string,
  74. paddingLeft ?: number | string,
  75. paddingRight ?: number | string,
  76. paddingBottom ?: number | string,
  77. padding ?: string,
  78. paddingHAlignment?: number,
  79. paddingVAlignment?: number,
  80. paddingAlignment ?: string,
  81. }) {
  82. if (!settings) {
  83. settings = {};
  84. }
  85. super(settings);
  86. // Per default a Window is focus scope
  87. this.isFocusScope = true;
  88. this.isActive = false;
  89. if (!this._UIElementVisualToBuildList) {
  90. this._UIElementVisualToBuildList = new Array<UIElement>();
  91. }
  92. // Patch the owner and also the parent property through the whole tree
  93. this._patchUIElement(this, null);
  94. // Screen Space UI
  95. if (!settings.worldPosition && !settings.worldRotation) {
  96. this._sceneData = Window.getSceneData(scene);
  97. this._canvas = this._sceneData.screenSpaceCanvas;
  98. this._isWorldSpaceCanvas = false;
  99. this._left = (settings.left != null) ? settings.left : 0;
  100. this._bottom = (settings.bottom != null) ? settings.bottom : 0;
  101. }
  102. // WorldSpace UI
  103. else {
  104. let w = (settings.width == null) ? 100 : settings.width;
  105. let h = (settings.height == null) ? 100 : settings.height;
  106. let wpos = (settings.worldPosition == null) ? Vector3.Zero() : settings.worldPosition;
  107. let wrot = (settings.worldRotation == null) ? Quaternion.Identity() : settings.worldRotation;
  108. this._canvas = new WorldSpaceCanvas2D(scene, new Size(w, h), { id: "GUI Canvas", cachingStrategy: Canvas2D.CACHESTRATEGY_DONTCACHE, worldPosition: wpos, worldRotation: wrot });
  109. this._isWorldSpaceCanvas = true;
  110. }
  111. this._renderObserver = this._canvas.renderObservable.add((e, s) => this._canvasPreRender(), Canvas2D.RENDEROBSERVABLE_PRE);
  112. this._disposeObserver = this._canvas.disposeObservable.add((e, s) => this._canvasDisposed());
  113. this._canvas.propertyChanged.add((e, s) => {
  114. if (e.propertyName === "overPrim") {
  115. this._overPrimChanged(e.oldValue, e.newValue);
  116. }
  117. });
  118. this._mouseOverUIElement = null;
  119. }
  120. public get canvas(): Canvas2D {
  121. return this._canvas;
  122. }
  123. @dependencyProperty(ContentControl.CONTENTCONTROL_PROPCOUNT + 0, pi => Window.leftProperty = pi)
  124. public get left(): number {
  125. return this._left;
  126. }
  127. public set left(value: number) {
  128. let old = new Vector2(this._left, this._bottom);
  129. this._left = value;
  130. this.onPropertyChanged("_position", old, this._position);
  131. }
  132. @dependencyProperty(ContentControl.CONTENTCONTROL_PROPCOUNT + 1, pi => Window.bottomProperty = pi)
  133. public get bottom(): number {
  134. return this._bottom;
  135. }
  136. public set bottom(value: number) {
  137. let old = new Vector2(this._left, this._bottom);
  138. this._bottom = value;
  139. this.onPropertyChanged("_position", old, this._position);
  140. }
  141. @dependencyProperty(ContentControl.CONTENTCONTROL_PROPCOUNT + 2, pi => Window.positionProperty = pi)
  142. public get position(): Vector2 {
  143. return this._position;
  144. }
  145. public set position(value: Vector2) {
  146. this._left = value.x;
  147. this._bottom = value.y;
  148. }
  149. @dependencyProperty(ContentControl.CONTENTCONTROL_PROPCOUNT + 3, pi => Window.isActiveProperty = pi)
  150. public get isActive(): boolean {
  151. return this._isActive;
  152. }
  153. public set isActive(value: boolean) {
  154. this._isActive = value;
  155. }
  156. public get focusManager(): FocusManager {
  157. return this._sceneData.focusManager;
  158. }
  159. protected get _position(): Vector2 {
  160. return new Vector2(this.left, this.bottom);
  161. }
  162. protected createVisualTree() {
  163. super.createVisualTree();
  164. let p = this._visualPlaceholder;
  165. p.createSimpleDataBinding(Group2D.positionProperty, "position");
  166. }
  167. public _registerVisualToBuild(uiel: UIElement) {
  168. if (uiel._isFlagSet(UIElement.flagVisualToBuild)) {
  169. return;
  170. }
  171. if (!this._UIElementVisualToBuildList) {
  172. this._UIElementVisualToBuildList = new Array<UIElement>();
  173. }
  174. this._UIElementVisualToBuildList.push(uiel);
  175. uiel._setFlags(UIElement.flagVisualToBuild);
  176. }
  177. private _overPrimChanged(oldPrim: Prim2DBase, newPrim: Prim2DBase) {
  178. let curOverEl = this._mouseOverUIElement;
  179. let newOverEl: UIElement = null;
  180. let curGroup = newPrim ? newPrim.traverseUp(p => p instanceof Group2D) : null;
  181. while (curGroup) {
  182. let uiel = curGroup.getExternalData<UIElement>("_GUIOwnerElement_");
  183. if (uiel) {
  184. newOverEl = uiel;
  185. break;
  186. }
  187. curGroup = curGroup.parent ? curGroup.parent.traverseUp(p => p instanceof Group2D) : null;
  188. }
  189. if (curOverEl === newOverEl) {
  190. return;
  191. }
  192. if (curOverEl) {
  193. curOverEl.isMouseOver = false;
  194. }
  195. if (newOverEl) {
  196. newOverEl.isMouseOver = true;
  197. }
  198. this._mouseOverUIElement = newOverEl;
  199. }
  200. private _canvasPreRender() {
  201. // Check if we have visual to create
  202. if (this._UIElementVisualToBuildList.length > 0) {
  203. // Sort the UI Element to get the highest (so lowest hierarchy depth) in the hierarchy tree first
  204. let sortedElementList = this._UIElementVisualToBuildList.sort((a, b) => a.hierarchyDepth - b.hierarchyDepth);
  205. for (let el of sortedElementList) {
  206. el._createVisualTree();
  207. }
  208. this._UIElementVisualToBuildList.splice(0);
  209. }
  210. }
  211. private _canvasDisposed() {
  212. this._canvas.disposeObservable.remove(this._disposeObserver);
  213. this._canvas.renderObservable.remove(this._renderObserver);
  214. }
  215. private _sceneData: GUISceneData;
  216. private _canvas: Canvas2D;
  217. private _left: number;
  218. private _bottom: number;
  219. private _isActive: boolean;
  220. private _isWorldSpaceCanvas: boolean;
  221. private _renderObserver: Observer<Canvas2D>;
  222. private _disposeObserver: Observer<SmartPropertyBase>;
  223. private _UIElementVisualToBuildList: Array<UIElement>;
  224. private _mouseOverUIElement: UIElement;
  225. private static getSceneData(scene: Scene): GUISceneData {
  226. return Window._sceneData.getOrAddWithFactory(scene.uid, k => new GUISceneData(scene));
  227. }
  228. private static _sceneData: StringDictionary<GUISceneData> = new StringDictionary<GUISceneData>();
  229. }
  230. @registerWindowRenderingTemplate("BABYLON.Window", "Default", () => new DefaultWindowRenderingTemplate ())
  231. export class DefaultWindowRenderingTemplate extends UIElementRenderingTemplateBase {
  232. createVisualTree(owner: UIElement, visualPlaceholder: Group2D): { root: Prim2DBase; contentPlaceholder: Prim2DBase } {
  233. let r = new Rectangle2D({ parent: visualPlaceholder, fill: "#808080FF" });
  234. return { root: r, contentPlaceholder: r };
  235. }
  236. }
  237. }