phaser3-flex
Version:
Flex containers for Phaser
556 lines (490 loc) • 13.8 kB
text/typescript
import {
AlignItems,
Alignment,
Config,
FlexDirection,
Item,
JustifyContent,
} from "./sharedtypes"
import {
checkHeight,
checkWidth,
fitHeight,
fitTextToColumn,
fitWidth,
getItemsSize,
resetHeights,
resetWidths,
setAlignH,
setAlignV,
setItems,
setJustify
} from "./helpers";
class Flex {
/**
* X position. (Default = 0)
*/
x: number;
/**
* Y position. (Default = 0)
*/
y: number;
/**
* Width of this object. (Default = 0)
*/
width: number;
/**
* Height of this object. (Default = 0)
*/
height: number;
/**
* Minimum distance between this object content and its border. (Default = 10)
*/
padding: number;
/**
* Minimum distance between items contained inside this object. (Default = 4)
*/
itemsMargin: number;
/**
* Alignment of the items with respect to the cross axis. (Default = AlignItems.CENTER)
*/
alignItems: number;
/**
* Sets how items are placed in the flex object defining the main axis. (Default = FlexDirection.ROW)
*/
flexDirection: number;
/**
* Alignment of the items with respect to the main axis. (Default = JustifyContent.FLEX_START)
*/
justifyContent: number;
/**
* Array of all items managed by this object.
*/
items: Item[];
/**
* Position of this object anchor relative to its width and height. (x and y between 0 and 1).
* Sets how this object is placed.
*/
origin: { x: number, y: number };
/**
* Original size of this object in the main axis.
*/
basis: number;
scene: Phaser.Scene;
flexGrow: number;
flexShrink: number;
/**
* @private
*/
_fparent: Flex;
/**
* @private
*/
_scrollFactorX: number;
/**
* @private
*/
_scrollFactorY: number;
/**
* @private
*/
_isFlex: boolean;
/**
* @private
*/
_basisSum: number;
/**
* @private
*/
_heights: number[];
/**
* @private
*/
_widths: number[];
/**
* @private
*/
_growSum: number;
/**
* @private
*/
_shrinkSum: number;
/**
* @private
*/
_bounds: { left: number, right: number, top: number, bottom: number };
/**
* Creates an instance of Flex class
* @param scene
* @param config
* @returns
*/
constructor(scene: Phaser.Scene, config: Config) {
this.scene = scene;
this.x = config.x || 0;
this.y = config.y || 0;
this.width = config.width == undefined ? scene.scale.width : config.width;
this.height = config.height || 0;
this.padding = config.padding || 10;
this.itemsMargin = config.itemsMargin || 4;
this.alignItems = config.alignItems || AlignItems.CENTER;
this.flexDirection = config.flexDirection || FlexDirection.ROW;
this.justifyContent = config.justifyContent || JustifyContent.FLEX_START;
this.items = [];
this.origin = { x: 0, y: 0 };
this._scrollFactorX = 0;
this._scrollFactorY = 0;
this._fparent = null;
this._isFlex = true;
this._basisSum = 0;
this._heights = [];
this._widths = [];
this._growSum = 0;
this._shrinkSum = 0;
this._bounds = { left: 0, right: 0, top: 0, bottom: 0 };
this.scene.events.once("destroy", this.destroy, this);
return this;
}
/**
* Adds an item to the items list of this object. The position and size of this items
* are managed by this object.
*
* @param item
* @param flexGrow
* @param flexShrink
* @returns This Flex instance.
*/
add(item: Item, flexGrow: number = 0, flexShrink: number = 1): Flex {
item.setOrigin(0, 0);
item.setScrollFactor(this._scrollFactorX, this._scrollFactorY);
if (item["_isFlex"]) {
item.flexGrow = flexGrow;
item.flexShrink = flexShrink;
item["_fparent"] = this;
} else {
item.flexGrow = 0;
item.flexShrink = 0;
}
if (this.width && item.type == "Text" && this.flexDirection == FlexDirection.COLUMN) {
fitTextToColumn(this, item);
}
item.basis = this.flexDirection == FlexDirection.ROW ? item.width : item.height;
this._basisSum += item.basis;
this.items.push(item);
this._heights.push(item.height);
this._widths.push(item.width);
this._growSum += item.flexGrow;
this._shrinkSum += item.flexShrink * item.basis;
if (this.flexDirection == FlexDirection.ROW) {
checkHeight(this, item.height);
if (!item["_isFlex"]) checkWidth(this, getItemsSize(this));
}
if (this.flexDirection == FlexDirection.COLUMN) {
checkWidth(this, item.width);
if (!item["_isFlex"]) checkHeight(this, getItemsSize(this));
}
setItems(this);
if (this._fparent) {
if (this._fparent.flexDirection == FlexDirection.ROW) {
checkHeight(this._fparent, this.height);
} else {
checkWidth(this._fparent, this.width);
}
setItems(this._fparent);
}
return this;
}
/**
* Each item managed by this object are destroyed.
*
* @returns This Flex instance.
*/
clear(): Flex {
this.items.forEach(item => {
if (item["_isFlex"]) {
item["clear"]();
}
});
this.items.forEach(item => item.destroy());
this.items = [];
this._heights = [];
this._widths = [];
this._basisSum = 0;
return this;
}
/**
* An item is removed from the items list managed by this flex object.
*
* @param index Index of the item to be removed in the items array of this instance.
* @param destroy The item should be destroyed?.
* @returns This Flex instance.
*/
remove(index: number, destroy: boolean): Flex {
if (this.items[index] == undefined) {
return;
}
let item = this.items[index];
item["_fparent"] = null;
this._basisSum -= item.basis;
this.items.splice(index, 1);
this._heights.splice(index, 1);
this._widths.splice(index, 1);
if (destroy) {
item.destroy();
}
if (this.flexDirection == FlexDirection.ROW) {
fitHeight(this);
}
if (this.flexDirection == FlexDirection.COLUMN) {
fitWidth(this);
}
setItems(this);
return this;
}
/**
* Sets the size of this object.
*
* @param width
* @param height
* @returns This Flex instance.
*/
setSize(width: number, height: number): Flex {
this.setWidth(width);
this.setHeight(height);
return this;
}
/**
* Disposes all resources used by this object.
*
*/
destroy() {
this.clear();
this.items = null;
this._widths = null;
this._heights = null;
this._bounds = null;
this.origin = null;
this._fparent = null;
}
/**
* Sets the *alignItems* property of this object.
*
* @param alignItems
* @returns This Flex instance.
*/
setAlignItems(alignItems: AlignItems): Flex {
if (this.alignItems == AlignItems.STRETCH && alignItems != AlignItems.STRETCH) {
if (this.flexDirection == FlexDirection.ROW) {
resetHeights(this);
} else {
resetWidths(this);
}
}
this.alignItems = alignItems;
switch (alignItems) {
case AlignItems.CENTER:
if (this.flexDirection == FlexDirection.ROW) {
setAlignV(this, Alignment.CENTER);
} else {
setAlignH(this, Alignment.CENTER);
}
break;
case AlignItems.FLEX_START:
if (this.flexDirection == FlexDirection.ROW) {
setAlignV(this, Alignment.TOP);
} else {
setAlignH(this, Alignment.LEFT);
}
break;
case AlignItems.FLEX_END:
if (this.flexDirection == FlexDirection.ROW) {
setAlignV(this, Alignment.BOTTOM);
} else {
setAlignH(this, Alignment.RIGHT);
}
break;
case AlignItems.STRETCH:
if (this.flexDirection == FlexDirection.ROW) {
setAlignV(this, Alignment.STRETCH);
} else {
setAlignH(this, Alignment.STRETCH);
}
break;
default:
break;
}
return this;
}
/**
* Sets the *height* of this object.
*
* @param height
* @returns This Flex instance.
*/
setHeight(height: number): Flex {
this.height = height;
resetHeights(this);
setItems(this);
return this;
}
/**
* Sets the *width* of this object.
*
* @param width
* @returns This Flex instance.
*/
setWidth(width: number): Flex {
this.width = width;
resetWidths(this);
if (this.flexDirection == FlexDirection.COLUMN) {
for (let i = 0; i < this.items.length; i++) {
let item = this.items[i];
if (item.type == "Text") {
fitTextToColumn(this, item);
}
}
}
setItems(this);
return this;
}
/**
* Sets the *justifyContent* property of this object.
*
* @param justifyContent
* @returns This Flex instance.
*/
setJustifyContent(justifyContent: JustifyContent): Flex {
this.justifyContent = justifyContent;
switch (justifyContent) {
case JustifyContent.CENTER:
if (this.flexDirection == FlexDirection.ROW) {
setAlignH(this, Alignment.CENTER);
} else {
setAlignV(this, Alignment.CENTER);
}
break;
case JustifyContent.FLEX_START:
if (this.flexDirection == FlexDirection.ROW) {
setAlignH(this, Alignment.LEFT);
} else {
setAlignV(this, Alignment.TOP);
}
break;
case JustifyContent.FLEX_END:
if (this.flexDirection == FlexDirection.ROW) {
setAlignH(this, Alignment.RIGHT);
} else {
setAlignV(this, Alignment.BOTTOM);
}
break;
case JustifyContent.SPACE_AROUND:
setJustify(this);
break;
case JustifyContent.SPACE_BETWEEN:
setJustify(this);
break;
default:
break;
}
return this;
}
/**
* Sets the *origin* property of this object.
*
* @param x
* @param y
* @returns This Flex instance.
*/
setOrigin(x: number, y: number): Flex {
if (y == undefined) {
y = x;
}
this.origin.x = x;
this.origin.y = y;
if (x > 1) {
this.origin.x = 1;
};
if (x < 0) {
this.origin.x = 0;
};
if (y > 1) {
this.origin.y = 1;
};
if (y < 0) {
this.origin.y = 0;
};
setItems(this);
return this;
}
/**
* Sets the *scrollFactor* property of this object.
*
* @param x
* @param y
* @returns This Flex instance.
*/
setScrollFactor(x: number, y: number | undefined): Flex {
if (x > 1) {
x = 1;
}
if (x < 0) {
x = 0;
}
if (!y) {
y = x;
}
this._scrollFactorX = x;
this._scrollFactorY = y;
for (let i = 0; i < this.items.length; i++) {
this.items[i].setScrollFactor(x, y);
}
return this;
}
/**
* Sets the x position of this object.
*
* @param x
* @returns This Flex instance.
*/
setX(x: number): Flex {
this.x = x;
setItems(this);
return this;
}
/**
* Sets the y position of this object.
*
* @param y
* @returns This Flex instance.
*/
setY(y: number): Flex {
this.y = y;
setItems(this);
return this;
}
/**
* Updates items positions. Should be used only if any item have changed its size.
*/
update() {
let change = false;
if (this.flexDirection == FlexDirection.ROW) {
this.items.forEach(item => {
if (item.basis != item.width) {
this._basisSum += item.width - item.basis;
item.basis = item.width;
change = true;
}
});
} else {
this.items.forEach(item => {
if (item.basis != item.height) {
this._basisSum += item.height - item.height;
item.basis = item.height;
change = true;
}
});
}
if (change) setItems(this);
}
}
export { Flex };