Home Reference Source

cables_dev/cables_ui/src/ui/components/texturepreviewer.js

import { Logger, ele } from "cables-shared-client";
import userSettings from "./usersettings.js";

import srcShaderFragment from "./texturepreviewer_glsl.frag";
import srcShaderVertex from "./texturepreviewer_glsl.vert";

const MODE_CORNER = 0;
const MODE_HOVER = 1;

/**
 * texturepreview floating over the patchfield
 *
 * @export
 * @class TexturePreviewer
 */
export default class TexturePreviewer
{
    constructor()
    {
        this._log = new Logger();

        this._texturePorts = [];
        this._showing = false;
        this._lastTimeActivity = 0;
        this._mode = userSettings.get("texpreviewMode") == "corner" ? MODE_CORNER : MODE_HOVER;
        this._paused = false;
        this._shader = null;
        this._shaderTexUniform = null;
        this._tempTexturePort = null;
        this._hoveringTexPort = false;
        this._listeningFrame = false;
        this._emptyCubemap = null;
        this._timer = new CABLES.Timer();
        this._timer.play();
        this._currentHeight = -1;
        this._currentWidth = -1;
        this._lastClicked = null;
        this.scale = 0.2;

        this._ele = document.getElementById("bgpreview");
        this.setSize();

        userSettings.on("change", (key, v) =>
        {
            if (key == "texpreviewTransparent") this.setSize();
            if (key == "texpreviewSize") this.setSize(userSettings.get(key));
            if (key == "bgpreviewMax") this.enableBgPreview();
        });

        this._initListener();
        this.enableBgPreview();

        if (this._mode == MODE_HOVER)
        {
            this._enabled = false;
            ele.byId("bgpreviewButtonsContainer").classList.add("hidden");
            ele.byId("bgpreview").classList.add("hidden");
        }
    }

    _initListener()
    {
        if (!window.gui)
        {
            setTimeout(this._initListener.bind(this), 300);
            return;
        }

        if (this._mode == MODE_HOVER)
        {
            gui.on("portHovered", (port) =>
            {
                if (port && port.get() && port.get().tex)
                {
                    this._enabled = true;
                    this.selectTexturePort(port);

                    clearTimeout(this._hoverTimeout);
                    this._hoverTimeout = setTimeout(() =>
                    {
                        this._enabled = false;
                    }, 50);
                }
            });
        }

        if (this._mode == MODE_CORNER)
        {
            gui.opParams.on("opSelected", () =>
            {
                if (!gui.opParams.op) return;
                let foundPreview = false;
                const ports = gui.opParams.op.portsOut;

                for (let i = 0; i < ports.length; i++)
                {
                    if (!foundPreview && ports[i].uiAttribs.preview)
                    {
                        this.selectTexturePort(ports[i]);
                        return;
                    }
                }
            });
        }
    }

    needsVizLayer()
    {
        return this._mode == MODE_HOVER;
    }


