import { Matrix } from '../../../maths/matrix/Matrix';
import { Bounds } from '../bounds/Bounds';
import { getGlobalBounds } from '../bounds/getGlobalBounds';
import { getLocalBounds } from '../bounds/getLocalBounds';
import { checkChildrenDidChange } from '../utils/checkChildrenDidChange';
import type { Size } from '../../../maths/misc/Size';
import type { Container } from '../Container';
export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
export interface MeasureMixinConstructor
{
width?: number;
height?: number;
}
export interface MeasureMixin extends Required<MeasureMixinConstructor>
{
getSize(out?: Size): Size;
setSize(width: number, height?: number): void;
setSize(value: Optional<Size, 'height'>): void;
getLocalBounds(bounds?: Bounds): Bounds;
getBounds(skipUpdate?: boolean, bounds?: Bounds): Bounds;
_localBoundsCacheData: LocalBoundsCacheData;
_localBoundsCacheId: number;
_setWidth(width: number, localWidth: number): void;
_setHeight(height: number, localHeight: number): void;
}
interface LocalBoundsCacheData
{
data: number[];
index: number;
didChange: boolean;
localBounds: Bounds;
}
const tempMatrix = new Matrix();
export const measureMixin: Partial<Container> = {
_localBoundsCacheId: -1,
_localBoundsCacheData: null,
/**
* The width of the Container, setting this will actually modify the scale to achieve the value set.
* @memberof scene.Container#
*/
get width(): number
{
return Math.abs(this.scale.x * this.getLocalBounds().width);
},
set width(value: number)
{
const localWidth = this.getLocalBounds().width;
this._setWidth(value, localWidth);
},
/**
* The height of the Container, setting this will actually modify the scale to achieve the value set.
* @memberof scene.Container#
*/
get height(): number
{
return Math.abs(this.scale.y * this.getLocalBounds().height);
},
set height(value: number)
{
const localHeight = this.getLocalBounds().height;
this._setHeight(value, localHeight);
},
/**
* Retrieves the size of the container as a Size object.
* This is faster than get the width and height separately.
* @param out - Optional object to store the size in.
* @returns - The size of the container.
* @memberof scene.Container#
*/
getSize(out?: Size): Size
{
if (!out)
{
out = {} as Size;
}
const bounds = this.getLocalBounds();
out.width = Math.abs(this.scale.x * bounds.width);
out.height = Math.abs(this.scale.y * bounds.height);
return out;
},
/**
* Sets the size of the container to the specified width and height.
* This is faster than setting the width and height separately.
* @param value - This can be either a number or a Size object.
* @param height - The height to set. Defaults to the value of `width` if not provided.
* @memberof scene.Container#
*/
setSize(value: number | Optional<Size, 'height'>, height?: number)
{
const size = this.getLocalBounds();
let convertedWidth: number;
let convertedHeight: number;
if (typeof value !== 'object')
{
convertedWidth = value;
convertedHeight = height ?? value;
}
else
{
convertedWidth = value.width;
convertedHeight = value.height ?? value.width;
}
if (convertedWidth !== undefined)
{
this._setWidth(convertedWidth, size.width);
}
if (convertedHeight !== undefined)
{
this._setHeight(convertedHeight, size.height);
}
},
_setWidth(value: number, localWidth: number)
{
const sign = Math.sign(this.scale.x) || 1;
if (localWidth !== 0)
{
this.scale.x = (value / localWidth) * sign;
}
else
{
this.scale.x = sign;
}
},
_setHeight(value: number, localHeight: number)
{
const sign = Math.sign(this.scale.y) || 1;
if (localHeight !== 0)
{
this.scale.y = (value / localHeight) * sign;
}
else
{
this.scale.y = sign;
}
},
/**
* Retrieves the local bounds of the container as a Bounds object.
* @returns - The bounding area.
* @memberof scene.Container#
*/
getLocalBounds(): Bounds
{
if (!this._localBoundsCacheData)
{
this._localBoundsCacheData = {
data: [],
index: 1,
didChange: false,
localBounds: new Bounds()
};
}
const localBoundsCacheData = this._localBoundsCacheData;
localBoundsCacheData.index = 1;
localBoundsCacheData.didChange = false;
if (localBoundsCacheData.data[0] !== this._didChangeId >> 12)
{
localBoundsCacheData.didChange = true;
localBoundsCacheData.data[0] = this._didChangeId >> 12;
}
checkChildrenDidChange(this, localBoundsCacheData);
if (localBoundsCacheData.didChange)
{
getLocalBounds(this, localBoundsCacheData.localBounds, tempMatrix);
}
return localBoundsCacheData.localBounds;
},
/**
* Calculates and returns the (world) bounds of the display object as a Rectangle.
* @param skipUpdate - Setting to `true` will stop the transforms of the scene graph from
* being updated. This means the calculation returned MAY be out of date BUT will give you a
* nice performance boost.
* @param bounds - Optional bounds to store the result of the bounds calculation.
* @returns - The minimum axis-aligned rectangle in world space that fits around this object.
* @memberof scene.Container#
*/
getBounds(skipUpdate?: boolean, bounds?: Bounds): Bounds
{
return getGlobalBounds(this, skipUpdate, bounds || new Bounds());
},
} as Container;