blob: 192a6687cd5a42a50d8b735440d06eb51c6782dd [file] [log] [blame]
// 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.
// 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 {
/**
* @param {!WebAudio.GraphVisualizer.NodeCreationData} data
* @param {string} label
*/
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;
/** @type {!WebAudio.GraphVisualizer.NodeLayout} */
this._layout = {
inputPortSectionHeight: 0,
outputPortSectionHeight: 0,
maxTextLength: 0,
totalHeight: 0,
};
/** @type {!Map<string, !WebAudio.GraphVisualizer.Port>} */
this.ports = new Map();
this._initialize(data);
}
/**
* @param {!WebAudio.GraphVisualizer.NodeCreationData} 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.
* @param {string} paramId
* @param {string} paramType
*/
addParamPort(paramId, paramType) {
const paramPorts = this.getPortsByType(WebAudio.GraphVisualizer.PortTypes.Param);
const numberOfParams = paramPorts.length;
const {x, y} = WebAudio.GraphVisualizer.NodeRendererUtility.calculateParamPortXY(
numberOfParams, this._layout.inputPortSectionHeight);
this._addPort({
id: generateParamPortId(this.id, paramId),
type: WebAudio.GraphVisualizer.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();
}
/**
* @param {!WebAudio.GraphVisualizer.PortTypes} type
* @return {!Array<!WebAudio.GraphVisualizer.Port>}
*/
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
* @param {!WebAudio.GraphVisualizer.NodeCreationData} data
*/
_updateNodeLayoutAfterAddingNode(data) {
// Even if there are no input ports, leave room for the node label.
const {
TotalInputPortHeight,
LeftSideTopPadding,
BottomPaddingWithoutParam,
TotalOutputPortHeight,
NodeLabelFontStyle,
} = WebAudio.GraphVisualizer.GraphStyles;
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.
* @param {number} numberOfParams
* @param {string} paramType
*/
_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 * WebAudio.GraphVisualizer.GraphStyles.TotalParamPortHeight +
WebAudio.GraphVisualizer.GraphStyles.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, WebAudio.GraphVisualizer.GraphStyles.ParamLabelFontStyle);
this._layout.maxTextLength = Math.max(this._layout.maxTextLength, paramLabelLength);
this._updateNodeSize();
}
_updateNodeSize() {
this.size = {
width: Math.ceil(
WebAudio.GraphVisualizer.GraphStyles.LeftMarginOfText + this._layout.maxTextLength +
WebAudio.GraphVisualizer.GraphStyles.RightMarginOfText),
height: this._layout.totalHeight,
};
}
// Setup the properties of each input port.
_setupInputPorts() {
for (let i = 0; i < this.numberOfInputs; i++) {
const {x, y} = WebAudio.GraphVisualizer.NodeRendererUtility.calculateInputPortXY(i);
this._addPort({
id: generateInputPortId(this.id, i),
type: WebAudio.GraphVisualizer.PortTypes.In,
x,
y,
});
}
}
// 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} =
WebAudio.GraphVisualizer.NodeRendererUtility.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);
port.x = x;
port.y = y;
} else {
this._addPort({
id: portId,
type: WebAudio.GraphVisualizer.PortTypes.Out,
x,
y,
});
}
}
}
/** @param {!WebAudio.GraphVisualizer.Port} port */
_addPort(port) {
this.ports.set(port.id, port);
}
}
/**
* Generates the port id for the input of node.
* @param {string} nodeId
* @param {number | undefined} inputIndex
* @return {string}
*/
export const generateInputPortId = (nodeId, inputIndex) => {
return `${nodeId}-input-${inputIndex || 0}`;
};
/**
* Generates the port id for the output of node.
* @param {string} nodeId
* @param {number | undefined} outputIndex
* @return {string}
*/
export const generateOutputPortId = (nodeId, outputIndex) => {
return `${nodeId}-output-${outputIndex || 0}`;
};
/**
* Generates the port id for the param of node.
* @param {string} nodeId
* @param {string} paramId
* @return {string}
*/
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 {
constructor() {
this._totalNumberOfNodes = 0;
}
/**
* Generates the label for a node of a graph.
* @param {string} nodeType
* @return {string}
*/
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;
}
}
/**
* Get the text width using given font style.
* @param {string} text
* @param {?string} fontStyle
* @return {number}
*/
export const measureTextWidth = (text, fontStyle) => {
if (!WebAudio.GraphVisualizer._contextForFontTextMeasuring) {
WebAudio.GraphVisualizer._contextForFontTextMeasuring = createElement('canvas').getContext('2d');
}
const context = WebAudio.GraphVisualizer._contextForFontTextMeasuring;
context.save();
context.font = fontStyle;
const width = UI.measureTextWidth(context, text);
context.restore();
return width;
};
/* Legacy exported object */
self.WebAudio = self.WebAudio || {};
/* Legacy exported object */
WebAudio = WebAudio || {};
/* Legacy exported object */
WebAudio.GraphVisualizer = WebAudio.GraphVisualizer || {};
/**
* @constructor
*/
WebAudio.GraphVisualizer.NodeView = NodeView;
/**
* @constructor
*/
WebAudio.GraphVisualizer.NodeLabelGenerator = NodeLabelGenerator;
WebAudio.GraphVisualizer.generateInputPortId = generateInputPortId;
WebAudio.GraphVisualizer.generateOutputPortId = generateOutputPortId;
WebAudio.GraphVisualizer.generateParamPortId = generateParamPortId;
WebAudio.GraphVisualizer.measureTextWidth = measureTextWidth;