123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664 |
- module BABYLON {
- export interface ILayoutData {
-
- }
- @className("LayoutEngineBase", "BABYLON")
- /**
- * This is the base class you have to extend in order to implement your own Layout Engine.
- * Note that for performance reason, each different Layout Engine type can be exposed as one/many singleton or must be instanced each time.
- * If data has to be associated to a given primitive you can use the SmartPropertyPrim.addExternalData API to do it.
- */
- export class LayoutEngineBase implements ILockable {
- constructor() {
- this.layoutDirtyOnPropertyChangedMask = 0;
- }
- public updateLayout(prim: Prim2DBase) {
- }
- public get isChildPositionAllowed(): boolean {
- return false;
- }
- isLocked(): boolean {
- return this._isLocked;
- }
- lock(): boolean {
- if (this._isLocked) {
- return false;
- }
- this._isLocked = true;
- return true;
- }
- public layoutDirtyOnPropertyChangedMask;
- private _isLocked: boolean;
- }
- @className("CanvasLayoutEngine", "BABYLON")
- /**
- * The default Layout Engine, primitive are positioning into a Canvas, using their x/y coordinates.
- * This layout must be used as a Singleton through the CanvasLayoutEngine.Singleton property.
- */
- export class CanvasLayoutEngine extends LayoutEngineBase {
- private static _singleton: CanvasLayoutEngine = null;
- public static get Singleton(): CanvasLayoutEngine {
- if (!CanvasLayoutEngine._singleton) {
- CanvasLayoutEngine._singleton = new CanvasLayoutEngine();
- }
- return CanvasLayoutEngine._singleton;
- }
- constructor() {
- super();
- this.layoutDirtyOnPropertyChangedMask = Prim2DBase.sizeProperty.flagId | Prim2DBase.actualSizeProperty.flagId;
- }
- // A very simple (no) layout computing...
- // The Canvas and its direct children gets the Canvas' size as Layout Area
- // Indirect children have their Layout Area to the actualSize (margin area) of their parent
- public updateLayout(prim: Prim2DBase) {
- // If this prim is layoutDiry we update its layoutArea and also the one of its direct children
- if (prim._isFlagSet(SmartPropertyPrim.flagLayoutDirty)) {
- for (let child of prim.children) {
- this._doUpdate(child);
- }
- prim._clearFlags(SmartPropertyPrim.flagLayoutDirty);
- }
- }
- private _doUpdate(prim: Prim2DBase) {
- // Canvas ?
- if (prim instanceof Canvas2D) {
- prim.layoutArea = prim.actualSize; //.multiplyByFloats(prim.scaleX, prim.scaleY);
- }
- // Direct child of Canvas ?
- else if (prim.parent instanceof Canvas2D) {
- prim.layoutArea = prim.owner.actualSize; //.multiplyByFloats(prim.owner.scaleX, prim.owner.scaleY);
- }
- // Indirect child of Canvas
- else {
- prim.layoutArea = prim.parent.contentArea;
- }
- }
- get isChildPositionAllowed(): boolean {
- return true;
- }
- }
- @className("StackPanelLayoutEngine", "BABYLON")
- /**
- * A stack panel layout. Primitive will be stack either horizontally or vertically.
- * 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.
- */
- export class StackPanelLayoutEngine extends LayoutEngineBase {
- constructor() {
- super();
- this.layoutDirtyOnPropertyChangedMask = Prim2DBase.sizeProperty.flagId | Prim2DBase.actualSizeProperty.flagId;
- }
- public static get Horizontal(): StackPanelLayoutEngine {
- if (!StackPanelLayoutEngine._horizontal) {
- StackPanelLayoutEngine._horizontal = new StackPanelLayoutEngine();
- StackPanelLayoutEngine._horizontal.isHorizontal = true;
- StackPanelLayoutEngine._horizontal.lock();
- }
- return StackPanelLayoutEngine._horizontal;
- }
- public static get Vertical(): StackPanelLayoutEngine {
- if (!StackPanelLayoutEngine._vertical) {
- StackPanelLayoutEngine._vertical = new StackPanelLayoutEngine();
- StackPanelLayoutEngine._vertical.isHorizontal = false;
- StackPanelLayoutEngine._vertical.lock();
- }
- return StackPanelLayoutEngine._vertical;
- }
- private static _horizontal: StackPanelLayoutEngine = null;
- private static _vertical: StackPanelLayoutEngine = null;
- get isHorizontal(): boolean {
- return this._isHorizontal;
- }
- set isHorizontal(val: boolean) {
- if (this.isLocked()) {
- return;
- }
- this._isHorizontal = val;
- }
- private _isHorizontal: boolean = true;
- private static dstOffset = Vector4.Zero();
- private static dstArea = Size.Zero();
- public updateLayout(prim: Prim2DBase) {
- if (prim._isFlagSet(SmartPropertyPrim.flagLayoutDirty)) {
- let x = 0;
- let y = 0;
- let h = this.isHorizontal;
- let max = 0;
- for (let child of prim.children) {
- if (child._isFlagSet(SmartPropertyPrim.flagNoPartOfLayout)) {
- continue;
- }
- let layoutArea: Size;
- if (child._hasMargin) {
- child.margin.computeWithAlignment(prim.layoutArea, child.actualSize, child.marginAlignment, child.actualScale, StackPanelLayoutEngine.dstOffset, StackPanelLayoutEngine.dstArea, true);
- layoutArea = StackPanelLayoutEngine.dstArea.clone();
- child.layoutArea = layoutArea;
- } else {
- layoutArea = child.layoutArea;
- child.margin.computeArea(child.actualSize, layoutArea);
- }
- max = Math.max(max, h ? layoutArea.height : layoutArea.width);
- }
- for (let child of prim.children) {
- if (child._isFlagSet(SmartPropertyPrim.flagNoPartOfLayout)) {
- continue;
- }
- child.layoutAreaPos = new Vector2(x, y);
- let layoutArea = child.layoutArea;
- if (h) {
- x += layoutArea.width;
- child.layoutArea = new Size(layoutArea.width, max);
- } else {
- y += layoutArea.height;
- child.layoutArea = new Size(max, layoutArea.height);
- }
- }
- prim._clearFlags(SmartPropertyPrim.flagLayoutDirty);
- }
- }
- get isChildPositionAllowed(): boolean {
- return false;
- }
- }
- /**
- * GridData is used specify what row(s) and column(s) a primitive is placed in when its parent is using a Grid Panel Layout.
- */
- export class GridData implements ILayoutData{
- /**
- * the row number of the grid
- **/
- public row:number;
- /**
- * the column number of the grid
- **/
- public column:number;
- /**
- * the number of rows a primitive will occupy
- **/
- public rowSpan:number;
- /**
- * the number of columns a primitive will occupy
- **/
- public columnSpan:number;
- /**
- * Create a Grid Data that describes where a primitive will be placed in a Grid Panel Layout.
- * @param row the row number of the grid
- * @param column the column number of the grid
- * @param rowSpan the number of rows a primitive will occupy
- * @param columnSpan the number of columns a primitive will occupy
- **/
- constructor(row:number, column:number, rowSpan?:number, columnSpan?:number){
- this.row = row;
- this.column = column;
- this.rowSpan = (rowSpan == null) ? 1 : rowSpan;
- this.columnSpan = (columnSpan == null) ? 1 : columnSpan;
- }
- }
- class GridDimensionDefinition {
- public static Pixels = 1;
- public static Stars = 2;
- public static Auto = 3;
- _parse(value: string, res: (v: number, vp: number, t: number) => void) {
- let v = value.toLocaleLowerCase().trim();
- if (v.indexOf("auto") === 0) {
- res(null, null, GridDimensionDefinition.Auto);
- } else if (v.indexOf("*") !== -1) {
- let i = v.indexOf("*");
- let w = 1;
- if(i > 0){
- w = parseFloat(v.substr(0, i));
- }
- res(w, null, GridDimensionDefinition.Stars);
- } else {
- let w = parseFloat(v);
- res(w, w, GridDimensionDefinition.Pixels);
- }
- }
- }
- class RowDefinition extends GridDimensionDefinition {
- heightPixels: number;
- height: number;
- heightType: number;
- }
- class ColumnDefinition extends GridDimensionDefinition {
- widthPixels: number;
- width: number;
- widthType: number;
- }
- @className("GridPanelLayoutEngine", "BABYLON")
- /**
- * A grid panel layout. Grid panel is a table that has rows and columns.
- * When adding children to a primitive that is using grid panel layout, you must assign a GridData object to the child to indicate where the child will appear in the grid.
- */
- export class GridPanelLayoutEngine extends LayoutEngineBase {
- constructor(settings: { rows: [{ height: string }], columns: [{ width: string }] }) {
- super();
- this.layoutDirtyOnPropertyChangedMask = Prim2DBase.sizeProperty.flagId | Prim2DBase.actualSizeProperty.flagId;
- this._rows = new Array<RowDefinition>();
- this._columns = new Array<ColumnDefinition>();
- if (settings.rows) {
- for (let row of settings.rows) {
- let r = new RowDefinition();
- r._parse(row.height, (v, vp, t) => {
- r.height = v;
- r.heightPixels = vp;
- r.heightType = t;
- });
- this._rows.push(r);
- }
- }
- if (settings.columns) {
- for (let col of settings.columns) {
- let r = new ColumnDefinition();
- r._parse(col.width, (v, vp, t) => {
- r.width = v;
- r.widthPixels = vp;
- r.widthType = t;
- });
- this._columns.push(r);
- }
- }
- }
- private _rows: Array<RowDefinition>;
- private _columns: Array<ColumnDefinition>;
- private _children: Prim2DBase[][] = [];
-
- private _rowBottoms: Array<number> = [];
- private _columnLefts: Array<number> = [];
- private _rowHeights: Array<number> = [];
- private _columnWidths: Array<number> = [];
- private static dstOffset = Vector4.Zero();
- private static dstArea = Size.Zero();
- public updateLayout(prim: Prim2DBase) {
- if (prim._isFlagSet(SmartPropertyPrim.flagLayoutDirty)) {
- for (let child of prim.children) {
- if (child._isFlagSet(SmartPropertyPrim.flagNoPartOfLayout)) {
- continue;
- }
- if (child._hasMargin) {
- child.margin.computeWithAlignment(prim.layoutArea, child.actualSize, child.marginAlignment, child.actualScale, GridPanelLayoutEngine.dstOffset, GridPanelLayoutEngine.dstArea, true);
- child.layoutArea = GridPanelLayoutEngine.dstArea.clone();
- } else {
- child.margin.computeArea(child.actualSize, child.layoutArea);
- }
- }
- this._updateGrid(prim);
- let _children = this._children;
- let rl = this._rows.length;
- let cl = this._columns.length;
- let columnWidth = 0;
- let rowHeight = 0;
- let layoutArea = new BABYLON.Size(0, 0);
- for(let i = 0; i < _children.length; i++){
- let children = _children[i];
- if(children){
- let bottom = this._rowBottoms[i];
- let rowHeight = this._rowHeights[i];
- let oBottom = bottom;
- let oRowHeight = rowHeight;
- for(let j = 0; j < children.length; j++){
-
- let left = this._columnLefts[j];
- let columnWidth = this._columnWidths[j];
- let child = children[j];
- if(child){
- let gd = <GridData>child.layoutData;
- if(gd.columnSpan > 1){
- for(let k = j+1; k < gd.columnSpan + j && k < cl; k++){
- columnWidth += this._columnWidths[k];
- }
- }
- if(gd.rowSpan > 1){
-
- for(let k = i+1; k < gd.rowSpan + i && k < rl; k++){
- rowHeight += this._rowHeights[k];
- bottom = this._rowBottoms[k];
- }
-
- }
- layoutArea.width = columnWidth;
- layoutArea.height = rowHeight;
- child.margin.computeWithAlignment(layoutArea, child.actualSize, child.marginAlignment, child.actualScale, GridPanelLayoutEngine.dstOffset, GridPanelLayoutEngine.dstArea);
- child.layoutAreaPos = new BABYLON.Vector2(left + GridPanelLayoutEngine.dstOffset.x, bottom + GridPanelLayoutEngine.dstOffset.y);
-
- bottom = oBottom;
- rowHeight = oRowHeight;
-
- }
- }
- }
-
- }
- prim._clearFlags(SmartPropertyPrim.flagLayoutDirty);
- }
- }
- get isChildPositionAllowed(): boolean {
- return false;
- }
- private _getMaxChildHeightInRow(rowNum:number):number{
- let rows = this._rows;
- let cl = this._columns.length;
- let rl = this._rows.length;
- let children = this._children;
- let row = rows[rowNum];
- let maxHeight = 0;
- if(children && children[rowNum]){
- for(let i = 0; i < cl; i++){
- let child = children[rowNum][i];
-
- if(child){
- let span = (<GridData>child.layoutData).rowSpan;
- if(maxHeight < child.layoutArea.height/span){
- maxHeight = child.layoutArea.height/span;
- }
- }
- }
- }
- return maxHeight;
- }
- private _getMaxChildWidthInColumn(colNum:number):number{
- let columns = this._columns;
- let cl = this._columns.length;
- let rl = this._rows.length;
- let children = this._children;
- let column = columns[colNum];
- let maxWidth = 0;
- if(children){
- for(let i = 0; i < rl; i++){
- if(children[i]){
- let child = children[i][colNum];
- if(child){
- let span = (<GridData>child.layoutData).columnSpan;
- if(maxWidth < child.layoutArea.width/span){
- maxWidth = child.layoutArea.width/span;
- }
- }
- }
- }
- }
- return maxWidth;
- }
- private _updateGrid(prim:Prim2DBase){
- let _children = this._children;
- //remove prim.children from _children
- for(let i = 0; i < _children.length; i++){
- let children = _children[i];
- if(children){
- children.length = 0;
- }
- }
- let childrenThatSpan:Array<Prim2DBase>;
- //add prim.children to _children
- for(let child of prim.children){
-
- if(!child.layoutData){
- continue;
- }
- let gd = <GridData>child.layoutData;
- if(!_children[gd.row]){
- _children[gd.row] = [];
- }
- if(gd.columnSpan == 1 && gd.rowSpan == 1){
- _children[gd.row][gd.column] = child;
- }else{
- if(!childrenThatSpan){
- childrenThatSpan = [];
- }
- //when children span, we need to add them to _children whereever they span to so that
- //_getMaxChildHeightInRow and _getMaxChildWidthInColumn will work correctly.
- childrenThatSpan.push(child);
- for(let i = gd.row; i < gd.row + gd.rowSpan; i++){
- for(let j = gd.column; j < gd.column + gd.columnSpan; j++){
- _children[i][j] = child;
- }
- }
- }
- }
- let rows = this._rows;
- let columns = this._columns;
- let rl = this._rows.length;
- let cl = this._columns.length;
- //get fixed and auto row heights first
- var starIndexes = [];
- var totalStars = 0;
- var rowHeights = 0;
- let columnWidths = 0;
- for (let i = 0; i < rl; i++) {
- let row = this._rows[i];
- if(row.heightType == GridDimensionDefinition.Auto){
- this._rowHeights[i] = this._getMaxChildHeightInRow(i);
- rowHeights += this._rowHeights[i];
- }else if(row.heightType == GridDimensionDefinition.Pixels){
- let maxChildHeight = this._getMaxChildHeightInRow(i);
- this._rowHeights[i] = Math.max(row.heightPixels, maxChildHeight);
- rowHeights += this._rowHeights[i];
- }else if(row.heightType == GridDimensionDefinition.Stars){
- starIndexes.push(i);
- totalStars += row.height;
- }
- }
- //star row heights
- if(starIndexes.length > 0){
- let remainingHeight = prim.contentArea.height - rowHeights;
- for(let i = 0; i < starIndexes.length; i++){
- let rowIndex = starIndexes[i];
- let starHeight = (this._rows[rowIndex].height / totalStars) * remainingHeight;
- let maxChildHeight = this._getMaxChildHeightInRow(i);
- this._rowHeights[rowIndex] = Math.max(starHeight, maxChildHeight);
- }
- }
- //get fixed and auto column widths
- starIndexes.length = 0;
- totalStars = 0;
- for (let i = 0; i < cl; i++) {
- let column = this._columns[i];
- if(column.widthType == GridDimensionDefinition.Auto){
- this._columnWidths[i] = this._getMaxChildWidthInColumn(i);
- columnWidths += this._columnWidths[i];
- }else if(column.widthType == GridDimensionDefinition.Pixels){
- let maxChildWidth = this._getMaxChildWidthInColumn(i);
- this._columnWidths[i] = Math.max(column.widthPixels, maxChildWidth);
- columnWidths += this._columnWidths[i];
- }else if(column.widthType == GridDimensionDefinition.Stars){
- starIndexes.push(i);
- totalStars += column.width;
- }
- }
- //star column widths
- if(starIndexes.length > 0){
- let remainingWidth = prim.contentArea.width - columnWidths;
- for(let i = 0; i < starIndexes.length; i++){
- let columnIndex = starIndexes[i];
- let starWidth = (this._columns[columnIndex].width / totalStars) * remainingWidth;
- let maxChildWidth = this._getMaxChildWidthInColumn(i);
- this._columnWidths[columnIndex] = Math.max(starWidth, maxChildWidth);
- }
- }
- let y = 0;
- this._rowBottoms[rl - 1] = y;
- for (let i = rl - 2; i >= 0; i--) {
- y += this._rowHeights[i+1];
- this._rowBottoms[i] = y;
- }
- let x = 0;
- this._columnLefts[0] = x;
-
- for (let i = 1; i < cl; i++) {
- x += this._columnWidths[i-1];
- this._columnLefts[i] = x;
- }
- //remove duplicate references to children that span
- if(childrenThatSpan){
- for(var i = 0; i < childrenThatSpan.length; i++){
-
- let child = childrenThatSpan[i];
- let gd = <GridData>child.layoutData;
- for(let i = gd.row; i < gd.row + gd.rowSpan; i++){
- for(let j = gd.column; j < gd.column + gd.columnSpan; j++){
- if(i == gd.row && j == gd.column){
- continue;
- }
- if(_children[i][j] == child){
- _children[i][j] = null;
- }
- }
- }
- }
- }
- }
- }
- }
|