@babylonjs/gui
Version:
Babylon.js GUI module =====================
528 lines • 20.9 kB
JavaScript
import { __decorate } from "@babylonjs/core/tslib.es6.js";
import { Container } from "./container.js";
import { ValueAndUnit } from "../valueAndUnit.js";
import { Control } from "./control.js";
import { Tools } from "@babylonjs/core/Misc/tools.js";
import { RegisterClass } from "@babylonjs/core/Misc/typeStore.js";
import { serialize } from "@babylonjs/core/Misc/decorators.js";
/**
* Class used to create a 2D grid container
*/
export class Grid extends Container {
/**
* Sets/Gets a boolean indicating that control content must be clipped
* Please note that not clipping content may generate issues with adt.useInvalidateRectOptimization so it is recommended to turn this optimization off if you want to use unclipped children
*/
set clipContent(value) {
this._clipContent = value;
// This value has to be replicated on all of the container cells
for (const key in this._cells) {
this._cells[key].clipContent = value;
}
}
get clipContent() {
return this._clipContent;
}
/**
* Sets/Gets a boolean indicating if the children are clipped to the current control bounds.
* Please note that not clipping children may generate issues with adt.useInvalidateRectOptimization so it is recommended to turn this optimization off if you want to use unclipped children
*/
set clipChildren(value) {
this._clipChildren = value;
// This value has to be replicated on all of the container cells
for (const key in this._cells) {
this._cells[key].clipChildren = value;
}
}
get clipChildren() {
return this._clipChildren;
}
/**
* Gets the number of columns
*/
get columnCount() {
return this._columnDefinitions.length;
}
/**
* Gets the number of rows
*/
get rowCount() {
return this._rowDefinitions.length;
}
/** Gets the list of children */
get children() {
return this._childControls;
}
/** Gets the list of cells (e.g. the containers) */
get cells() {
return this._cells;
}
/**
* Gets the definition of a specific row
* @param index defines the index of the row
* @returns the row definition
*/
getRowDefinition(index) {
if (index < 0 || index >= this._rowDefinitions.length) {
return null;
}
return this._rowDefinitions[index];
}
/**
* Gets the definition of a specific column
* @param index defines the index of the column
* @returns the column definition
*/
getColumnDefinition(index) {
if (index < 0 || index >= this._columnDefinitions.length) {
return null;
}
return this._columnDefinitions[index];
}
/**
* Adds a new row to the grid
* @param height defines the height of the row (either in pixel or a value between 0 and 1)
* @param isPixel defines if the height is expressed in pixel (or in percentage)
* @returns the current grid
*/
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;
}
/**
* Adds a new column to the grid
* @param width defines the width of the column (either in pixel or a value between 0 and 1)
* @param isPixel defines if the width is expressed in pixel (or in percentage)
* @returns the current grid
*/
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;
}
/**
* Update a row definition
* @param index defines the index of the row to update
* @param height defines the height of the row (either in pixel or a value between 0 and 1)
* @param isPixel defines if the weight is expressed in pixel (or in percentage)
* @returns the current grid
*/
setRowDefinition(index, height, isPixel = false) {
if (index < 0 || index >= this._rowDefinitions.length) {
return this;
}
const 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;
}
/**
* Update a column definition
* @param index defines the index of the column to update
* @param width defines the width of the column (either in pixel or a value between 0 and 1)
* @param isPixel defines if the width is expressed in pixel (or in percentage)
* @returns the current grid
*/
setColumnDefinition(index, width, isPixel = false) {
if (index < 0 || index >= this._columnDefinitions.length) {
return this;
}
const 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;
}
/**
* Gets the list of children stored in a specific cell
* @param row defines the row to check
* @param column defines the column to check
* @returns the list of controls
*/
getChildrenAt(row, column) {
const cell = this._cells[`${row}:${column}`];
if (!cell) {
return null;
}
return cell.children;
}
/**
* Gets a string representing the child cell info (row x column)
* @param child defines the control to get info from
* @returns a string containing the child cell info (row x column)
*/
getChildCellInfo(child) {
return child._tag;
}
_removeCell(cell, key) {
if (!cell) {
return;
}
super.removeControl(cell);
for (const control of cell.children) {
const childIndex = this._childControls.indexOf(control);
if (childIndex !== -1) {
this._childControls.splice(childIndex, 1);
}
}
delete this._cells[key];
}
_offsetCell(previousKey, key) {
if (!this._cells[key]) {
return;
}
this._cells[previousKey] = this._cells[key];
for (const control of this._cells[previousKey].children) {
control._tag = previousKey;
}
delete this._cells[key];
}
/**
* Remove a column definition at specified index
* @param index defines the index of the column to remove
* @returns the current grid
*/
removeColumnDefinition(index) {
if (index < 0 || index >= this._columnDefinitions.length) {
return this;
}
for (let x = 0; x < this._rowDefinitions.length; x++) {
const key = `${x}:${index}`;
const cell = this._cells[key];
this._removeCell(cell, key);
}
for (let x = 0; x < this._rowDefinitions.length; x++) {
for (let y = index + 1; y < this._columnDefinitions.length; y++) {
const previousKey = `${x}:${y - 1}`;
const 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;
}
/**
* Remove a row definition at specified index
* @param index defines the index of the row to remove
* @returns the current grid
*/
removeRowDefinition(index) {
if (index < 0 || index >= this._rowDefinitions.length) {
return this;
}
for (let y = 0; y < this._columnDefinitions.length; y++) {
const key = `${index}:${y}`;
const cell = this._cells[key];
this._removeCell(cell, key);
}
for (let y = 0; y < this._columnDefinitions.length; y++) {
for (let x = index + 1; x < this._rowDefinitions.length; x++) {
const previousKey = `${x - 1}:${y}`;
const 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;
}
/**
* Adds a new control to the current grid
* @param control defines the control to add
* @param row defines the row where to add the control (0 by default)
* @param column defines the column where to add the control (0 by default)
* @returns the current grid
*/
addControl(control, row = 0, column = 0) {
if (this._rowDefinitions.length === 0) {
// Add default row definition
this.addRowDefinition(1, false);
}
if (this._columnDefinitions.length === 0) {
// Add default column definition
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;
}
const x = Math.min(row, this._rowDefinitions.length - 1);
const y = Math.min(column, this._columnDefinitions.length - 1);
const 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;
goodContainer.clipContent = this.clipContent;
goodContainer.clipChildren = this.clipChildren;
super.addControl(goodContainer);
}
goodContainer.addControl(control);
this._childControls.push(control);
control._tag = key;
control.parent = this;
this._markAsDirty();
return this;
}
/**
* Removes a control from the current container
* @param control defines the control to remove
* @returns the current container
*/
removeControl(control) {
const index = this._childControls.indexOf(control);
if (index !== -1) {
this._childControls.splice(index, 1);
}
const cell = this._cells[control._tag];
if (cell) {
cell.removeControl(control);
control._tag = null;
}
this._markAsDirty();
return this;
}
/**
* Creates a new Grid
* @param name defines control name
*/
constructor(name) {
super(name);
this.name = name;
this._rowDefinitions = new Array();
this._rowDefinitionObservers = [];
this._columnDefinitions = new Array();
this._columnDefinitionObservers = [];
this._cells = {};
this._childControls = new Array();
}
_getTypeName() {
return "Grid";
}
_getGridDefinitions(definitionCallback) {
const widths = [];
const heights = [];
const lefts = [];
const tops = [];
let availableWidth = this._currentMeasure.width;
let globalWidthPercentage = 0;
let availableHeight = this._currentMeasure.height;
let globalHeightPercentage = 0;
// Heights
let index = 0;
for (const rowDefinition of this._rowDefinitions) {
if (rowDefinition.isPixel) {
const height = rowDefinition.getValue(this._host);
availableHeight -= height;
heights[index] = height;
}
else {
globalHeightPercentage += rowDefinition.value;
}
index++;
}
let top = 0;
index = 0;
for (const rowDefinition of this._rowDefinitions) {
tops.push(top);
if (!rowDefinition.isPixel) {
const height = Math.round((rowDefinition.value / globalHeightPercentage) * availableHeight);
top += height;
heights[index] = height;
}
else {
top += rowDefinition.getValue(this._host);
}
index++;
}
// Widths
index = 0;
for (const columnDefinition of this._columnDefinitions) {
if (columnDefinition.isPixel) {
const width = columnDefinition.getValue(this._host);
availableWidth -= width;
widths[index] = width;
}
else {
globalWidthPercentage += columnDefinition.value;
}
index++;
}
let left = 0;
index = 0;
for (const columnDefinition of this._columnDefinitions) {
lefts.push(left);
if (!columnDefinition.isPixel) {
const width = Math.round((columnDefinition.value / globalWidthPercentage) * availableWidth);
left += width;
widths[index] = width;
}
else {
left += columnDefinition.getValue(this._host);
}
index++;
}
definitionCallback(lefts, tops, widths, heights);
}
_additionalProcessing(parentMeasure, context) {
this._getGridDefinitions((lefts, tops, widths, heights) => {
// Setting child sizes
for (const key in this._cells) {
if (!Object.prototype.hasOwnProperty.call(this._cells, key)) {
continue;
}
const split = key.split(":");
const x = parseInt(split[0]);
const y = parseInt(split[1]);
const cell = this._cells[key];
cell.leftInPixels = lefts[y];
cell.topInPixels = tops[x];
cell.widthInPixels = widths[y];
cell.heightInPixels = heights[x];
cell._left.ignoreAdaptiveScaling = true;
cell._top.ignoreAdaptiveScaling = true;
cell._width.ignoreAdaptiveScaling = true;
cell._height.ignoreAdaptiveScaling = true;
}
});
super._additionalProcessing(parentMeasure, context);
}
_flagDescendantsAsMatrixDirty() {
for (const key in this._cells) {
if (!Object.prototype.hasOwnProperty.call(this._cells, key)) {
continue;
}
const child = this._cells[key];
child._markMatrixAsDirty();
}
}
_renderHighlightSpecific(context) {
super._renderHighlightSpecific(context);
this._getGridDefinitions((lefts, tops, widths, heights) => {
// Columns
for (let 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();
}
// Rows
for (let 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 */
dispose() {
super.dispose();
for (const control of this._childControls) {
control.dispose();
}
for (let index = 0; index < this._rowDefinitions.length; index++) {
this._rowDefinitions[index].onChangedObservable.remove(this._rowDefinitionObservers[index]);
}
for (let index = 0; index < this._columnDefinitions.length; index++) {
this._columnDefinitions[index].onChangedObservable.remove(this._columnDefinitionObservers[index]);
}
this._rowDefinitionObservers.length = 0;
this._rowDefinitions.length = 0;
this._columnDefinitionObservers.length = 0;
this._columnDefinitions.length = 0;
this._cells = {};
this._childControls.length = 0;
}
/**
* Serializes the current control
* @param serializationObject defined the JSON serialized object
* @param force force serialization even if isSerializable === false
* @param allowCanvas defines if the control is allowed to use a Canvas2D object to serialize
*/
serialize(serializationObject, force, allowCanvas) {
super.serialize(serializationObject, force, allowCanvas);
if (!this.isSerializable && !force) {
return;
}
serializationObject.columnCount = this.columnCount;
serializationObject.rowCount = this.rowCount;
serializationObject.columns = [];
serializationObject.rows = [];
serializationObject.tags = [];
for (let i = 0; i < this.columnCount; ++i) {
const cd = this.getColumnDefinition(i);
const childSerializationObject = { value: cd?.getValue(this.host), unit: cd?.unit };
serializationObject.columns.push(childSerializationObject);
}
for (let i = 0; i < this.rowCount; ++i) {
const rd = this.getRowDefinition(i);
const childSerializationObject = { value: rd?.getValue(this.host), unit: rd?.unit };
serializationObject.rows.push(childSerializationObject);
}
for (const child of this.children) {
serializationObject.tags.push(child._tag);
}
}
/**
* @internal
*/
_parseFromContent(serializedObject, host) {
super._parseFromContent(serializedObject, host);
const children = [];
for (const child of this.children) {
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);
}
}
}
__decorate([
serialize()
], Grid.prototype, "clipContent", null);
RegisterClass("BABYLON.GUI.Grid", Grid);
//# sourceMappingURL=grid.js.map