    _renderTexture(tp, element)
    {
        if (!tp && this._lastClickedP)
        {
            tp = this.updateTexturePort(this._lastClickedP);
        }
        if (!tp || !this._enabled)
        {
            return;
        }

        let port = tp;
        if (tp.port)port = tp.port;

        const id = tp.id;
        const texSlot = 5;
        const texSlotCubemap = texSlot + 1;

        let meta = true;
        if (element)meta = false;

        const previewCanvasEle = element || document.getElementById("preview_img_" + id);

        if (!previewCanvasEle)
        {
            console.log("no previewCanvasEle");
            return;
        }

        const previewCanvas = previewCanvasEle.getContext("2d");

        if (previewCanvas && port && port.get())
        {
            const perf = CABLES.UI.uiProfiler.start("texpreview");
            const cgl = port.op.patch.cgl;

            if (!this._emptyCubemap) this._emptyCubemap = CGL.Texture.getEmptyCubemapTexture(cgl);
            port.op.patch.cgl.profileData.profileTexPreviews++;

            if (!this._mesh)
            {
                const geom = new CGL.Geometry("tex preview rect");
                geom.vertices = [1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, -1.0, 0.0];
                geom.texCoords = [
                    1.0, 1.0,
                    0.0, 1.0,
                    1.0, 0.0,
                    0.0, 0.0];
                geom.verticesIndices = [0, 1, 2, 3, 1, 2];
                this._mesh = new CGL.Mesh(cgl, geom);
            }
            if (!this._shader)
            {
                this._shader = new CGL.Shader(cgl, "texPreviewShader");
                this._shader.setModules(["MODULE_VERTEX_POSITION", "MODULE_COLOR", "MODULE_BEGIN_FRAG"]);
                this._shader.setSource(srcShaderVertex, srcShaderFragment);
                this._shaderTexUniform = new CGL.Uniform(this._shader, "t", "tex", texSlot);
                this._shaderTexCubemapUniform = new CGL.Uniform(this._shader, "tc", "cubeMap", texSlotCubemap);

                this._shaderTexUniformW = new CGL.Uniform(this._shader, "f", "width", port.get().width);
                this._shaderTexUniformH = new CGL.Uniform(this._shader, "f", "height", port.get().height);
                this._shaderTypeUniform = new CGL.Uniform(this._shader, "f", "type", 0);
                this._shaderTimeUniform = new CGL.Uniform(this._shader, "f", "time", 0);
            }

            cgl.pushPMatrix();

            mat4.ortho(cgl.pMatrix, -1, 1, 1, -1, 0.001, 11);

            const oldTex = cgl.getTexture(texSlot);
            const oldTexCubemap = cgl.getTexture(texSlotCubemap);

            let texType = 0;
            if (!port.get()) return;
            if (port.get().cubemap) texType = 1;
            if (port.get().textureType == CGL.Texture.TYPE_DEPTH) texType = 2;

            this._showInfo(port.get());

            if (texType == 0 || texType == 2)
            {
                cgl.setTexture(texSlot, port.get().tex);
                cgl.setTexture(texSlotCubemap, this._emptyCubemap.cubemap, cgl.gl.TEXTURE_CUBE_MAP);
            }
            else if (texType == 1)
            {
                cgl.setTexture(texSlotCubemap, port.get().cubemap, cgl.gl.TEXTURE_CUBE_MAP);
            }

            this._shaderTypeUniform.setValue(texType);

            this._timer.update();
            this._shaderTimeUniform.setValue(this._timer.get());

            this._mesh.render(this._shader);
            if (texType == 0) cgl.setTexture(texSlot, oldTex);
            if (texType == 1) cgl.setTexture(texSlotCubemap, oldTexCubemap);

            cgl.popPMatrix();
            cgl.resetViewPort();

            const s = this._getCanvasSize(port, port.get(), meta);
            if (s[0] == 0 || s[1] == 0) return;


            if (texType == 1)s[0] *= 1.33;

            if (this._currentWidth != s[0] * this.scale || this._currentHeight != s[1] * this.scale)
            {
                this._currentWidth = previewCanvasEle.width = s[0] * this.scale;
                this._currentHeight = previewCanvasEle.height = s[1] * this.scale;
            }

            const perf2 = CABLES.UI.uiProfiler.start("texpreview22");

            // if (this._mode == MODE_CORNER)
            // {
            previewCanvas.clearRect(0, 0, this._currentWidth, previewCanvasEle.height);

            if (this._currentWidth > 0 && cgl.canvas.width > 0 && cgl.canvas.height > 0 && previewCanvasEle.width != 0 && previewCanvasEle.height > 0)
                previewCanvas.drawImage(cgl.canvas, 0, 0, this._currentWidth, previewCanvasEle.height);
            // }

            if (this._mode == MODE_HOVER && this._enabled)
            {
                const vizCtx = gui.patchView.patchRenderer.vizLayer._eleCanvas.getContext("2d");
                vizCtx.save();

                if (userSettings.get("texpreviewTransparent")) vizCtx.globalAlpha = 0.5;

                let w = 150;
                let h = Math.min(150, 150 * previewCanvasEle.height / this._currentWidth);
                let x = Math.round(gui.patchView.patchRenderer.viewBox.mouseX * gui.patchView.patchRenderer._cgl.pixelDensity + 53);
                let y = Math.round(gui.patchView.patchRenderer.viewBox.mouseY * gui.patchView.patchRenderer._cgl.pixelDensity - 0);
                vizCtx.translate(x, y);

                if (w <= 1)w = h / 2;
                if (h <= 1)h = w / 2;

                if (port.get().width < w && port.get().height < h) vizCtx.imageSmoothingEnabled = false;

                vizCtx.scale(1, -1);

                if (w > 0 && h > 0 && cgl.canvas && cgl.canvas.width > 0 && cgl.canvas.height > 0) vizCtx.drawImage(cgl.canvas, 0, 0, w, h);
                vizCtx.scale(1, 1);
                vizCtx.globalAlpha = 1;
                vizCtx.restore();
            }


            perf2.finish();

            cgl.gl.clearColor(0, 0, 0, 0);
            cgl.gl.clear(cgl.gl.COLOR_BUFFER_BIT | cgl.gl.DEPTH_BUFFER_BIT);

            perf.finish();
        }
    }

