babylon.canvas2dLayoutEngine.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. module BABYLON {
  2. export interface ILayoutData {
  3. }
  4. @className("LayoutEngineBase", "BABYLON")
  5. /**
  6. * This is the base class you have to extend in order to implement your own Layout Engine.
  7. * Note that for performance reason, each different Layout Engine type can be exposed as one/many singleton or must be instanced each time.
  8. * If data has to be associated to a given primitive you can use the SmartPropertyPrim.addExternalData API to do it.
  9. */
  10. export class LayoutEngineBase implements ILockable {
  11. constructor() {
  12. this.layoutDirtyOnPropertyChangedMask = 0;
  13. }
  14. public newChild(child: Prim2DBase, data: ILayoutData) {
  15. }
  16. public updateLayout(prim: Prim2DBase) {
  17. }
  18. public get isChildPositionAllowed(): boolean {
  19. return false;
  20. }
  21. isLocked(): boolean {
  22. return this._isLocked;
  23. }
  24. lock(): boolean {
  25. if (this._isLocked) {
  26. return false;
  27. }
  28. this._isLocked = true;
  29. return true;
  30. }
  31. public layoutDirtyOnPropertyChangedMask;
  32. private _isLocked: boolean;
  33. }
  34. @className("CanvasLayoutEngine", "BABYLON")
  35. /**
  36. * The default Layout Engine, primitive are positioning into a Canvas, using their x/y coordinates.
  37. * This layout must be used as a Singleton through the CanvasLayoutEngine.Singleton property.
  38. */
  39. export class CanvasLayoutEngine extends LayoutEngineBase {
  40. private static _singleton: CanvasLayoutEngine = null;
  41. public static get Singleton(): CanvasLayoutEngine {
  42. if (!CanvasLayoutEngine._singleton) {
  43. CanvasLayoutEngine._singleton = new CanvasLayoutEngine();
  44. }
  45. return CanvasLayoutEngine._singleton;
  46. }
  47. constructor() {
  48. super();
  49. this.layoutDirtyOnPropertyChangedMask = Prim2DBase.sizeProperty.flagId | Prim2DBase.actualSizeProperty.flagId;
  50. }
  51. // A very simple (no) layout computing...
  52. // The Canvas and its direct children gets the Canvas' size as Layout Area
  53. // Indirect children have their Layout Area to the actualSize (margin area) of their parent
  54. public updateLayout(prim: Prim2DBase) {
  55. // If this prim is layoutDiry we update its layoutArea and also the one of its direct children
  56. if (prim._isFlagSet(SmartPropertyPrim.flagLayoutDirty)) {
  57. for (let child of prim.children) {
  58. this._doUpdate(child);
  59. }
  60. prim._clearFlags(SmartPropertyPrim.flagLayoutDirty);
  61. }
  62. }
  63. private _doUpdate(prim: Prim2DBase) {
  64. // Canvas ?
  65. if (prim instanceof Canvas2D) {
  66. prim.layoutArea = prim.actualSize; //.multiplyByFloats(prim.scaleX, prim.scaleY);
  67. }
  68. // Direct child of Canvas ?
  69. else if (prim.parent instanceof Canvas2D) {
  70. prim.layoutArea = prim.owner.actualSize; //.multiplyByFloats(prim.owner.scaleX, prim.owner.scaleY);
  71. }
  72. // Indirect child of Canvas
  73. else {
  74. prim.layoutArea = prim.parent.contentArea;
  75. }
  76. }
  77. get isChildPositionAllowed(): boolean {
  78. return true;
  79. }
  80. }
  81. @className("StackPanelLayoutEngine", "BABYLON")
  82. /**
  83. * A stack panel layout. Primitive will be stack either horizontally or vertically.
  84. * This Layout type must be used as a Singleton, use the StackPanelLayoutEngine.Horizontal for an horizontal stack panel or StackPanelLayoutEngine.Vertical for a vertical one.
  85. */
  86. export class StackPanelLayoutEngine extends LayoutEngineBase {
  87. constructor() {
  88. super();
  89. this.layoutDirtyOnPropertyChangedMask = Prim2DBase.sizeProperty.flagId | Prim2DBase.actualSizeProperty.flagId;
  90. }
  91. public static get Horizontal(): StackPanelLayoutEngine {
  92. if (!StackPanelLayoutEngine._horizontal) {
  93. StackPanelLayoutEngine._horizontal = new StackPanelLayoutEngine();
  94. StackPanelLayoutEngine._horizontal.isHorizontal = true;
  95. StackPanelLayoutEngine._horizontal.lock();
  96. }
  97. return StackPanelLayoutEngine._horizontal;
  98. }
  99. public static get Vertical(): StackPanelLayoutEngine {
  100. if (!StackPanelLayoutEngine._vertical) {
  101. StackPanelLayoutEngine._vertical = new StackPanelLayoutEngine();
  102. StackPanelLayoutEngine._vertical.isHorizontal = false;
  103. StackPanelLayoutEngine._vertical.lock();
  104. }
  105. return StackPanelLayoutEngine._vertical;
  106. }
  107. private static _horizontal: StackPanelLayoutEngine = null;
  108. private static _vertical: StackPanelLayoutEngine = null;
  109. get isHorizontal(): boolean {
  110. return this._isHorizontal;
  111. }
  112. set isHorizontal(val: boolean) {
  113. if (this.isLocked()) {
  114. return;
  115. }
  116. this._isHorizontal = val;
  117. }
  118. private _isHorizontal: boolean = true;
  119. private static dstOffset = Vector4.Zero();
  120. private static dstArea = Size.Zero();
  121. public updateLayout(prim: Prim2DBase) {
  122. if (prim._isFlagSet(SmartPropertyPrim.flagLayoutDirty)) {
  123. let x = 0;
  124. let y = 0;
  125. let h = this.isHorizontal;
  126. let max = 0;
  127. for (let child of prim.children) {
  128. if (child._isFlagSet(SmartPropertyPrim.flagNoPartOfLayout)) {
  129. continue;
  130. }
  131. let layoutArea: Size;
  132. if (child._hasMargin) {
  133. child.margin.computeWithAlignment(prim.layoutArea, child.actualSize, child.marginAlignment, child.actualScale, StackPanelLayoutEngine.dstOffset, StackPanelLayoutEngine.dstArea, true);
  134. layoutArea = StackPanelLayoutEngine.dstArea.clone();
  135. child.layoutArea = layoutArea;
  136. } else {
  137. layoutArea = child.layoutArea;
  138. child.margin.computeArea(child.actualSize, layoutArea);
  139. }
  140. max = Math.max(max, h ? layoutArea.height : layoutArea.width);
  141. }
  142. for (let child of prim.children) {
  143. if (child._isFlagSet(SmartPropertyPrim.flagNoPartOfLayout)) {
  144. continue;
  145. }
  146. child.layoutAreaPos = new Vector2(x, y);
  147. let layoutArea = child.layoutArea;
  148. if (h) {
  149. x += layoutArea.width;
  150. child.layoutArea = new Size(layoutArea.width, max);
  151. } else {
  152. y += layoutArea.height;
  153. child.layoutArea = new Size(max, layoutArea.height);
  154. }
  155. }
  156. prim._clearFlags(SmartPropertyPrim.flagLayoutDirty);
  157. }
  158. }
  159. get isChildPositionAllowed(): boolean {
  160. return false;
  161. }
  162. }
  163. }