Source: scene/view/ViewContainer.ts

import { type InstructionSet } from '../../rendering/renderers/shared/instructions/InstructionSet';
import { type RenderPipe } from '../../rendering/renderers/shared/instructions/RenderPipe';
import { type Renderer } from '../../rendering/renderers/types';
import { Bounds } from '../container/bounds/Bounds';
import { Container } from '../container/Container';
import { type IRenderLayer } from '../layers/RenderLayer';

import type { PointData } from '../../maths/point/PointData';
import type { View } from '../../rendering/renderers/shared/view/View';
import type { DestroyOptions } from '../container/destroyTypes';

/**
 * A ViewContainer is a type of container that represents a view.
 * This view can be a Sprite, a Graphics object, or any other object that can be rendered.
 * This class is abstract and should not be used directly.
 * @memberof scene
 */
export abstract class ViewContainer extends Container implements View
{
    /** @private */
    public override readonly renderPipeId: string;
    /** @private */
    public readonly canBundle = true;
    /** @private */
    public override allowChildren = false;

    /** @private */
    public _roundPixels: 0 | 1 = 0;
    /** @private */
    public _lastUsed = -1;

    protected _bounds: Bounds = new Bounds(0, 1, 0, 0);
    protected _boundsDirty = true;

    /**
     * The local bounds of the view.
     * @type {rendering.Bounds}
     */
    public get bounds()
    {
        if (!this._boundsDirty) return this._bounds;

        this.updateBounds();

        this._boundsDirty = false;

        return this._bounds;
    }

    /** @private */
    protected abstract updateBounds(): void;

    /**
     * Whether or not to round the x/y position of the sprite.
     * @type {boolean}
     */
    get roundPixels()
    {
        return !!this._roundPixels;
    }

    set roundPixels(value: boolean)
    {
        this._roundPixels = value ? 1 : 0;
    }

    /**
     * Checks if the object contains the given point.
     * @param point - The point to check
     */
    public containsPoint(point: PointData)
    {
        const bounds = this.bounds;
        const { x, y } = point;

        return (x >= bounds.minX
            && x <= bounds.maxX
            && y >= bounds.minY
            && y <= bounds.maxY);
    }

    /** @private */
    public abstract batched: boolean;

    /** @private */
    protected onViewUpdate()
    {
        this._didViewChangeTick++;

        this._boundsDirty = true;

        if (this.didViewUpdate) return;
        this.didViewUpdate = true;

        const renderGroup = this.renderGroup || this.parentRenderGroup;

        if (renderGroup)
        {
            renderGroup.onChildViewUpdate(this);
        }
    }

    public override destroy(options?: DestroyOptions): void
    {
        super.destroy(options);

        this._bounds = null;
    }

    public override collectRenderablesSimple(
        instructionSet: InstructionSet,
        renderer: Renderer,
        currentLayer: IRenderLayer,
    ): void
    {
        const { renderPipes, renderableGC } = renderer;

        // TODO add blends in
        renderPipes.blendMode.setBlendMode(this, this.groupBlendMode, instructionSet);

        const rp = renderPipes as unknown as Record<string, RenderPipe>;

        rp[this.renderPipeId].addRenderable(this, instructionSet);

        renderableGC.addRenderable(this);

        this.didViewUpdate = false;

        const children = this.children;
        const length = children.length;

        for (let i = 0; i < length; i++)
        {
            children[i].collectRenderables(instructionSet, renderer, currentLayer);
        }
    }
}