You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
310 lines
8.0 KiB
JavaScript
310 lines
8.0 KiB
JavaScript
class Cable {
|
|
start = "";
|
|
end = "";
|
|
strPath = "";
|
|
color = "";
|
|
strokeWidth = 10;
|
|
bufferSize = 20;
|
|
buffer = [];
|
|
path = null;
|
|
|
|
constructor(start) {
|
|
this.start = start;
|
|
this.randomColor();
|
|
}
|
|
|
|
randomColor() {
|
|
const hue = Math.floor(Math.random() * 360);
|
|
const saturation = Math.floor(50 + Math.random() * (50 + 1)) + "%";
|
|
const lightness = "75%";
|
|
this.color = "hsl(" + hue + ", " + saturation + ", " + lightness + ")";
|
|
}
|
|
|
|
createCable(position) {
|
|
this.path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
|
|
this.path.setAttribute("fill", "none");
|
|
|
|
this.path.setAttribute("stroke", this.color);
|
|
this.path.setAttribute("stroke-width", this.strokeWidth);
|
|
this.path.setAttribute("stroke-linecap", "round");
|
|
|
|
this.buffer = [];
|
|
this.appendToBuffer(position);
|
|
this.strPath = "M" + position.x + " " + position.y;
|
|
this.path.setAttribute("d", this.strPath);
|
|
}
|
|
|
|
appendToBuffer(position) {
|
|
this.buffer.push(position);
|
|
while (this.buffer.length > this.bufferSize) {
|
|
this.buffer.shift();
|
|
}
|
|
}
|
|
|
|
getAveragePoint(offset) {
|
|
// Calculate the average point, starting at offset in the buffer
|
|
let len = this.buffer.length;
|
|
if (len % 2 === 1 || len >= this.bufferSize) {
|
|
let totalX = 0;
|
|
let totalY = 0;
|
|
let position, i;
|
|
let count = 0;
|
|
for (i = offset; i < len; i++) {
|
|
count++;
|
|
position = this.buffer[i];
|
|
totalX += position.x;
|
|
totalY += position.y;
|
|
}
|
|
return {
|
|
x: totalX / count,
|
|
y: totalY / count,
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
updatePath() {
|
|
let position = this.getAveragePoint(0);
|
|
if (position) {
|
|
// Get the smoothed part of the path that will not change
|
|
this.strPath += " L" + position.x + " " + position.y;
|
|
|
|
// Get the last part of the path (close to the current mouse position)
|
|
// This part will change if the mouse moves again
|
|
var tmpPath = "";
|
|
for (var offset = 2; offset < this.buffer.length; offset += 2) {
|
|
position = this.getAveragePoint(offset);
|
|
tmpPath += " L" + position.x + " " + position.y;
|
|
}
|
|
this.path.setAttribute("d", this.strPath + tmpPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
class Panel {
|
|
width = 0;
|
|
height = 0;
|
|
|
|
svg = null;
|
|
container = null;
|
|
containerBoundingClient = null;
|
|
|
|
model = {};
|
|
params = [];
|
|
sockets = [];
|
|
preset = {};
|
|
|
|
cable = null;
|
|
cables = null;
|
|
|
|
constructor(svg, container, preset = {}, debug = false) {
|
|
this.svg = this.htmlToElement(svg);
|
|
this.container = container;
|
|
this.container.style.position = "relative";
|
|
this.containerRect = container.getBoundingClientRect();
|
|
this.preset = { ...preset };
|
|
this.debug = debug;
|
|
|
|
while (container.firstChild) {
|
|
container.removeChild(container.lastChild);
|
|
}
|
|
container.appendChild(this.svg);
|
|
|
|
this.setSize();
|
|
this.createParams();
|
|
|
|
this.createSockets();
|
|
this.createCables();
|
|
|
|
this.svg.addEventListener("mousedown", (e) => this.startCable(e));
|
|
this.svg.addEventListener("mousemove", (e) => this.drawCable(e));
|
|
this.svg.addEventListener("mouseup", (e) => this.endCable(e));
|
|
// this.svg.addEventListener("mouseleave", (e) => this.endCable(e));
|
|
}
|
|
|
|
htmlToElement(string) {
|
|
var template = document.createElement("template");
|
|
string = string.trim(); // Never return a text node of whitespace as the result
|
|
template.innerHTML = string;
|
|
return template.content.firstChild;
|
|
}
|
|
|
|
setSize() {
|
|
this.width = this.svg.getAttribute("width");
|
|
this.height = this.svg.getAttribute("height");
|
|
this.container.style.width = this.width + "px";
|
|
this.container.style.height = this.height + "px";
|
|
}
|
|
|
|
createParams() {
|
|
// this.svg.querySelector("#params").style.visibility = "hidden";
|
|
this.params = this.svg.querySelectorAll("#params [fill='#FF0000']");
|
|
this.model.params = [];
|
|
|
|
for (const param of this.params) {
|
|
let rect = param.getBoundingClientRect();
|
|
|
|
let control = document.createElement("input");
|
|
control.setAttribute("type", "range");
|
|
control.setAttribute("data-width", rect.width);
|
|
control.setAttribute("data-height", rect.height);
|
|
|
|
control.setAttribute("data-fgcolor", "white");
|
|
control.classList.add("input-knob");
|
|
control.style.position = "absolute";
|
|
control.style.left = rect.left - this.containerRect.left + "px";
|
|
control.style.top = rect.top - this.containerRect.top + "px";
|
|
|
|
control.setAttribute("name", param.id);
|
|
|
|
if (this.preset.hasOwnProperty(param.id)) {
|
|
control.value = this.preset[param.id];
|
|
}
|
|
|
|
this.container.appendChild(control);
|
|
|
|
this.model.params.push(param.id);
|
|
|
|
if (this.debug) {
|
|
let label = document.createElement("label");
|
|
label.setAttribute("for", param.id);
|
|
label.innerHTML = param.id;
|
|
|
|
label.style.position = "absolute";
|
|
label.style.left = rect.left - this.containerRect.left + "px";
|
|
label.style.top = rect.top + rect.height + "px";
|
|
label.style.fontSize = "1rem";
|
|
label.style.backgroundColor = "red";
|
|
label.style.color = "white";
|
|
|
|
this.container.appendChild(label);
|
|
}
|
|
}
|
|
let group = this.svg.querySelector("#params");
|
|
if (group) group.style.display = "none";
|
|
}
|
|
|
|
createSockets() {
|
|
// this.svg.querySelector("#sockets").style.visibility = "hidden";
|
|
this.sockets = this.svg.querySelectorAll("#sockets [fill='#00FF00']");
|
|
this.model.sockets = [];
|
|
for (const socket of this.sockets) {
|
|
let rect = socket.getBoundingClientRect();
|
|
|
|
let input = document.createElement("input");
|
|
input.setAttribute("name", socket.id);
|
|
input.classList.add("socket");
|
|
|
|
input.style.position = "absolute";
|
|
input.style.left = rect.left - this.containerRect.left + "px";
|
|
input.style.top = rect.top - this.containerRect.top + "px";
|
|
input.style.width = rect.width + "px";
|
|
input.style.height = rect.height + "px";
|
|
|
|
input.style.border = "none";
|
|
input.style.background = "none";
|
|
input.style.opacity = 0;
|
|
|
|
if (this.debug) {
|
|
input.style.border = "1px solid #00FF00";
|
|
input.style.opacity = 1;
|
|
}
|
|
|
|
input.style.cursor = "alias";
|
|
|
|
input.addEventListener("mouseup", (e) => this.endCable(e));
|
|
|
|
input.addEventListener("mousedown", (e) => {
|
|
e.preventDefault();
|
|
this.startCable(e);
|
|
return false;
|
|
});
|
|
|
|
this.container.appendChild(input);
|
|
|
|
this.model.sockets.push(socket.id);
|
|
}
|
|
|
|
let group = this.svg.querySelector("#sockets");
|
|
if (group) group.style.display = "none";
|
|
}
|
|
|
|
createCables() {
|
|
let cables = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
|
|
cables.classList.add("cables");
|
|
cables.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
cables.setAttribute("width", this.width);
|
|
cables.setAttribute("height", this.height);
|
|
cables.setAttribute("viewBox", `0 0 ${this.width} ${this.height}`);
|
|
cables.setAttribute("fill", "none");
|
|
cables.style.pointerEvents = "none";
|
|
cables.style.position = "absolute";
|
|
cables.style.left = 0;
|
|
cables.style.top = 0;
|
|
cables.style.width = this.width;
|
|
cables.style.height = this.height;
|
|
|
|
this.cables = cables;
|
|
this.container.appendChild(this.cables);
|
|
}
|
|
|
|
startCable(event) {
|
|
let position = this.getMousePosition(event);
|
|
let socket = event.target;
|
|
|
|
if (socket && socket.classList.contains("socket")) {
|
|
this.cable = new Cable(socket.getAttribute("name"));
|
|
this.cable.createCable(position);
|
|
let cable = this.cable.path;
|
|
cable.style.pointerEvents = "stroke";
|
|
|
|
cable.addEventListener("click", (e) => {
|
|
console.log(e);
|
|
cable.remove();
|
|
});
|
|
|
|
this.cables.appendChild(this.cable.path);
|
|
|
|
for (const input of this.container.querySelectorAll(".input-knob")) {
|
|
input.style.pointerEvents = "none";
|
|
}
|
|
}
|
|
}
|
|
|
|
drawCable(event) {
|
|
if (this.cable) {
|
|
this.cable.appendToBuffer(this.getMousePosition(event));
|
|
this.cable.updatePath();
|
|
}
|
|
}
|
|
|
|
endCable(event) {
|
|
if (this.cable) {
|
|
let socket = event.target;
|
|
|
|
if (socket && socket.classList.contains("socket")) {
|
|
this.cable.end = socket.getAttribute("name");
|
|
|
|
// TODO: log cable
|
|
} else {
|
|
this.cable.path.remove();
|
|
}
|
|
|
|
for (const input of this.container.querySelectorAll(".input-knob")) {
|
|
input.style.pointerEvents = "all";
|
|
}
|
|
|
|
this.cable = null;
|
|
}
|
|
}
|
|
|
|
getMousePosition(e) {
|
|
return {
|
|
x: e.pageX - this.containerRect.left,
|
|
y: e.pageY - this.containerRect.top,
|
|
};
|
|
}
|
|
}
|