    drawVizLayer(vizCtx)
    {
        // vizCtx.drawImage(cgl.canvas, 0, 0, w, h);
        // if (this._mode == MODE_HOVER && this._enabled && this._ele && this._ele.width > 0 && this._ele.height > 0)
        // {
        // // const vizCtx = gui.patchView.patchRenderer.vizLayer._eleCanvas.getContext("2d");

        //     if (userSettings.get("texpreviewTransparent")) vizCtx.globalAlpha = 0.5;
        //     vizCtx.save();
        //     let w = 150;
        //     let h = Math.min(150, 150 * this._ele.height / this._currentWidth);
        //     let x = Math.round(gui.patchView.patchRenderer.viewBox.mouseX * gui.patchView.patchRenderer._cgl.pixelDensity + 53);
        //     let y = Math.round(gui.patchView.patchRenderer.viewBox.mouseY * gui.patchView.patchRenderer._cgl.pixelDensity - 0);
        //     vizCtx.translate(x, y);

        //     if (this._ele.width < w && this._ele.height < h)vizCtx.imageSmoothingEnabled = false;


        //     if (w <= 1)w = h / 2;
        //     if (h <= 1)h = w / 2;
        //     vizCtx.scale(1, -1);
        //     vizCtx.drawImage(this._ele, 0, 0, w, h);

        //     vizCtx.strokeStyle = "black";
        //     vizCtx.strokeRect(0, 0, w + 1, h + 1);


        //     vizCtx.scale(1, 1);
        //     vizCtx.globalAlpha = 1;
        //     vizCtx.restore();
        // }
    }


    toggleSize(m)
    {
        let size = userSettings.get("texpreviewSize");

        if (size == null || size == undefined)size = 30;

        if (size % 10 != 0)size = 30;
        size += m * 10;
        if (size <= 0)size = 10;
        if (size > 100)size = 10;

        this.scale = size / 100;

        userSettings.set("texpreviewSize", this.scale * 100);
    }

    setSize(size)
    {
        if (!size)size = userSettings.get("texpreviewSize") || 50;

        if (userSettings.get("texpreviewTransparent")) this._ele.style.opacity = 0.5;
        else this._ele.style.opacity = 1;

        this.scale = size / 100;

        userSettings.set("texpreviewSize", this.scale * 100);
    }

