cables_dev/cables_ui/src/ui/glpatch/vizlayer.js
import { Logger, Events } from "cables-shared-client";
import userSettings from "../components/usersettings.js";
import gluiconfig from "./gluiconfig.js";
/**
* managing data vizualizations on the patchfield (e.g. viztexture/vizgraph/vizString ops)
*
* @export
* @class VizLayer
* @extends {Events}
*/
export default class VizLayer extends Events
{
constructor(glPatch)
{
super();
this._log = new Logger("VizLayer");
this._usingGl = false;
this._items = [];
this._itemsLookup = {};
this._glPatch = glPatch;
this.paused = userSettings.get("vizlayerpaused") || false;
gui.on("uiloaded", () =>
{
this._updateSize();
});
userSettings.on("change", (key, value) =>
{
if (key == "vizlayerpaused") this.paused = value;
});
this._glPatch.on("resize", this._updateSize.bind(this));
this._eleCanvas = document.createElement("canvas");
this._eleCanvas.id = "gluiPreviewLayer";
this._eleCanvas.classList.add("gluiPreviewLayer");
// this._eleCanvas.style.zIndex = this._glPatch._cgl.canvas.style.zIndex + 2;
document.body.appendChild(this._eleCanvas);
this._updateSize();
gui.corePatch().cgl.on("beginFrame", () =>
{
this._fallBackrendererDisabled = true;
this._usingGl = true;
this.renderVizLayer(true);
});
// gui.corePatch().on("reqAnimFrame", () =>
// {
// if (!this._usingGl)
// {
// console.log(2);
// this.renderVizLayer();
// }
// });
gui.corePatch().on("onOpAdd", (a) =>
{
if (a.renderVizLayer || a.renderVizLayerGl)
{
let item = this._itemsLookup[a.id];
if (item)console.log("vizlayer id already exists...");
if (!item)
{
item = {
"op": a,
"port": a.portsIn[0],
"ports": a.portsIn
};
this._itemsLookup[a.id] = item;
item.op.on("delete", (op) =>
{
this._removeOpItem(op);
});
this._items.push(item);
}
}
});
}
_updateSize()
{
if (this._eleCanvas.width != this._glPatch._cgl.canvasWidth ||
this._eleCanvas.height != this._glPatch._cgl.canvasHeight)
{
this._eleCanvas.style.width = this._glPatch._cgl.canvas.width / window.devicePixelRatio + "px";
this._eleCanvas.style.height = this._glPatch._cgl.canvas.height / window.devicePixelRatio + "px";
this._eleCanvas.width = this._glPatch._cgl.canvasWidth;
this._eleCanvas.height = this._glPatch._cgl.canvasHeight;
this._canvasCtx = this._eleCanvas.getContext("2d");
}
}
render()
{
}
renderVizLayer(gl)
{
if (!gl && this._fallBackrendererDisabled)
{
if (performance.now() - this.lastGlRendering > 500) this._fallBackrendererDisabled = false;
return;
}
if (gl && !gui.corePatch().cgl.hasFrameStarted() && this._usingGl)
{
this._usingGl = false;
return;
}
if (gl) this.lastGlRendering = performance.now();
this._canvasCtx.clearRect(0, 0, this._eleCanvas.width, this._eleCanvas.height);
this._canvasCtx.fillStyle = gui.theme.colors_vizlayer.colorBackground || "#222";
const perf = CABLES.UI.uiProfiler.start("glVizPreviewLayer.renderVizLayer");
const paddingY = this._glPatch.viewBox.patchToScreenConv(0, 25)[1];
this._updateSize();
const w = this._eleCanvas.width;
let count = 0;
for (let i = 0; i < this._items.length; i++)
{
const item = this._items[i];
const port = item.port;
if (!port || !item.op || !item.op.uiAttribs || !item.op.uiAttribs.translate) continue;
item.posX = item.op.uiAttribs.translate.x;
item.posY = item.op.uiAttribs.translate.y;
const pos = this._glPatch.viewBox.patchToScreenCoords(item.posX, item.posY);
pos[1] += paddingY;
pos[0] *= window.devicePixelRatio;
pos[1] *= window.devicePixelRatio;
const glop = this._glPatch.getGlOp(item.op);
if (!glop || glop.opUiAttribs.subPatch != this._glPatch.subPatch) continue;
let ww = glop.w;
if (glop.opUiAttribs.resizable)ww += gluiconfig.rectResizeSize;
const sizeOp = this._glPatch.viewBox.patchToScreenConv(ww, glop.h);
const size = [sizeOp[0], sizeOp[1] - paddingY - (paddingY / 2)];
sizeOp[0] *= window.devicePixelRatio;
sizeOp[1] *= window.devicePixelRatio;
size[0] *= window.devicePixelRatio;
size[1] *= window.devicePixelRatio;
if (pos[0] < -sizeOp[0] || pos[1] < -sizeOp[1] || pos[0] > this._eleCanvas.width || pos[1] > this._eleCanvas.height) continue;
this._canvasCtx.save();
// this._canvasCtx.clearRect(pos[0] - 1, pos[1] - 1, size[0] + 2, size[1] + 2);
// this._canvasCtx.strokeStyle = "transparent";
let region = new Path2D();
region.rect(pos[0], pos[1], size[0], size[1]);
this._canvasCtx.clip(region);
const scale = 1000 / gui.patchView._patchRenderer.viewBox.zoom * 1.5;
if (count > 10 || this.paused || Math.max(sizeOp[1], sizeOp[0]) < 20)
{
this._canvasCtx.save();
this._canvasCtx.scale(scale, scale);
this._canvasCtx.font = "normal 6px sourceCodePro";
this._canvasCtx.fillStyle = gui.theme.colors_vizlayer.colorText || "#FFF";
this._canvasCtx.textAlign = "center";
this._canvasCtx.fillText("paused", (pos[0] + size[0] / 2) / scale, (pos[1] + (size[1] / 2)) / scale);
this._canvasCtx.restore();
}
else
{
const layer =
{
"x": pos[0],
"y": pos[1],
"width": size[0],
"height": size[1],
"scale": w / gui.patchView._patchRenderer.viewBox.zoom * 0.6,
"useGl": this._usingGl,
"vizLayer": this
};
if (!item.op.uiAttribs.vizLayerMaxZoom || this._glPatch.viewBox.zoom < item.op.uiAttribs.vizLayerMaxZoom)
if (pos[0] === pos[0] && size[0] === size[0])
{
if (gl && item.op.renderVizLayerGl) item.op.renderVizLayerGl(this._canvasCtx, layer, this);
if (item.op.renderVizLayer)item.op.renderVizLayer(this._canvasCtx, layer, this);
}
}
item.oldPos = [pos[0], pos[1], size[0], size[1]];
this._canvasCtx.restore();
count++;
}
if (gui.texturePreview().needsVizLayer())
{
gui.texturePreview().drawVizLayer(this._canvasCtx);
}
this._glPatch.debugData.numVizLayers = count;
perf.finish();
}
_removeOpItem(op)
{
if (!op)
{
console.log("unknown vizlayer to remove");
return;
}
const it = this._itemsLookup[op.id];
let idx = this._items.indexOf(it);
if (idx > -1) this._items.splice(idx, 1);
else this._log.warn("could not find item");
delete this._itemsLookup[op.id];
if (this._items.length == 0) this._canvasCtx.clearRect(0, 0, this._eleCanvas.width, this._eleCanvas.height);
}
pauseInteraction()
{
this._eleCanvas.style["pointer-events"] = "none";
}
resumeInteraction()
{
this._eleCanvas.style["pointer-events"] = "none";
}
clear(ctx, layer)
{
ctx.fillStyle = gui.theme.colors_vizlayer.colorBackground || "#222" || "#222";
ctx.fillRect(layer.x, layer.y, layer.width, layer.height);
}
_textWrap(str, max)
{
const space = " ";
const lines = str.split("\n");
const newLines = [];
const newLinesIdx = [];
for (let i = 0; i < lines.length; i++)
{
const words = lines[i].split(" ");
let charCount = 0;
let lineWords = [];
for (let j = 0; j < words.length; j++)
{
charCount += words[j].length + 1;
if (charCount >= max - 2)
{
newLines.push(lineWords.join(space));
newLinesIdx.push(i);
charCount = words[j].length + 1;
lineWords = [];
}
lineWords.push(words[j]);
}
newLines.push(lineWords.join(space));
newLinesIdx.push(i);
}
return {
"linesIdx": newLinesIdx,
"lines": newLines
};
}
renderText(ctx, layer, lines, options)
{
let indent = "";
let fs = Math.max(1, options.fontSize);
if (options.zoomText)fs *= (1.0 / layer.scale);
let padding = fs * 0.25;
const lineHeight = fs + padding;
let numLines = Math.floor(layer.height / layer.scale / lineHeight);
let offset = Math.floor(options.scroll * lines.length);
offset = Math.max(offset, 0);
offset = Math.min(offset, lines.length - numLines);
if (lines.length < numLines)offset = 0;
ctx.font = "normal " + fs + "px sourceCodePro";
ctx.fillStyle = gui.theme.colors_vizlayer.colorText || "#FFF";
if (options.showLineNum) for (let i = 0; i < (offset + numLines + " ").length; i++) indent += " ";
let numChars = Math.ceil(layer.width / layer.scale / 6) - indent.length;
ctx.fillStyle = gui.theme.colors_vizlayer.colorText || "#FFF";
let hl = options.syntax && options.syntax != "text";
let linesIdx = null;
let lb = "\n";
if (options.showWhitespace) lb = "⏎" + lb;
if (options.wrap)
{
const r = this._textWrap(lines.join(lb), numChars);
lines = r.lines;
linesIdx = r.linesIdx;
}
let lastline = "Z";
for (let i = offset; i < offset + numLines; i += 1)
{
if (i >= lines.length || i < 0) continue;
if (options.showWhitespace)
{
lines[i] = lines[i].replaceAll(" ", "·");
lines[i] = lines[i].replaceAll("\t", "⇿");
}
if (options.showLineNum)
{
let idx = i;
if (linesIdx)idx = linesIdx[i];
if (lastline != idx)
{
lastline = idx;
ctx.fillStyle = gui.theme.colors_vizlayer.colorLineNumbers || "#888";
ctx.fillText(idx,
layer.x / layer.scale + padding,
layer.y / layer.scale + lineHeight + ((i - offset) * lineHeight));
ctx.fillStyle = gui.theme.colors_vizlayer.colorText || "#FFF";
}
}
if (hl)
{
const data = hljs.highlight(lines[i], { "language": "glsl" });
let fake = "";
for (let j = 0; j < data._emitter.rootNode.children.length; j++)
{
const child = data._emitter.rootNode.children[j];
if (typeof child == "string")
{
ctx.fillStyle = gui.theme.colors_vizlayer.colorText || "#FFF";
ctx.fillText(indent + fake + child,
layer.x / layer.scale + padding,
layer.y / layer.scale + lineHeight + ((i - offset) * lineHeight));
for (let k = 0; k < child.length; k++) fake += " ";
}
else
{
if (child.scope && child.children)
{
if (child.scope == "built_in")ctx.fillStyle = "#418ce9"; // blue
else if (child.scope == "comment")ctx.fillStyle = "#0b0"; // green
else if (child.scope == "number")ctx.fillStyle = "#49d6b2"; // cyan
else if (child.scope == "literal")ctx.fillStyle = "#49d6b2"; // cyan
else if (child.scope == "meta" || child.scope == "keyword" || child.scope == "type")ctx.fillStyle = "#ecce64"; // yello
else
{
ctx.fillStyle = "#d00";
}
for (let l = 0; l < child.children.length; l++)
{
ctx.fillText(indent + fake + child.children[l],
layer.x / layer.scale + padding,
layer.y / layer.scale + lineHeight + ((i - offset) * lineHeight));
for (let k = 0; k < child.children[l].length; k++) fake += " ";
}
}
}
}
}
else
{
ctx.fillText(indent + lines[i],
layer.x / layer.scale + padding,
layer.y / layer.scale + lineHeight + ((i - offset) * lineHeight));
}
}
const gradHeight = 30;
if (offset > 0)
{
const radGrad = ctx.createLinearGradient(0, layer.y / layer.scale + 5, 0, layer.y / layer.scale + gradHeight);
radGrad.addColorStop(0, gui.theme.colors_vizlayer.colorBackground || "#222");
radGrad.addColorStop(1, "rgba(34,34,34,0.0)");
ctx.fillStyle = radGrad;
ctx.fillRect(layer.x / layer.scale, layer.y / layer.scale, layer.width, gradHeight);
}
if (offset + numLines < lines.length)
{
const radGrad = ctx.createLinearGradient(0, layer.y / layer.scale + layer.height / layer.scale - gradHeight + 5, 0, layer.y / layer.scale + layer.height / layer.scale - gradHeight + gradHeight);
radGrad.addColorStop(1, gui.theme.colors_vizlayer.colorBackground || "#222");
radGrad.addColorStop(0, "rgba(34,34,34,0.0)");
ctx.fillStyle = radGrad;
ctx.fillRect(layer.x / layer.scale, layer.y / layer.scale + layer.height / layer.scale - gradHeight, layer.width, gradHeight);
}
}
}