@lightningjs/renderer
Version:
Lightning 3 Renderer
1,174 lines • 40.7 kB
JavaScript
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2023 Comcast Cable Communications Management, LLC.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { assertTruthy, getNewId, mergeColorAlphaPremultiplied, } from '../utils.js';
import { EventEmitter } from '../common/EventEmitter.js';
import { copyRect, intersectRect, createBound, boundInsideBound, } from './lib/utils.js';
import { Matrix3d } from './lib/Matrix3d.js';
import { RenderCoords } from './lib/RenderCoords.js';
import { CoreAnimation } from './animations/CoreAnimation.js';
import { CoreAnimationController } from './animations/CoreAnimationController.js';
export var LngNodeRenderState;
(function (LngNodeRenderState) {
LngNodeRenderState[LngNodeRenderState["Init"] = 0] = "Init";
LngNodeRenderState[LngNodeRenderState["OutOfBounds"] = 2] = "OutOfBounds";
LngNodeRenderState[LngNodeRenderState["InBounds"] = 4] = "InBounds";
LngNodeRenderState[LngNodeRenderState["InViewport"] = 8] = "InViewport";
})(LngNodeRenderState || (LngNodeRenderState = {}));
const LngNodeRenderStateMap = new Map();
LngNodeRenderStateMap.set(LngNodeRenderState.Init, 'init');
LngNodeRenderStateMap.set(LngNodeRenderState.OutOfBounds, 'outOfBounds');
LngNodeRenderStateMap.set(LngNodeRenderState.InBounds, 'inBounds');
LngNodeRenderStateMap.set(LngNodeRenderState.InViewport, 'inViewport');
export var UpdateType;
(function (UpdateType) {
/**
* Child updates
*/
UpdateType[UpdateType["Children"] = 1] = "Children";
/**
* Scale/Rotate transform update
*
* @remarks
* LngNode Properties Updated:
* - `scaleRotateTransform`
*/
UpdateType[UpdateType["ScaleRotate"] = 2] = "ScaleRotate";
/**
* Translate transform update (x/y/width/height/pivot/mount)
*
* @remarks
* LngNode Properties Updated:
* - `localTransform`
*/
UpdateType[UpdateType["Local"] = 4] = "Local";
/**
* Global Transform update
*
* @remarks
* LngNode Properties Updated:
* - `globalTransform`
* - `renderCoords`
* - `renderBound`
*/
UpdateType[UpdateType["Global"] = 8] = "Global";
/**
* Clipping rect update
*
* @remarks
* LngNode Properties Updated:
* - `clippingRect`
*/
UpdateType[UpdateType["Clipping"] = 16] = "Clipping";
/**
* Calculated ZIndex update
*
* @remarks
* LngNode Properties Updated:
* - `calcZIndex`
*/
UpdateType[UpdateType["CalculatedZIndex"] = 32] = "CalculatedZIndex";
/**
* Z-Index Sorted Children update
*
* @remarks
* LngNode Properties Updated:
* - `children` (sorts children by their `calcZIndex`)
*/
UpdateType[UpdateType["ZIndexSortedChildren"] = 64] = "ZIndexSortedChildren";
/**
* Premultiplied Colors update
*
* @remarks
* LngNode Properties Updated:
* - `premultipliedColorTl`
* - `premultipliedColorTr`
* - `premultipliedColorBl`
* - `premultipliedColorBr`
*/
UpdateType[UpdateType["PremultipliedColors"] = 128] = "PremultipliedColors";
/**
* World Alpha update
*
* @remarks
* LngNode Properties Updated:
* - `worldAlpha` = `parent.worldAlpha` * `alpha`
*/
UpdateType[UpdateType["WorldAlpha"] = 256] = "WorldAlpha";
/**
* Render State update
*
* @remarks
* LngNode Properties Updated:
* - `renderState`
*/
UpdateType[UpdateType["RenderState"] = 512] = "RenderState";
/**
* Is Renderable update
*
* @remarks
* LngNode Properties Updated:
* - `isRenderable`
*/
UpdateType[UpdateType["IsRenderable"] = 1024] = "IsRenderable";
/**
* Render Texture update
*/
UpdateType[UpdateType["RenderTexture"] = 2048] = "RenderTexture";
/**
* Track if parent has render texture
*/
UpdateType[UpdateType["ParentRenderTexture"] = 4096] = "ParentRenderTexture";
/**
* None
*/
UpdateType[UpdateType["None"] = 0] = "None";
/**
* All
*/
UpdateType[UpdateType["All"] = 8191] = "All";
})(UpdateType || (UpdateType = {}));
/**
* A visual Node in the Renderer scene graph.
*
* @remarks
* A Node is a basic building block of the Renderer scene graph. It can be a
* container for other Nodes, or it can be a leaf Node that renders a solid
* color, gradient, image, or specific texture, using a specific shader.
*
* For text rendering, see {@link TextNode}.
*
* This is named `LngNode` because the browser already defines a `Node` class.
*/
export class LngNode extends EventEmitter {
stage;
children = [];
_id = getNewId();
props;
updateType = UpdateType.All;
globalTransform;
scaleRotateTransform;
localTransform;
renderCoords;
renderBound;
strictBound;
preloadBound;
clippingRect = {
x: 0,
y: 0,
width: 0,
height: 0,
valid: false,
};
isRenderable = false;
renderState = LngNodeRenderState.Init;
worldAlpha = 1;
premultipliedColorTl = 0;
premultipliedColorTr = 0;
premultipliedColorBl = 0;
premultipliedColorBr = 0;
calcZIndex = 0;
hasRTTupdates = false;
parentHasRenderTexture = false;
_shader = null;
_src = '';
constructor(stage, props) {
super();
this.stage = stage;
this.props = {
...props,
parent: null,
texture: null,
shader: null,
src: '',
rtt: false,
data: props.data || {},
};
// Assign props to instance
this.parent = props.parent;
this.shader = props.shader;
this.texture = props.texture;
this.src = props.src || '';
// FIXME
// this.data = props.data;
this.rtt = props.rtt;
this.updateScaleRotateTransform();
}
//#region Textures
loadTexture() {
const { texture } = this.props;
assertTruthy(texture);
// If texture is already loaded / failed, trigger loaded event manually
// so that users get a consistent event experience.
// We do this in a microtask to allow listeners to be attached in the same
// synchronous task after calling loadTexture()
queueMicrotask(() => {
// Preload texture if required
if (this.textureOptions.preload) {
this.stage.txManager.getCtxTexture(texture).load();
}
if (texture.state === 'loaded') {
this.onTextureLoaded(texture, texture.dimensions);
}
else if (texture.state === 'failed') {
this.onTextureFailed(texture, texture.error);
}
else if (texture.state === 'freed') {
this.onTextureFreed(texture);
}
texture.on('loaded', this.onTextureLoaded);
texture.on('failed', this.onTextureFailed);
texture.on('freed', this.onTextureFreed);
});
}
unloadTexture() {
if (this.texture) {
this.texture.off('loaded', this.onTextureLoaded);
this.texture.off('failed', this.onTextureFailed);
this.texture.off('freed', this.onTextureFreed);
this.texture.setRenderableOwner(this, false);
}
}
autosizeNode(dimensions) {
if (this.autosize) {
this.width = dimensions.width;
this.height = dimensions.height;
}
}
onTextureLoaded = (target, dimensions) => {
this.autosizeNode(dimensions);
// Texture was loaded. In case the RAF loop has already stopped, we request
// a render to ensure the texture is rendered.
this.stage.requestRender();
// If parent has a render texture, flag that we need to update
// @todo: Reserve type for RTT updates
if (this.parentHasRenderTexture) {
this.setRTTUpdates(1);
}
this.emit('loaded', {
type: 'texture',
dimensions,
});
};
onTextureFailed = (target, error) => {
this.emit('failed', {
type: 'texture',
error,
});
};
onTextureFreed = (target) => {
this.emit('freed', {
type: 'texture',
});
};
//#endregion Textures
loadShader(shaderType, props) {
const shManager = this.stage.renderer.getShaderManager();
assertTruthy(shManager);
const { shader, props: p } = shManager.loadShader(shaderType, props);
this._shader = shader;
this.props.shaderProps = p;
this.setUpdateType(UpdateType.IsRenderable);
}
/**
* Change types types is used to determine the scope of the changes being applied
*
* @remarks
* See {@link UpdateType} for more information on each type
*
* @param type
*/
setUpdateType(type) {
this.updateType |= type;
// If we're updating this node at all, we need to inform the parent
// (and all ancestors) that their children need updating as well
const parent = this.props.parent;
if (parent && !(parent.updateType & UpdateType.Children)) {
parent.setUpdateType(UpdateType.Children);
}
// If node is part of RTT texture
// Flag that we need to update
if (this.parentHasRenderTexture) {
this.setRTTUpdates(type);
}
}
sortChildren() {
this.children.sort((a, b) => a.calcZIndex - b.calcZIndex);
}
updateScaleRotateTransform() {
this.scaleRotateTransform = Matrix3d.rotate(this.props.rotation, this.scaleRotateTransform).scale(this.props.scaleX, this.props.scaleY);
}
updateLocalTransform() {
assertTruthy(this.scaleRotateTransform);
const pivotTranslateX = this.props.pivotX * this.props.width;
const pivotTranslateY = this.props.pivotY * this.props.height;
const mountTranslateX = this.props.mountX * this.props.width;
const mountTranslateY = this.props.mountY * this.props.height;
this.localTransform = Matrix3d.translate(pivotTranslateX - mountTranslateX + this.props.x, pivotTranslateY - mountTranslateY + this.props.y, this.localTransform)
.multiply(this.scaleRotateTransform)
.translate(-pivotTranslateX, -pivotTranslateY);
this.setUpdateType(UpdateType.Global);
}
/**
* @todo: test for correct calculation flag
* @param delta
*/
update(delta, parentClippingRect) {
if (this.updateType & UpdateType.ScaleRotate) {
this.updateScaleRotateTransform();
this.setUpdateType(UpdateType.Local);
}
if (this.updateType & UpdateType.Local) {
this.updateLocalTransform();
this.setUpdateType(UpdateType.Global);
}
const parent = this.props.parent;
let childUpdateType = UpdateType.None;
if (this.updateType & UpdateType.ParentRenderTexture) {
let p = this.parent;
while (p) {
if (p.rtt) {
this.parentHasRenderTexture = true;
}
p = p.parent;
}
}
// If we have render texture updates and not already running a full update
if (this.updateType ^ UpdateType.All &&
this.updateType & UpdateType.RenderTexture) {
this.children.forEach((child) => {
child.setUpdateType(UpdateType.All);
});
}
if (this.updateType & UpdateType.Global) {
assertTruthy(this.localTransform);
this.globalTransform = Matrix3d.copy(parent?.globalTransform || this.localTransform, this.globalTransform);
if (this.parentHasRenderTexture && this.props.parent?.rtt) {
this.globalTransform = Matrix3d.identity();
}
if (parent) {
this.globalTransform.multiply(this.localTransform);
}
this.calculateRenderCoords();
this.updateBoundingRect();
this.setUpdateType(UpdateType.Clipping | UpdateType.RenderState | UpdateType.Children);
childUpdateType |= UpdateType.Global;
}
if (this.updateType & UpdateType.Clipping) {
this.calculateClippingRect(parentClippingRect);
this.setUpdateType(UpdateType.Children);
childUpdateType |= UpdateType.Clipping;
}
if (this.updateType & UpdateType.WorldAlpha) {
if (parent) {
this.worldAlpha = parent.worldAlpha * this.props.alpha;
}
else {
this.worldAlpha = this.props.alpha;
}
this.setUpdateType(UpdateType.Children |
UpdateType.PremultipliedColors |
UpdateType.IsRenderable);
childUpdateType |= UpdateType.WorldAlpha;
}
if (this.updateType & UpdateType.PremultipliedColors) {
this.premultipliedColorTl = mergeColorAlphaPremultiplied(this.props.colorTl, this.worldAlpha, true);
// If all the colors are the same just sent them all to the same value
if (this.props.colorTl === this.props.colorTr &&
this.props.colorBl === this.props.colorBr &&
this.props.colorTl === this.props.colorBl) {
this.premultipliedColorTr =
this.premultipliedColorBl =
this.premultipliedColorBr =
this.premultipliedColorTl;
}
else {
this.premultipliedColorTr = mergeColorAlphaPremultiplied(this.props.colorTr, this.worldAlpha, true);
this.premultipliedColorBl = mergeColorAlphaPremultiplied(this.props.colorBl, this.worldAlpha, true);
this.premultipliedColorBr = mergeColorAlphaPremultiplied(this.props.colorBr, this.worldAlpha, true);
}
}
if (this.updateType & UpdateType.RenderState) {
this.updateRenderState(parentClippingRect);
this.setUpdateType(UpdateType.IsRenderable);
}
if (this.updateType & UpdateType.IsRenderable) {
this.updateIsRenderable();
}
// No need to update zIndex if there is no parent
if (parent && this.updateType & UpdateType.CalculatedZIndex) {
this.calculateZIndex();
// Tell parent to re-sort children
parent.setUpdateType(UpdateType.ZIndexSortedChildren);
}
if (this.updateType & UpdateType.Children &&
this.children.length &&
!this.rtt) {
this.children.forEach((child) => {
// Trigger the depenedent update types on the child
child.setUpdateType(childUpdateType);
// If child has no updates, skip
if (child.updateType === 0) {
return;
}
child.update(delta, this.clippingRect);
});
}
// Sorting children MUST happen after children have been updated so
// that they have the oppotunity to update their calculated zIndex.
if (this.updateType & UpdateType.ZIndexSortedChildren) {
// reorder z-index
this.sortChildren();
}
// reset update type
this.updateType = 0;
}
//check if LngNode is renderable based on props
checkRenderProps() {
if (this.props.texture) {
return true;
}
if (!this.props.width || !this.props.height) {
return false;
}
if (this._shader) {
return true;
}
if (this.props.clipping) {
return true;
}
if (this.props.color !== 0) {
return true;
}
// Consider removing these checks and just using the color property check above.
// Maybe add a forceRender prop for nodes that should always render.
if (this.props.colorTop !== 0) {
return true;
}
if (this.props.colorBottom !== 0) {
return true;
}
if (this.props.colorLeft !== 0) {
return true;
}
if (this.props.colorRight !== 0) {
return true;
}
if (this.props.colorTl !== 0) {
return true;
}
if (this.props.colorTr !== 0) {
return true;
}
if (this.props.colorBl !== 0) {
return true;
}
if (this.props.colorBr !== 0) {
return true;
}
return false;
}
checkRenderBounds(parentClippingRect) {
assertTruthy(this.renderBound);
const rectW = parentClippingRect.width || this.stage.root.width;
const rectH = parentClippingRect.height || this.stage.root.height;
this.strictBound = createBound(parentClippingRect.x, parentClippingRect.y, parentClippingRect.x + rectW, parentClippingRect.y + rectH, this.strictBound);
const renderM = this.stage.boundsMargin;
this.preloadBound = createBound(parentClippingRect.x - renderM[3], parentClippingRect.y - renderM[0], parentClippingRect.x + rectW + renderM[1], parentClippingRect.y + rectH + renderM[2], this.preloadBound);
if (boundInsideBound(this.renderBound, this.strictBound)) {
return LngNodeRenderState.InViewport;
}
if (boundInsideBound(this.renderBound, this.preloadBound)) {
return LngNodeRenderState.InBounds;
}
return LngNodeRenderState.OutOfBounds;
}
updateRenderState(parentClippingRect) {
const renderState = this.checkRenderBounds(parentClippingRect);
if (renderState !== this.renderState) {
let previous = this.renderState;
this.renderState = renderState;
if (previous === LngNodeRenderState.InViewport) {
this.emit('outOfViewport', {
previous,
current: renderState,
});
}
if (previous < LngNodeRenderState.InBounds &&
renderState === LngNodeRenderState.InViewport) {
this.emit(LngNodeRenderStateMap.get(LngNodeRenderState.InBounds), {
previous,
current: renderState,
});
previous = LngNodeRenderState.InBounds;
}
else if (previous > LngNodeRenderState.InBounds &&
renderState === LngNodeRenderState.OutOfBounds) {
this.emit(LngNodeRenderStateMap.get(LngNodeRenderState.InBounds), {
previous,
current: renderState,
});
previous = LngNodeRenderState.InBounds;
}
const event = LngNodeRenderStateMap.get(renderState);
assertTruthy(event);
this.emit(event, {
previous,
current: renderState,
});
}
}
setRenderState(state) {
if (state !== this.renderState) {
this.renderState = state;
this.emit(LngNodeRenderState[state]);
}
}
/**
* This function updates the `isRenderable` property based on certain conditions.
*
* @returns
*/
updateIsRenderable() {
let newIsRenderable;
if (this.worldAlpha === 0 || !this.checkRenderProps()) {
newIsRenderable = false;
}
else {
newIsRenderable = this.renderState > LngNodeRenderState.OutOfBounds;
}
if (this.isRenderable !== newIsRenderable) {
this.isRenderable = newIsRenderable;
this.onChangeIsRenderable(newIsRenderable);
}
}
onChangeIsRenderable(isRenderable) {
this.texture?.setRenderableOwner(this, isRenderable);
}
calculateRenderCoords() {
const { width, height, globalTransform: transform } = this;
assertTruthy(transform);
const { tx, ty, ta, tb, tc, td } = transform;
if (tb === 0 && tc === 0) {
const minX = tx;
const maxX = tx + width * ta;
const minY = ty;
const maxY = ty + height * td;
this.renderCoords = RenderCoords.translate(
//top-left
minX, minY,
//top-right
maxX, minY,
//bottom-right
maxX, maxY,
//bottom-left
minX, maxY, this.renderCoords);
}
else {
this.renderCoords = RenderCoords.translate(
//top-left
tx, ty,
//top-right
tx + width * ta, ty + width * tc,
//bottom-right
tx + width * ta + height * tb, ty + width * tc + height * td,
//bottom-left
tx + height * tb, ty + height * td, this.renderCoords);
}
}
updateBoundingRect() {
const { renderCoords, globalTransform: transform } = this;
assertTruthy(transform);
assertTruthy(renderCoords);
const { tb, tc } = transform;
const { x1, y1, x3, y3 } = renderCoords;
if (tb === 0 || tc === 0) {
this.renderBound = createBound(x1, y1, x3, y3, this.renderBound);
}
else {
const { x2, x4, y2, y4 } = renderCoords;
this.renderBound = createBound(Math.min(x1, x2, x3, x4), Math.min(y1, y2, y3, y4), Math.max(x1, x2, x3, x4), Math.max(y1, y2, y3, y4), this.renderBound);
}
}
/**
* This function calculates the clipping rectangle for a node.
*
* The function then checks if the node is rotated. If the node requires clipping and is not rotated, a new clipping rectangle is created based on the node's global transform and dimensions.
* If a parent clipping rectangle exists, it is intersected with the node's clipping rectangle (if it exists), or replaces the node's clipping rectangle.
*
* Finally, the node's parentClippingRect and clippingRect properties are updated.
*/
calculateClippingRect(parentClippingRect) {
assertTruthy(this.globalTransform);
const { clippingRect, props, globalTransform: gt } = this;
const { clipping } = props;
const isRotated = gt.tb !== 0 || gt.tc !== 0;
if (clipping && !isRotated) {
clippingRect.x = gt.tx;
clippingRect.y = gt.ty;
clippingRect.width = this.width * gt.ta;
clippingRect.height = this.height * gt.td;
clippingRect.valid = true;
}
else {
clippingRect.valid = false;
}
if (parentClippingRect.valid && clippingRect.valid) {
// Intersect parent clipping rect with node clipping rect
intersectRect(parentClippingRect, clippingRect, clippingRect);
}
else if (parentClippingRect.valid) {
// Copy parent clipping rect
copyRect(parentClippingRect, clippingRect);
clippingRect.valid = true;
}
}
calculateZIndex() {
const props = this.props;
const z = props.zIndex || 0;
const p = props.parent?.zIndex || 0;
let zIndex = z;
if (props.parent?.zIndexLocked) {
zIndex = z < p ? z : p;
}
this.calcZIndex = zIndex;
}
/**
* Destroy the node and cleanup all resources
*/
destroy() {
this.unloadTexture();
this.clippingRect.valid = false;
this.isRenderable = false;
delete this.renderCoords;
delete this.renderBound;
delete this.strictBound;
delete this.preloadBound;
delete this.globalTransform;
delete this.scaleRotateTransform;
delete this.localTransform;
this.props.texture = null;
this.props.shader = null;
this.props.shaderProps = null;
this._shader = null;
if (this.rtt) {
this.stage.renderer.removeRTTNode(this);
}
this.removeAllListeners();
this.parent = null;
}
renderQuads(renderer) {
const { texture, width, height, textureOptions, shaderProps, rtt } = this.props;
const shader = this._shader;
// Prevent quad rendering if parent has a render texture
// and renderer is not currently rendering to a texture
if (this.parentHasRenderTexture) {
if (!renderer.renderToTextureActive) {
return;
}
// Prevent quad rendering if parent render texture is not the active render texture
if (this.parentRenderTexture !== renderer.activeRttNode) {
return;
}
}
const { premultipliedColorTl, premultipliedColorTr, premultipliedColorBl, premultipliedColorBr, } = this;
const { zIndex, worldAlpha, globalTransform: gt, clippingRect } = this;
assertTruthy(gt);
// add to list of renderables to be sorted before rendering
renderer.addQuad({
width,
height,
colorTl: premultipliedColorTl,
colorTr: premultipliedColorTr,
colorBl: premultipliedColorBl,
colorBr: premultipliedColorBr,
texture,
textureOptions,
zIndex,
shader,
shaderProps,
alpha: worldAlpha,
clippingRect,
tx: gt.tx,
ty: gt.ty,
ta: gt.ta,
tb: gt.tb,
tc: gt.tc,
td: gt.td,
rtt,
parentHasRenderTexture: this.parentHasRenderTexture,
framebufferDimensions: this.framebufferDimensions,
});
}
//#region Properties
get id() {
return this._id;
}
get x() {
return this.props.x;
}
set x(value) {
if (this.props.x !== value) {
this.props.x = value;
this.setUpdateType(UpdateType.Local);
}
}
get absX() {
return (this.props.x +
(this.props.parent?.absX || this.props.parent?.globalTransform?.tx || 0));
}
get absY() {
return this.props.y + (this.props.parent?.absY ?? 0);
}
get y() {
return this.props.y;
}
set y(value) {
if (this.props.y !== value) {
this.props.y = value;
this.setUpdateType(UpdateType.Local);
}
}
get width() {
return this.props.width;
}
set width(value) {
if (this.props.width !== value) {
this.props.width = value;
this.setUpdateType(UpdateType.Local);
if (this.props.rtt) {
this.texture = this.stage.txManager.loadTexture('RenderTexture', {
width: this.width,
height: this.height,
});
this.textureOptions = { preload: true };
this.setUpdateType(UpdateType.RenderTexture);
}
}
}
get height() {
return this.props.height;
}
set height(value) {
if (this.props.height !== value) {
this.props.height = value;
this.setUpdateType(UpdateType.Local);
if (this.props.rtt) {
this.texture = this.stage.txManager.loadTexture('RenderTexture', {
width: this.width,
height: this.height,
});
this.textureOptions = { preload: true };
this.setUpdateType(UpdateType.RenderTexture);
}
}
}
get scale() {
// The LngNode `scale` property is only used by Animations.
// Unlike INode, `null` should never be possibility for Animations.
return this.scaleX;
}
set scale(value) {
// The LngNode `scale` property is only used by Animations.
// Unlike INode, `null` should never be possibility for Animations.
this.scaleX = value;
this.scaleY = value;
}
get scaleX() {
return this.props.scaleX;
}
set scaleX(value) {
if (this.props.scaleX !== value) {
this.props.scaleX = value;
this.setUpdateType(UpdateType.ScaleRotate);
}
}
get scaleY() {
return this.props.scaleY;
}
set scaleY(value) {
if (this.props.scaleY !== value) {
this.props.scaleY = value;
this.setUpdateType(UpdateType.ScaleRotate);
}
}
get mount() {
return this.props.mount;
}
set mount(value) {
if (this.props.mountX !== value || this.props.mountY !== value) {
this.props.mountX = value;
this.props.mountY = value;
this.props.mount = value;
this.setUpdateType(UpdateType.Local);
}
}
get mountX() {
return this.props.mountX;
}
set mountX(value) {
if (this.props.mountX !== value) {
this.props.mountX = value;
this.setUpdateType(UpdateType.Local);
}
}
get mountY() {
return this.props.mountY;
}
set mountY(value) {
if (this.props.mountY !== value) {
this.props.mountY = value;
this.setUpdateType(UpdateType.Local);
}
}
get pivot() {
return this.props.pivot;
}
set pivot(value) {
if (this.props.pivotX !== value || this.props.pivotY !== value) {
this.props.pivotX = value;
this.props.pivotY = value;
this.props.pivot = value;
this.setUpdateType(UpdateType.Local);
}
}
get pivotX() {
return this.props.pivotX;
}
set pivotX(value) {
if (this.props.pivotX !== value) {
this.props.pivotX = value;
this.setUpdateType(UpdateType.Local);
}
}
get pivotY() {
return this.props.pivotY;
}
set pivotY(value) {
if (this.props.pivotY !== value) {
this.props.pivotY = value;
this.setUpdateType(UpdateType.Local);
}
}
get rotation() {
return this.props.rotation;
}
set rotation(value) {
if (this.props.rotation !== value) {
this.props.rotation = value;
this.setUpdateType(UpdateType.ScaleRotate);
}
}
get alpha() {
return this.props.alpha;
}
set alpha(value) {
this.props.alpha = value;
this.setUpdateType(UpdateType.PremultipliedColors | UpdateType.WorldAlpha);
}
get autosize() {
return this.props.autosize;
}
set autosize(value) {
this.props.autosize = value;
}
get clipping() {
return this.props.clipping;
}
set clipping(value) {
this.props.clipping = value;
this.setUpdateType(UpdateType.Clipping);
}
get color() {
return this.props.color;
}
set color(value) {
if (this.props.colorTl !== value ||
this.props.colorTr !== value ||
this.props.colorBl !== value ||
this.props.colorBr !== value) {
this.colorTl = value;
this.colorTr = value;
this.colorBl = value;
this.colorBr = value;
}
this.props.color = value;
this.setUpdateType(UpdateType.PremultipliedColors);
}
get colorTop() {
return this.props.colorTop;
}
set colorTop(value) {
if (this.props.colorTl !== value || this.props.colorTr !== value) {
this.colorTl = value;
this.colorTr = value;
}
this.props.colorTop = value;
this.setUpdateType(UpdateType.PremultipliedColors);
}
get colorBottom() {
return this.props.colorBottom;
}
set colorBottom(value) {
if (this.props.colorBl !== value || this.props.colorBr !== value) {
this.colorBl = value;
this.colorBr = value;
}
this.props.colorBottom = value;
this.setUpdateType(UpdateType.PremultipliedColors);
}
get colorLeft() {
return this.props.colorLeft;
}
set colorLeft(value) {
if (this.props.colorTl !== value || this.props.colorBl !== value) {
this.colorTl = value;
this.colorBl = value;
}
this.props.colorLeft = value;
this.setUpdateType(UpdateType.PremultipliedColors);
}
get colorRight() {
return this.props.colorRight;
}
set colorRight(value) {
if (this.props.colorTr !== value || this.props.colorBr !== value) {
this.colorTr = value;
this.colorBr = value;
}
this.props.colorRight = value;
this.setUpdateType(UpdateType.PremultipliedColors);
}
get colorTl() {
return this.props.colorTl;
}
set colorTl(value) {
this.props.colorTl = value;
this.setUpdateType(UpdateType.PremultipliedColors);
}
get colorTr() {
return this.props.colorTr;
}
set colorTr(value) {
this.props.colorTr = value;
this.setUpdateType(UpdateType.PremultipliedColors);
}
get colorBl() {
return this.props.colorBl;
}
set colorBl(value) {
this.props.colorBl = value;
this.setUpdateType(UpdateType.PremultipliedColors);
}
get colorBr() {
return this.props.colorBr;
}
set colorBr(value) {
this.props.colorBr = value;
this.setUpdateType(UpdateType.PremultipliedColors);
}
// we're only interested in parent zIndex to test
// if we should use node zIndex is higher then parent zIndex
get zIndexLocked() {
return this.props.zIndexLocked || 0;
}
set zIndexLocked(value) {
this.props.zIndexLocked = value;
this.setUpdateType(UpdateType.CalculatedZIndex | UpdateType.Children);
this.children.forEach((child) => {
child.setUpdateType(UpdateType.CalculatedZIndex);
});
}
get zIndex() {
return this.props.zIndex;
}
set zIndex(value) {
this.props.zIndex = value;
this.setUpdateType(UpdateType.CalculatedZIndex | UpdateType.Children);
this.children.forEach((child) => {
child.setUpdateType(UpdateType.CalculatedZIndex);
});
}
get parent() {
return this.props.parent;
}
set parent(newParent) {
const oldParent = this.props.parent;
if (oldParent === newParent) {
return;
}
this.props.parent = newParent;
if (oldParent) {
const index = oldParent.children.indexOf(this);
assertTruthy(index !== -1, "LngNode.parent: Node not found in old parent's children!");
oldParent.children.splice(index, 1);
oldParent.setUpdateType(UpdateType.Children | UpdateType.ZIndexSortedChildren);
}
if (newParent) {
newParent.children.push(this);
// Since this node has a new parent, to be safe, have it do a full update.
this.setUpdateType(UpdateType.All);
// Tell parent that it's children need to be updated and sorted.
newParent.setUpdateType(UpdateType.Children | UpdateType.ZIndexSortedChildren);
if (newParent.rtt || newParent.parentHasRenderTexture) {
this.setRTTUpdates(UpdateType.All);
}
}
this.updateScaleRotateTransform();
}
get rtt() {
return this.props.rtt;
}
set rtt(value) {
if (this.props.rtt === true) {
this.props.rtt = value;
// unload texture if we used to have a render texture
if (value === false && this.texture !== null) {
this.unloadTexture();
this.setUpdateType(UpdateType.All);
this.children.forEach((child) => {
child.parentHasRenderTexture = false;
});
this.stage.renderer?.removeRTTNode(this);
return;
}
}
// if the new value is false and we didnt have rtt previously, we don't need to do anything
if (value === false) {
return;
}
// load texture
this.texture = this.stage.txManager.loadTexture('RenderTexture', {
width: this.width,
height: this.height,
});
this.textureOptions = { preload: true };
this.props.rtt = true;
this.hasRTTupdates = true;
this.setUpdateType(UpdateType.All);
this.children.forEach((child) => {
child.setUpdateType(UpdateType.All);
});
// Store RTT nodes in a separate list
this.stage.renderer?.renderToTexture(this);
}
get shader() {
return this.props.shader;
}
set shader(value) {
if (value === null && this._shader === null) {
return;
}
if (value === null) {
this._shader = null;
this.props.shader = null;
this.setUpdateType(UpdateType.IsRenderable);
return;
}
this.props.shader = value;
assertTruthy(value);
this.loadShader(value.shType, value.props);
}
get shaderProps() {
return this.props.shaderProps;
}
set shaderProps(value) {
this.props.shaderProps = value;
}
get src() {
return this._src;
}
set src(imageUrl) {
if (this._src === imageUrl) {
return;
}
this._src = imageUrl;
if (!imageUrl) {
this.texture = null;
return;
}
this.texture = this.stage.txManager.loadTexture('ImageTexture', {
src: imageUrl,
});
}
/**
* Returns the framebuffer dimensions of the node.
* If the node has a render texture, the dimensions are the same as the node's dimensions.
* If the node does not have a render texture, the dimensions are inherited from the parent.
* If the node parent has a render texture and the node is a render texture, the nodes dimensions are used.
*/
get framebufferDimensions() {
if (this.parentHasRenderTexture && !this.rtt && this.parent) {
return this.parent.framebufferDimensions;
}
return { width: this.width, height: this.height };
}
/**
* Returns the parent render texture node if it exists.
*/
get parentRenderTexture() {
let parent = this.parent;
while (parent) {
if (parent.rtt) {
return parent;
}
parent = parent.parent;
}
return null;
}
get texture() {
return this.props.texture;
}
set texture(value) {
if (this.props.texture === value) {
return;
}
const oldTexture = this.props.texture;
if (oldTexture) {
oldTexture.setRenderableOwner(this, false);
this.unloadTexture();
}
this.props.texture = value;
if (value) {
value.setRenderableOwner(this, this.isRenderable);
this.loadTexture();
}
this.setUpdateType(UpdateType.IsRenderable);
}
set textureOptions(value) {
this.props.textureOptions = value;
}
get textureOptions() {
return this.props.textureOptions;
}
setRTTUpdates(type) {
this.hasRTTupdates = true;
this.parent?.setRTTUpdates(type);
}
animate(props, settings) {
const animation = new CoreAnimation(this, props, settings);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
const controller = new CoreAnimationController(this.stage.animationManager, animation);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return controller;
}
flush() {
// no-op
}
}
//# sourceMappingURL=LngNode.js.map