    _getCanvasSize(port, tex, meta)
    {
        let maxWidth = 300;
        let maxHeight = 200;

        if (!meta)
        {
            const patchRect = gui.patchView.element.getBoundingClientRect();
            maxWidth = Math.min(patchRect.width, port.op.patch.cgl.canvasWidth);
            maxHeight = Math.min(patchRect.height, port.op.patch.cgl.canvasHeight);
        }

        const aspect = tex.height / tex.width;
        let w = tex.width;

        if (w > maxWidth)w = maxWidth;
        let h = w * aspect;

        if (h > maxHeight)
        {
            w = maxHeight / aspect;
            h = maxHeight;
        }

        return [w, h];
    }

    _htmlDataObject(o)
    {
        if (o.port.get())
            return {
                "title": o.port.op.getName() + " - " + o.port.name,
                "id": o.id,
                "opid": o.opid,
                "order": parseInt(o.lastTimeClicked, 10),
                "size": o.port.get().width + " x " + o.port.get().height
            };
    }

    showActivity()
    {
        for (let i = 0; i < this._texturePorts.length; i++)
        {
            const activeIndic = document.getElementById("activity" + this._texturePorts[i].id);
            if (activeIndic)
            {
                if (this._texturePorts[i].activity > 0) activeIndic.innerHTML = this._texturePorts[i].activity + " FPS";
                else activeIndic.innerHTML = "";
            }
            this._texturePorts[i].activity = 0;
        }
    }



    enableBgPreview()
    {
        const enabled = userSettings.get("bgpreviewMax");
        this._enabled = enabled;

        // if (storeSetting)
        // {
        //     userSettings.set("bgpreviewMax", enabled);

        //     console.log("store bgpreview max", enabled);
        // }

        // console.log("bgpreviewMax", userSettings.get("bgpreviewMax"), enabled);


        if (this._mode == MODE_CORNER)
        {
            if (!enabled)
            {
                this.pressedEscape();

                ele.byId("bgpreviewInfo").classList.add("hidden");
                ele.byId("bgpreviewMin").classList.add("hidden");
                ele.byId("bgpreviewMax").classList.remove("hidden");
                ele.byId("texprevSize").classList.add("hidden");
                ele.byId("texprevSize2").classList.add("hidden");

                this._ele.classList.add("hidden");
            }
            else
            {
                this.paused = false;
                ele.byId("bgpreviewInfo").classList.remove("hidden");
                ele.byId("bgpreviewMin").classList.remove("hidden");
                ele.byId("bgpreviewMax").classList.add("hidden");
                ele.byId("texprevSize").classList.remove("hidden");
                ele.byId("texprevSize2").classList.remove("hidden");

                this._ele.classList.remove("hidden");

                if (this._lastClicked) this.selectTexturePort(this._lastClickedP);
            }
        }
    }

    hide()
    {
        this._paused = true;

        if (this._mode == MODE_CORNER) CABLES.UI.hideToolTip();
    }

    pressedEscape()
    {
        this.hide();
    }

    render()
    {
        if (!this.paused)
        {
            this._ele.style.display = "block";
            this._renderTexture(this._lastClicked, this._ele);

            if (this._ele.width + "px" != this._ele.style.width || this._ele.height + "px" != this._ele.style.height)
            {
                this._ele.style.width = this._ele.width + "px";
                this._ele.style.height = this._ele.height + "px";
            }
        }
    }

    selectTexturePortId(opid, portid)
    {
        if (!window.gui) return;

        const op = gui.corePatch().getOpById(opid);
        if (!op) return;

        const p = op.getPortById(portid);

        if (!p || p.links.length < 1) return;

        const thePort = p.links[0].getOtherPort(p);
        this.selectTexturePort(thePort);
    }

    hover(p)
    {
        let thePort = p;
        if (p.direction == CABLES.PORT_DIR_IN && p.isLinked())
            thePort = p.links[0].getOtherPort(p);

        if (this._lastClickedP != thePort)
        {
            this._hoveringTexPort = true;
            this._tempOldTexPort = this._lastClickedP;
            this.selectTexturePort(thePort);
        }
    }

