import { Nullable } from "babylonjs/types" ; import { Container } from "./container" ; import { ValueAndUnit } from "../valueAndUnit" ; import { Control } from "./control" ; import { Measure } from "../measure" ; import { Tools } from 'babylonjs/Misc/tools' ; import { RegisterClass } from 'babylonjs/Misc/typeStore' ; import { ICanvasRenderingContext } from "babylonjs/Engines/ICanvas" ; import { AdvancedDynamicTexture } from "../advancedDynamicTexture" ; import { Observer } from "babylonjs/Misc/observable" ; /**
 * Class used to create a 2D grid container
 */ export class Grid extends Container { private _rowDefinitions = new Array < ValueAndUnit > ( ) ; private _rowDefinitionObservers : Observer < void > [ ] = [ ] ; private _columnDefinitions = new Array < ValueAndUnit > ( ) ; private _columnDefinitionObservers : Observer < void > [ ] = [ ] ; private _cells : { [ key ] : Container ; } = { } ; private _childControls = new Array < Control > ( ) ; /**
     * Gets the number of columns
     */ public get columnCount ( ) : number { return this . _columnDefinitions . length ; } /**
     * Gets the number of rows
     */ public get rowCount ( ) : number { return this . _rowDefinitions . length ; } /** Gets the list of children */ public get children ( ) : Control [ ] { return this . _childControls ; } /** Gets the list of cells (e.g. the containers) */ public get cells ( ) : { [ key ] : Container ; } { return this . _cells ; } param index returns public getRowDefinition ( index ) { if ( index < 0 || index >= this . _rowDefinitions . length ) { return null ; } return this . _rowDefinitions [ index ] ; } param index returns public getColumnDefinition ( index ) { if ( index < 0 || index >= this . _columnDefinitions . length ) { return null ; } return this . _columnDefinitions [ index ] ; } param height param isPixel returns public addRowDefinition ( height , isPixel = false ) { this . _rowDefinitions . push ( new ValueAndUnit ( height , isPixel ? ValueAndUnit . UNITMODE_PIXEL : ValueAndUnit . UNITMODE_PERCENTAGE ) ) ; this . _rowDefinitionObservers . push ( this . _rowDefinitions [ this . rowCount - 1 ] . onChangedObservable . add ( ( ) => this . _markAsDirty ( ) ) ! ) ; this . _markAsDirty ( ) ; return this ; } param width param isPixel returns public addColumnDefinition ( width , isPixel = false ) { this . _columnDefinitions . push ( new ValueAndUnit ( width , isPixel ? ValueAndUnit . UNITMODE_PIXEL : ValueAndUnit . UNITMODE_PERCENTAGE ) ) ; this . _columnDefinitionObservers . push ( this . _columnDefinitions [ this . columnCount - 1 ] . onChangedObservable . add ( ( ) => this . _markAsDirty ( ) ) ! ) ; this . _markAsDirty ( ) ; return this ; } param index param height param isPixel returns public setRowDefinition ( index , height , isPixel = false ) { if ( index < 0 || index >= this . _rowDefinitions . length ) { return this ; } let current = this . _rowDefinitions [ index ] ; if ( current && current . isPixel === isPixel && current . value === height ) { return this ; } this . _rowDefinitions [ index ] . onChangedObservable . remove ( this . _rowDefinitionObservers [ index ] ) ; this . _rowDefinitions [ index ] = new ValueAndUnit ( height , isPixel ? ValueAndUnit . UNITMODE_PIXEL : ValueAndUnit . UNITMODE_PERCENTAGE ) ; this . _rowDefinitionObservers [ index ] = this . _rowDefinitions [ index ] . onChangedObservable . add ( ( ) => this . _markAsDirty ( ) ) ! ; this . _markAsDirty ( ) ; return this ; } param index param width param isPixel returns public setColumnDefinition ( index , width , isPixel = false ) { if ( index < 0 || index >= this . _columnDefinitions . length ) { return this ; } let current = this . _columnDefinitions [ index ] ; if ( current && current . isPixel === isPixel && current . value === width ) { return this ; } this . _columnDefinitions [ index ] . onChangedObservable . remove ( this . _columnDefinitionObservers [ index ] ) ; this . _columnDefinitions [ index ] = new ValueAndUnit ( width , isPixel ? ValueAndUnit . UNITMODE_PIXEL : ValueAndUnit . UNITMODE_PERCENTAGE ) ; this . _columnDefinitionObservers [ index ] = this . _columnDefinitions [ index ] . onChangedObservable . add ( ( ) => this . _markAsDirty ( ) ) ! ; this . _markAsDirty ( ) ; return this ; } param row param column returns public getChildrenAt ( row , column ) { const cell = this . _cells [ `${ row }:${ column }` ] ; if ( ! cell ) { return null ; } return cell . children ; } param child returns public getChildCellInfo ( child ) { return child . _tag ; } private _removeCell ( cell , key ) { if ( ! cell ) { return ; } super . removeControl ( cell ) ; for ( var control of cell . children ) { let childIndex = this . _childControls . indexOf ( control ) ; if ( childIndex !== - 1 ) { this . _childControls . splice ( childIndex , 1 ) ; } } delete this . _cells [ key ] ; } private _offsetCell ( previousKey , key ) { if ( ! this . _cells [ key ] ) { return ; } this . _cells [ previousKey ] = this . _cells [ key ] ; for ( var control of this . _cells [ previousKey ] . children ) { control . _tag = previousKey ; } delete this . _cells [ key ] ; } param index returns public removeColumnDefinition ( index ) { if ( index < 0 || index >= this . _columnDefinitions . length ) { return this ; } for ( var x = 0 ; x < this . _rowDefinitions . length ; x ++ ) { let key = `${ x }:${ index }` ; let cell = this . _cells [ key ] ; this . _removeCell ( cell , key ) ; } for ( var x = 0 ; x < this . _rowDefinitions . length ; x ++ ) { for ( var y = index + 1 ; y < this . _columnDefinitions . length ; y ++ ) { let previousKey = `${ x }:${ y - 1 }` ; let key = `${ x }:${ y }` ; this . _offsetCell ( previousKey , key ) ; } } this . _columnDefinitions [ index ] . onChangedObservable . remove ( this . _columnDefinitionObservers [ index ] ) ; this . _columnDefinitions . splice ( index , 1 ) ; this . _columnDefinitionObservers . splice ( index , 1 ) ; this . _markAsDirty ( ) ; return this ; } param index returns public removeRowDefinition ( index ) { if ( index < 0 || index >= this . _rowDefinitions . length ) { return this ; } for ( var y = 0 ; y < this . _columnDefinitions . length ; y ++ ) { let key = `${ index }:${ y }` ; let cell = this . _cells [ key ] ; this . _removeCell ( cell , key ) ; } for ( var y = 0 ; y < this . _columnDefinitions . length ; y ++ ) { for ( var x = index + 1 ; x < this . _rowDefinitions . length ; x ++ ) { let previousKey = `${ x - 1 }:${ y }` ; let key = `${ x }:${ y }` ; this . _offsetCell ( previousKey , key ) ; } } this . _rowDefinitions [ index ] . onChangedObservable . remove ( this . _rowDefinitionObservers [ index ] ) ; this . _rowDefinitions . splice ( index , 1 ) ; this . _rowDefinitionObservers . splice ( index , 1 ) ; this . _markAsDirty ( ) ; return this ; } param control param row param column returns public addControl ( control , row = 0 , column = 0 ) { if ( this . _rowDefinitions . length === 0 ) { this . addRowDefinition ( 1 , false ) ; } if ( this . _columnDefinitions . length === 0 ) { this . addColumnDefinition ( 1 , false ) ; } if ( this . _childControls . indexOf ( control ) !== - 1 ) { Tools . Warn ( `Control (Name:${ control . name }, UniqueId:${ control . uniqueId }) is already associated with this grid. You must remove it before reattaching it` ) ; return this ; } let x = Math . min ( row , this . _rowDefinitions . length - 1 ) ; let y = Math . min ( column , this . _columnDefinitions . length - 1 ) ; let key = `${ x }:${ y }` ; let goodContainer = this . _cells [ key ] ; if ( ! goodContainer ) { goodContainer = new Container ( key ) ; this . _cells [ key ] = goodContainer ; goodContainer . horizontalAlignment = Control . HORIZONTAL_ALIGNMENT_LEFT ; goodContainer . verticalAlignment = Control . VERTICAL_ALIGNMENT_TOP ; super . addControl ( goodContainer ) ; } goodContainer . addControl ( control ) ; this . _childControls . push ( control ) ; control . _tag = key ; control . parent = this ; this . _markAsDirty ( ) ; return this ; } param control returns public removeControl ( control ) { var index = this . _childControls . indexOf ( control ) ; if ( index !== - 1 ) { this . _childControls . splice ( index , 1 ) ; } let cell = this . _cells [ control . _tag ] ; if ( cell ) { cell . removeControl ( control ) ; control . _tag = null ; } this . _markAsDirty ( ) ; return this ; } param name constructor ( public name ? ) { super ( name ) ; } protected _getTypeName ( ) { return "Grid" ; } protected _getGridDefinitions ( definitionCallback ) { let widths = [ ] ; let heights = [ ] ; let lefts = [ ] ; let tops = [ ] ; let availableWidth = this . _currentMeasure . width ; let globalWidthPercentage = 0 ; let availableHeight = this . _currentMeasure . height ; let globalHeightPercentage = 0 ; let index = 0 ; for ( var rowDefinition of this . _rowDefinitions ) { if ( rowDefinition . isPixel ) { let height = rowDefinition . getValue ( this . _host ) ; availableHeight -= height ; heights [ index ] = height ; } else { globalHeightPercentage += rowDefinition . value ; } index ++ ; } let top = 0 ; index = 0 ; for ( var rowDefinition of this . _rowDefinitions ) { tops . push ( top ) ; if ( ! rowDefinition . isPixel ) { let height = ( rowDefinition . value / globalHeightPercentage ) * availableHeight ; top += height ; heights [ index ] = height ; } else { top += rowDefinition . getValue ( this . _host ) ; } index ++ ; } index = 0 ; for ( var columnDefinition of this . _columnDefinitions ) { if ( columnDefinition . isPixel ) { let width = columnDefinition . getValue ( this . _host ) ; availableWidth -= width ; widths [ index ] = width ; } else { globalWidthPercentage += columnDefinition . value ; } index ++ ; } let left = 0 ; index = 0 ; for ( var columnDefinition of this . _columnDefinitions ) { lefts . push ( left ) ; if ( ! columnDefinition . isPixel ) { let width = ( columnDefinition . value / globalWidthPercentage ) * availableWidth ; left += width ; widths [ index ] = width ; } else { left += columnDefinition . getValue ( this . _host ) ; } index ++ ; } definitionCallback ( lefts , tops , widths , heights ) ; } protected _additionalProcessing ( parentMeasure , context ) { this . _getGridDefinitions ( ( lefts , tops , widths , heights ) => { for ( var key in this . _cells ) { if ( ! this . _cells . hasOwnProperty ( key ) ) { continue ; } let split = key . split ( ":" ) ; let x = parseInt ( split [ 0 ] ) ; let y = parseInt ( split [ 1 ] ) ; let cell = this . _cells [ key ] ; cell . left = lefts [ y ] + "px" ; cell . top = tops [ x ] + "px" ; cell . width = widths [ y ] + "px" ; cell . height = heights [ x ] + "px" ; cell . _left . ignoreAdaptiveScaling = true ; cell . _top . ignoreAdaptiveScaling = true ; cell . _width . ignoreAdaptiveScaling = true ; cell . _height . ignoreAdaptiveScaling = true ; } } ) ; super . _additionalProcessing ( parentMeasure , context ) ; } public _flagDescendantsAsMatrixDirty ( ) { for ( var key in this . _cells ) { if ( ! this . _cells . hasOwnProperty ( key ) ) { continue ; } let child = this . _cells [ key ] ; child . _markMatrixAsDirty ( ) ; } } public _renderHighlightSpecific ( context ) { super . _renderHighlightSpecific ( context ) ; this . _getGridDefinitions ( ( lefts , tops , widths , heights ) => { for ( var index = 0 ; index < lefts . length ; index ++ ) { const left = this . _currentMeasure . left + lefts [ index ] + widths [ index ] ; context . beginPath ( ) ; context . moveTo ( left , this . _currentMeasure . top ) ; context . lineTo ( left , this . _currentMeasure . top + this . _currentMeasure . height ) ; context . stroke ( ) ; } for ( var index = 0 ; index < tops . length ; index ++ ) { const top = this . _currentMeasure . top + tops [ index ] + heights [ index ] ; context . beginPath ( ) ; context . moveTo ( this . _currentMeasure . left , top ) ; context . lineTo ( this . _currentMeasure . left + this . _currentMeasure . width , top ) ; context . stroke ( ) ; } } ) ; context . restore ( ) ; } /** Releases associated resources */ public dispose ( ) { super . dispose ( ) ; for ( var control of this . _childControls ) { control . dispose ( ) ; } for ( var index = 0 ; index < this . _rowDefinitions . length ; index ++ ) { this . _rowDefinitions [ index ] . onChangedObservable . remove ( this . _rowDefinitionObservers [ index ] ) ; } for ( var index = 0 ; index < this . _columnDefinitions . length ; index ++ ) { this . _columnDefinitions [ index ] . onChangedObservable . remove ( this . _columnDefinitionObservers [ index ] ) ; } this . _rowDefinitionObservers = [ ] ; this . _rowDefinitions = [ ] ; this . _columnDefinitionObservers = [ ] ; this . _columnDefinitions = [ ] ; this . _cells = { } ; this . _childControls = [ ] ; } param serializationObject public serialize ( serializationObject ) { super . serialize ( serializationObject ) ; serializationObject . columnCount = this . columnCount ; serializationObject . rowCount = this . rowCount ; serializationObject . columns = [ ] ; serializationObject . rows = [ ] ; serializationObject . tags = [ ] ; for ( let i = 0 ; i < this . columnCount ; ++ i ) { let cd = this . getColumnDefinition ( i ) ; let childSerializationObject = { value : cd ?. getValue ( this . host ) , unit : cd ?. unit } ; serializationObject . columns . push ( childSerializationObject ) ; } for ( let i = 0 ; i < this . rowCount ; ++ i ) { let rd = this . getRowDefinition ( i ) ; let childSerializationObject = { value : rd ?. getValue ( this . host ) , unit : rd ?. unit } ; serializationObject . rows . push ( childSerializationObject ) ; } this . children . forEach ( ( child ) => { serializationObject . tags . push ( child . _tag ) ; } ) ; } hidden public _parseFromContent ( serializedObject , host ) { super . _parseFromContent ( serializedObject , host ) ; let children = [ ] ; this . children . forEach ( ( child ) => { children . push ( child ) ; } ) ; this . removeRowDefinition ( 0 ) ; this . removeColumnDefinition ( 0 ) ; for ( let i = 0 ; i < serializedObject . columnCount ; ++ i ) { const columnValue = serializedObject . columns [ i ] . value ; const unit = serializedObject . columns [ i ] . unit ; this . addColumnDefinition ( columnValue , unit === 1 ? true : false ) ; } for ( let i = 0 ; i < serializedObject . rowCount ; ++ i ) { const rowValue = serializedObject . rows [ i ] . value ; const unit = serializedObject . rows [ i ] . unit ; this . addRowDefinition ( rowValue , unit === 1 ? true : false ) ; } for ( let i = 0 ; i < children . length ; ++ i ) { const cellInfo = serializedObject . tags [ i ] ; let rowNumber = parseInt ( cellInfo . substring ( 0 , cellInfo . search ( ":" ) ) ) ; if ( isNaN ( rowNumber ) ) { rowNumber = 0 ; } let columnNumber = parseInt ( cellInfo . substring ( cellInfo . search ( ":" ) + 1 ) ) ; if ( isNaN ( columnNumber ) ) { columnNumber = 0 ; } this . addControl ( children [ i ] , rowNumber , columnNumber ) ; } } } RegisterClass ( "BABYLON.GUI.Grid" , Grid ) ;