@quick-game/cli
Version:
Command line interface for rapid qg development
207 lines • 8.53 kB
JavaScript
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as UI from '../../../ui/legacy/legacy.js';
import { BottomPaddingWithoutParam, BottomPaddingWithParam, LeftMarginOfText, LeftSideTopPadding, NodeLabelFontStyle, ParamLabelFontStyle, RightMarginOfText, TotalInputPortHeight, TotalOutputPortHeight, TotalParamPortHeight, } from './GraphStyle.js';
import { calculateInputPortXY, calculateOutputPortXY, calculateParamPortXY } from './NodeRendererUtility.js';
// A class that represents a node of a graph, consisting of the information needed to layout the
// node and display the node. Each node has zero or more ports, including input, output, and param ports.
export class NodeView {
id;
type;
numberOfInputs;
numberOfOutputs;
label;
size;
position;
layout;
ports;
constructor(data, label) {
this.id = data.nodeId;
this.type = data.nodeType;
this.numberOfInputs = data.numberOfInputs;
this.numberOfOutputs = data.numberOfOutputs;
this.label = label;
this.size = { width: 0, height: 0 };
// Position of the center. If null, it means the graph layout has not been computed
// and this node should not be rendered. It will be set after layouting.
this.position = null;
this.layout = {
inputPortSectionHeight: 0,
outputPortSectionHeight: 0,
maxTextLength: 0,
totalHeight: 0,
};
this.ports = new Map();
this.initialize(data);
}
initialize(data) {
this.updateNodeLayoutAfterAddingNode(data);
this.setupInputPorts();
this.setupOutputPorts();
}
/**
* Add an AudioParam to this node.
* Note for @method removeParamPort: removeParamPort is not necessary because it will only happen
* when the parent NodeView is destroyed. So there is no need to remove port individually
* when the whole NodeView will be gone.
*/
addParamPort(paramId, paramType) {
const paramPorts = this.getPortsByType("Param" /* PortTypes.Param */);
const numberOfParams = paramPorts.length;
const { x, y } = calculateParamPortXY(numberOfParams, this.layout.inputPortSectionHeight);
this.addPort({
id: generateParamPortId(this.id, paramId),
type: "Param" /* PortTypes.Param */,
label: paramType,
x,
y,
});
this.updateNodeLayoutAfterAddingParam(numberOfParams + 1, paramType);
// The position of output ports may be changed if adding a param increases the total height.
this.setupOutputPorts();
}
getPortsByType(type) {
const result = [];
this.ports.forEach(port => {
if (port.type === type) {
result.push(port);
}
});
return result;
}
/**
* Use number of inputs and outputs to compute the layout
* for text and ports.
* Credit: This function is mostly borrowed from Audion/
* `audion.entryPoints.handleNodeCreated_()`.
* https://github.com/google/audion/blob/master/js/entry-points/panel.js
*/
updateNodeLayoutAfterAddingNode(data) {
// Even if there are no input ports, leave room for the node label.
const inputPortSectionHeight = TotalInputPortHeight * Math.max(1, data.numberOfInputs) + LeftSideTopPadding;
this.layout.inputPortSectionHeight = inputPortSectionHeight;
this.layout.outputPortSectionHeight = TotalOutputPortHeight * data.numberOfOutputs;
// Use the max of the left and right side heights as the total height.
// Include a little padding on the left.
this.layout.totalHeight =
Math.max(inputPortSectionHeight + BottomPaddingWithoutParam, this.layout.outputPortSectionHeight);
// Update max length with node label.
const nodeLabelLength = measureTextWidth(this.label, NodeLabelFontStyle);
this.layout.maxTextLength = Math.max(this.layout.maxTextLength, nodeLabelLength);
this.updateNodeSize();
}
/**
* After adding a param port, update the node layout based on the y value
* and label length.
*/
updateNodeLayoutAfterAddingParam(numberOfParams, paramType) {
// The height after adding param ports and input ports.
// Include a little padding on the left.
const leftSideMaxHeight = this.layout.inputPortSectionHeight + numberOfParams * TotalParamPortHeight + BottomPaddingWithParam;
// Use the max of the left and right side heights as the total height.
this.layout.totalHeight = Math.max(leftSideMaxHeight, this.layout.outputPortSectionHeight);
// Update max length with param label.
const paramLabelLength = measureTextWidth(paramType, ParamLabelFontStyle);
this.layout.maxTextLength = Math.max(this.layout.maxTextLength, paramLabelLength);
this.updateNodeSize();
}
updateNodeSize() {
this.size = {
width: Math.ceil(LeftMarginOfText + this.layout.maxTextLength + RightMarginOfText),
height: this.layout.totalHeight,
};
}
// Setup the properties of each input port.
setupInputPorts() {
for (let i = 0; i < this.numberOfInputs; i++) {
const { x, y } = calculateInputPortXY(i);
this.addPort({ id: generateInputPortId(this.id, i), type: "In" /* PortTypes.In */, x, y, label: undefined });
}
}
// Setup the properties of each output port.
setupOutputPorts() {
for (let i = 0; i < this.numberOfOutputs; i++) {
const portId = generateOutputPortId(this.id, i);
const { x, y } = calculateOutputPortXY(i, this.size, this.numberOfOutputs);
if (this.ports.has(portId)) {
// Update y value of an existing output port.
const port = this.ports.get(portId);
if (!port) {
throw new Error(`Unable to find port with id ${portId}`);
}
port.x = x;
port.y = y;
}
else {
this.addPort({ id: portId, type: "Out" /* PortTypes.Out */, x, y, label: undefined });
}
}
}
addPort(port) {
this.ports.set(port.id, port);
}
}
/**
* Generates the port id for the input of node.
*/
export const generateInputPortId = (nodeId, inputIndex) => {
return `${nodeId}-input-${inputIndex || 0}`;
};
/**
* Generates the port id for the output of node.
*/
export const generateOutputPortId = (nodeId, outputIndex) => {
return `${nodeId}-output-${outputIndex || 0}`;
};
/**
* Generates the port id for the param of node.
*/
export const generateParamPortId = (nodeId, paramId) => {
return `${nodeId}-param-${paramId}`;
};
// A label generator to convert UUID of node to shorter label to display.
// Each graph should have its own generator since the node count starts from 0.
export class NodeLabelGenerator {
totalNumberOfNodes;
constructor() {
this.totalNumberOfNodes = 0;
}
/**
* Generates the label for a node of a graph.
*/
generateLabel(nodeType) {
// To make the label concise, remove the suffix "Node" from the nodeType.
if (nodeType.endsWith('Node')) {
nodeType = nodeType.slice(0, nodeType.length - 4);
}
// Also, use an integer to replace the long UUID.
this.totalNumberOfNodes += 1;
const label = `${nodeType} ${this.totalNumberOfNodes}`;
return label;
}
}
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
// eslint-disable-next-line @typescript-eslint/naming-convention
let _contextForFontTextMeasuring;
/**
* Get the text width using given font style.
*/
export const measureTextWidth = (text, fontStyle) => {
if (!_contextForFontTextMeasuring) {
const context = document.createElement('canvas').getContext('2d');
if (!context) {
throw new Error('Unable to create canvas context.');
}
_contextForFontTextMeasuring = context;
}
const context = _contextForFontTextMeasuring;
context.save();
if (fontStyle) {
context.font = fontStyle;
}
const width = UI.UIUtils.measureTextWidth(context, text);
context.restore();
return width;
};
//# sourceMappingURL=NodeView.js.map