    hoverEnd()
    {
        if (this._hoveringTexPort)
        {
            if (!this._tempOldTexPort) this.enableBgPreview(false);
            else this.selectTexturePort(this._tempOldTexPort);
            this._hoveringTexPort = false;
            this._tempOldTexPort = null;
            this._lastClickedP = null;
        }
    }

    selectTexturePort(p)
    {
        if (!userSettings.get("bgpreview"))
        {
            this._lastClickedP = p;
            this._lastClicked = this.updateTexturePort(p);

            return;
        }


        if (this._mode == MODE_CORNER)
            ele.byId("bgpreviewButtonsContainer").classList.remove("hidden");

        CABLES.UI.hideToolTip();

        if (!this._listeningFrame && p)
        {
            this._listeningFrame = true;
            p.op.patch.cgl.on("beginFrame", () =>
            {
                this.render();
            });
        }

        this._lastClickedP = p;
        this._lastClicked = this.updateTexturePort(p);

        const tp = this.updateTexturePort(p);

        if (!tp)
        {
            return;
        }

        for (let i = 0; i < this._texturePorts.length; i++)
        {
            const el = document.getElementById("preview" + this._texturePorts[i].id);
            if (el)
                if (this._texturePorts[i].port.op != p.op) el.classList.remove("activePreview");
                else el.classList.add("activePreview");
        }
    }

    updateTexturePort(port)
    {
        let doUpdateHtml = false;
        const p = port;
        let idx = -1;

        if (p && p.get() && p.get().tex && port.direction == CABLES.PORT_DIR_OUT)
        {
            const id = port.op.id + port.name;

            idx = -1;
            for (let i = 0; i < this._texturePorts.length; i++)
                if (this._texturePorts[i].id == id)
                    idx = i;

            if (idx == -1)
            {
                doUpdateHtml = true;
                this._texturePorts.push({
                    "id": id,
                    "opid": port.op.id,
                    "port": p,
                    "lastTimeClicked": -1,
                    "doShow": false,
                    "activity": 0
                });
                idx = this._texturePorts.length - 1;
            }

            this._texturePorts[idx].updated = CABLES.now();
            this._texturePorts[idx].activity++;

            // if (this._mode == CABLES.UI.TexturePreviewer.MODE_ACTIVE) this._texturePorts[idx].doShow = true;
        }

        return this._texturePorts[idx];
    }

    _showInfo(tex)
    {
        let str = "";
        if (tex)
        {
            str = tex.width + " x " + tex.height;
            if (tex.textureType === CGL.Texture.TYPE_FLOAT) str += " 32bit";
        }

        if (this._infoStr == str) return;

        this._infoStr = str;
        ele.byId("bgpreviewInfo").innerText = str;
    }

    gotoOp()
    {
        if (this._lastClickedP) gui.patchView.centerSelectOp(this._lastClickedP.op.id);
    }

    deserialize(o)
    {
        // if (!o) return;

        // this.enableBgPreview(o.enabled);
        // const op = gui.corePatch().getOpById(o.op);

        // if (!op)
        // {
        //     console.log("texpreviewer cant find op");
        //     return;
        // }

        // const p = op.getPort(o.port);

        // this.selectTexturePort(p);
        // this._lastClicked = this.updateTexturePort(p);

        // // this.pin(o.pinned);
        // this.enableBgPreview(o.enabled);
    }

    serialize()
    {
        const o = {};

        // // o.pinned = gui.metaTexturePreviewer.pinned;

        // if (this._lastClickedP || this._lastClicked)
        // {
        //     const p = this._lastClicked || this._lastClickedP.port;

        //     if (p)
        //     {
        //         o.port = p.port.name;
        //         o.op = p.port.op.id;
        //         o.enabled = this._enabled;
        //     }

        //     // console.log(o);
        // }

        return o;
    }
}