babylon.canvas2dLayoutEngine.ts 6.6 KB

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