/* * 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 type { Dimensions } from '../../common/CommonTypes.js'; import { assertTruthy } from '../../utils.js'; import type { CoreTextureManager } from '../CoreTextureManager.js'; import { ImageTexture } from './ImageTexture.js'; import { Texture, TextureType, type TextureData, type TextureFailedEventHandler, type TextureLoadedEventHandler, type TextureState, } from './Texture.js'; let subTextureId = 0; /** * Properties of the {@link SubTexture} */ export interface SubTextureProps { /** * The texture that this sub-texture is a sub-region of. */ texture: Texture; /** * The x pixel position of the top-left of the sub-texture within the parent * texture. * * @default 0 */ x?: number; /** * The y pixel position of the top-left sub-texture within the parent * texture. * * @default 0 **/ y?: number; /** * The width of the sub-texture in pixels. * * @default 0 */ w?: number; /** * The height of the sub-texture in pixels **/ h?: number; } /** * A Texture that is a sub-region of another Texture. * * @remarks * The parent texture can be a Sprite Sheet/Texture Atlas and set using the * {@link SubTextureProps.texture} prop. The sub-region relative to the parent * texture is defined with the {@link SubTextureProps.x}, * {@link SubTextureProps.y}, {@link SubTextureProps.w}, and * {@link SubTextureProps.h} pixel values. */ export class SubTexture extends Texture { props: Required; parentTexture: Texture; public override type: TextureType = TextureType.subTexture; public subtextureId = `subtexture-${subTextureId++}`; constructor(txManager: CoreTextureManager, props: Required) { super(txManager); this.props = props; assertTruthy(props.texture, 'SubTexture requires a parent texture'); assertTruthy( props.texture instanceof ImageTexture, 'SubTexture requires an ImageTexture parent', ); // Resolve parent texture from cache or fallback to provided texture this.parentTexture = txManager.resolveParentTexture(props.texture); if (this.renderableOwners.length > 0) { this.parentTexture.setRenderableOwner(this.subtextureId, true); } // If parent 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(() => { const parentTx = this.parentTexture; if (parentTx.state === 'loaded' && parentTx.dimensions !== null) { this.onParentTxLoaded(parentTx, parentTx.dimensions); } else if (parentTx.state === 'loading') { this.onParentTxLoading(); } else if (parentTx.state === 'failed' && parentTx.error !== null) { this.onParentTxFailed(parentTx, parentTx.error); } else if (parentTx.state === 'freed') { this.onParentTxFreed(); } parentTx.on('loading', this.onParentTxLoading); parentTx.on('loaded', this.onParentTxLoaded); parentTx.on('failed', this.onParentTxFailed); parentTx.on('freed', this.onParentTxFreed); }); } private onParentTxLoaded: TextureLoadedEventHandler = () => { // We ignore the parent's passed dimensions, and simply use the SubTexture's // configured dimensions (because that's all that matters here) this.setState('loaded', { w: this.props.w, h: this.props.h, }); }; private onParentTxFailed: TextureFailedEventHandler = (target, error) => { this.retryCount = this.parentTexture.retryCount - 1; this.setState('failed', error); }; private onParentTxLoading = () => { this.setState('loading'); }; private onParentTxFreed = () => { this.setState('freed'); }; override onChangeIsRenderable(isRenderable: boolean): void { // Propagate the renderable owner change to the parent texture this.parentTexture.setRenderableOwner(this.subtextureId, isRenderable); } override async getTextureSource(): Promise { // Check if parent texture is loaded return new Promise((resolve, reject) => { resolve({ data: this.props, }); }); } // eslint-disable-next-line @typescript-eslint/no-unused-vars static override makeCacheKey(props: SubTextureProps): string | false { return false; } static override resolveDefaults( props: SubTextureProps, ): Required { return { texture: props.texture, x: props.x || 0, y: props.y || 0, w: props.w || 0, h: props.h || 0, }; } static z$__type__Props: SubTextureProps; }