Skip to content
Permalink
 
 
Cannot retrieve contributors at this time
1726 lines (1557 sloc) 64.8 KB
if (typeof define !== 'function') { var define = require(VVVVContext.Root+'/node_modules/amdefine')(module, VVVVContext.getRelativeRequire(require)) }
define(function(require,exports) {
var $ = require('jquery');
require('d3');
var VVVV = require('core/vvvv.core.defines');
var Makros = require('vvvv.makros');
var UIState = {
'Idle': 0,
'Connecting': 1,
'Moving': 2,
'Creating': 3,
'Changing': 4,
'AreaSelecting': 5,
'PinDragging': 6,
'Resizing': 7
}
function getAllUpstreamNodes(origin_node, node) {
if (node==undefined)
node = origin_node;
var upnodes = [];
var u = node.getUpstreamNodes();
for (var j=0; j<u.length; j++) {
if (u[j]!=origin_node && !u[j].delays_output) {
upnodes.push(u[j]);
upnodes = upnodes.concat(getAllUpstreamNodes(origin_node, u[j]));
}
}
return upnodes;
}
function getAllDownstreamNodes(origin_node, node) {
if (node==undefined)
node = origin_node;
var upnodes = [];
var u = node.getDownstreamNodes();
for (var j=0; j<u.length; j++) {
if (u[j]!=origin_node && !u[j].delays_output) {
upnodes.push(u[j]);
upnodes = upnodes.concat(getAllDownstreamNodes(origin_node, u[j]));
}
}
return upnodes;
}
var defaultOpacity = 0.85;
var blurredOpacity = 0.35;
VVVV.PinTypes.Value.makeLabel = VVVV.PinTypes.String.makeLabel = function(element, node) {
var rowCount = node.IOBoxRows();
var sliceCount = node.IOBoxInputPin().getSliceCount();
/*d3.select(element)
.append('svg:clipPath')
.attr('id', 'clip-path-'+node.id)
.append("svg:rect")
.attr('width', node.getWidth())
.attr('height', node.getHeight())*/
d3.select(element).selectAll('.vvvv-node-label').remove();
for (var i=0; i<rowCount; i++) {
d3.select(element)
.append('svg:text')
.text(node.IOBoxInputPin().getValue(i))
.attr('class', 'vvvv-node-label')
.attr('shape-rendering', 'crispEdges')
.attr('dy', function(d) {
return i*12+12;
})
.attr('dx', 4)
.attr('font-size', 10)
.attr('font-family', "'Lucida Sans Unicode', sans-serif")
//.attr('clip-path', 'url(#clip-path-'+node.id+')')
}
}
VVVV.PinTypes.Value.openInputBox = VVVV.PinTypes.String.openInputBox = function(win, $element, pin, sliceIdx) {
$inputbox = $("<input type='text' value='"+pin.getValue(sliceIdx).toString().replace(/\|/g, "||").replace(/'/g, '&apos;').replace(/</g, '&lt;').replace(/>/g, '&gt;')+"' class='pininputbox value resettable'/>");
$inputbox.css('position', $element.css('position'));
$inputbox.css('width', $element.css('width'));
$inputbox.css('height', $element.css('height'));
$inputbox.css('left', $element.css('left'));
$inputbox.css('top', $element.css('top'));
$element.replaceWith($inputbox);
$inputbox.get(0).select();
win.window.document.exitPointerLock = win.window.document.exitPointerLock ||
win.window.document.mozExitPointerLock ||
win.window.document.webkitExitPointerLock;
$inputbox.change(function() {
if (pin.typeName=="Value")
pin.setValue(sliceIdx, parseFloat($(this).val()));
else
pin.setValue(sliceIdx, $(this).val());
var cmd = {syncmode: 'diff', nodes: {}, links: []};
cmd.nodes[pin.node.id] = {pins: {}};
cmd.nodes[pin.node.id].pins[pin.pinname] = {values: pin.values};
pin.node.parentPatch.editor.update(pin.node.parentPatch, cmd);
//pin.node.parentPatch.afterUpdate();
});
$inputbox.keydown(function(e) {
if (e.which==13) {
$(this).change();
$(this).remove();
}
e.stopPropagation();
});
if (pin.typeName=='Value') {
function scroll(el, delta, e) {
var mod = $(el).val()%1;
if (!isNaN(mod)) {
var offset;
offset = 1;
if (e.altKey || mod!==0)
offset = 1.0/100.0;
delta *= offset;
$(el).val(parseFloat($(el).val())+delta);
$(el).change();
}
}
$inputbox.bind('mousewheel', function(e) {
var delta = e.originalEvent.wheelDelta/120;
scroll(this, delta, e);
e.preventDefault();
return false;
});
$inputbox.bind('DOMMouseScroll', function(e) {
var delta = -e.originalEvent.detail/3;
scroll(this, delta, e);
e.preventDefault();
return false;
})
win.window.document.addEventListener('pointerlockerror', function(e) {
console.log('POINTER LOCK ERROR');
/*alert("It seems Pointer Lock is not allowed yet. Click somewhere in the browser window (not the patch window) to request Pointer Lock");
var addPointerLockRequest = function() {
this.requestPointerLock();
}
$('body').bind('click', addPointerLockRequest);
$(document).on('pointerlockchange', function() {
if (document.pointerLockElement) {
alert('ok, you should be good now.');
}
$('body').unbind("click", addPointerLockRequest);
})*/
})
$inputbox.mousedown(function(e) {
if (e.which==3) {
this.requestPointerLock();
}
})
$inputbox.mouseup(function(e) {
if (e.which==3) {
win.window.document.exitPointerLock();
window.setTimeout(function() { $inputbox.remove(); }, 100); // has to be delayed, because Chrome crashes otherwise ..
win.state = UIState.Idle;
}
})
$inputbox.mousemove(function(e) {
if (win.window.document.pointerLockElement) {
win.state = UIState.PinDragging;
if (Math.abs(e.originalEvent.movementY)<50) // for some reason, movementY is some large number at the beginning
scroll(this, Math.floor(e.originalEvent.movementY*-0.5), e);
}
})
}
$inputbox.bind('paste', function(e) {
e.stopPropagation();
})
$inputbox.bind('copy', function(e) {
e.stopPropagation();
})
}
VVVV.PinTypes.Enum.openInputBox = function(win, $element, pin, sliceIdx) {
$inputbox = $("<select class='pininputbox value resettable'>");
for (var i=0; i<pin.enumOptions.length; i++) {
$opt = $('<option value="'+pin.enumOptions[i]+'">'+pin.enumOptions[i]+'</option>');
if (pin.getValue(sliceIdx)==pin.enumOptions[i])
$opt.attr('selected', true);
$inputbox.append($opt);
}
$inputbox.css('position', $element.css('position'));
$inputbox.css('width', $element.css('width'));
$inputbox.css('height', $element.css('height'));
$inputbox.css('left', $element.css('left'));
$inputbox.css('top', $element.css('top'));
$element.replaceWith($inputbox);
$inputbox.change(function() {
pin.setValue(sliceIdx, $(this).val());
var cmd = {syncmode: 'diff', nodes: {}, links: []};
cmd.nodes[pin.node.id] = {pins: {}};
cmd.nodes[pin.node.id].pins[pin.pinname] = {values: pin.values};
pin.node.parentPatch.editor.update(pin.node.parentPatch, cmd);
//pin.node.parentPatch.afterUpdate();
$(this).remove();
});
}
VVVV.PinTypes.Color.makeLabel = function(element, node) {
var rowCount = node.IOBoxRows();
var sliceCount = node.IOBoxInputPin().getSliceCount();
d3.select(element).selectAll('.vvvv-node-label').remove();
for (var i=0; i<rowCount; i++) {
d3.select(element)
.append('svg:rect')
.attr('class', 'vvvv-node-label')
.attr('height', node.getHeight()/rowCount-8)
.attr('width', node.getWidth())
.attr('y', i * 12 + 4)
.attr('fill', function(d) {
var col = node.IOBoxInputPin().getValue(i);
var svgcol = [0, 0, 0, 0];
if (col) {
for (var j=0; j<col.rgba.length; j++) {
svgcol[j] = parseInt(col.rgba[j]*256);
}
}
return 'rgba('+svgcol.join(',')+')';
})
}
}
VVVV.PinTypes.Color.openInputBox = function(win, $element, pin, sliceIdx) {
var modulatedComp = 0;
function setModulatedComp(e) {
if (e.altKey && e.shiftKey)
modulatedComp = 3;
else if (e.altKey)
modulatedComp = 1;
else if (e.shiftKey)
modulatedComp = 2;
else
modulatedComp = 0;
}
$(win.window.document)
.keydown(setModulatedComp)
.keyup(setModulatedComp)
$inputbox = $('<div class="pininputbox color resettable"></div>');
$inputbox.css('position', $element.css('position'));
$inputbox.css('width', "120px");
$inputbox.css('height', "66px");
$inputbox.css('left', $element.css('left'));
$inputbox.css('top', ($element.offset().top-60)+'px');
var col = pin.getValue(sliceIdx);
var svgcol = [];
for (var i=0; i<col.rgba.length; i++) {
svgcol[i] = parseInt(col.rgba[i]*256);
}
svgcol[3] = 255; // ignore alpha here ...
$inputbox.css('background-color', 'rgba('+svgcol.join(',')+')');
$element.replaceWith($inputbox);
var hsv = col.getHSV();
hsv[3] = col.rgba[3];
var labels = "HSVA";
for (var i=0; i<4; i++) {
$slider = $('<input type="range" name="hsv[]" min="0" max="1" step="0.001" value="'+hsv[i]+'"/>');
$inputbox.append('<span class="color-component">'+labels[i]+":</div>");
$inputbox.append($slider);
(function(j) {
$slider.on('input', function() {
hsv[j] = parseFloat($(this).val());
setPin();
});
}(i));
}
function scroll(delta) {
var incr = delta * 0.01;
hsv[modulatedComp] += incr;
if (modulatedComp!=0)
hsv[modulatedComp] = Math.min(1.0, Math.max(0.0, hsv[modulatedComp]));
setPin();
$inputbox.find("input[type='range']").each(function(i) {
$(this).val(hsv[i]);
});
}
function setPin() {
col.setHSV(hsv[0], hsv[1], hsv[2]);
col.rgba[3] = hsv[3];
$inputbox.css('background-color', 'rgba('+_(col.rgba).map(function(c) { return parseInt(c*255) }).join(',')+')');
ibx = $inputbox;
//var cmd = "<PATCH><NODE id='"+pin.node.id+"'><PIN pinname='"+pin.pinname+"' values='|"+col.toString()+"|'/></NODE></PATCH>";
pin.setValue(sliceIdx, col);
var cmd = {syncmode: 'diff', nodes: {}, links: []};
cmd.nodes[pin.node.id] = {pins: {}};
cmd.nodes[pin.node.id].pins[pin.pinname] = {values: _(pin.values).map(function(v) { return v.toString() }) };
pin.node.parentPatch.editor.update(pin.node.parentPatch, cmd);
}
$inputbox.on('mousewheel', function(e) {
var delta = e.originalEvent.wheelDelta/120;
scroll(delta);
e.preventDefault();
return false;
})
$inputbox.bind('DOMMouseScroll', function(e) {
var delta = -e.originalEvent.detail/3;
scroll(delta);
e.preventDefault();
return false;
})
}
var BrowserEditor = {}
BrowserEditor.PatchWindow = function(p, editor, selector) {
this.state = UIState.Idle;
var dragStart = {x: 0, y: 0};
var chart, nodes, inputPins, outputPins, links;
var linkStart = undefined;
var selectedNodes = [];
var patch = p;
var maxNodeId = 0;
var pageURL = location.protocol+'//'+location.host+(VVVVContext.Root[0]=='/' ? '' : location.pathname.replace(/\/[^\/]*$/, '')+'/')+VVVVContext.Root+'/patch.html';
var modKeyPressed = {CTRL: false, SHIFT: false, ALT: false};
var selectionBB = {x1: 0, y1: 0, x2: 0, y2: 0};
var focusedNodes = [];
if (!selector)
this.window = window.open(pageURL, p.nodename, "location=no, left=250, width="+p.windowWidth+", height="+p.windowHeight+", toolbar=no" );
else
this.window = window;
function resetSelection() {
chart.selectAll('.vvvv-node.selected')
.attr('class', function(d) { return d.isIOBox? 'vvvv-node vvvv-iobox' : 'vvvv-node' })
selectedNodes = [];
chart.selectAll('.vvvv-node')
.attr('opacity', defaultOpacity);
chart.selectAll('.vvvv-link')
.attr('opacity', 1.0);
}
function focusSubGraph(node) {
focusedNodes = [node].concat(getAllUpstreamNodes(node).concat(getAllDownstreamNodes(node)));
if (focusedNodes.length<=1)
return;
chart.selectAll('.vvvv-node').filter(function(d) {
return focusedNodes.indexOf(d)<0;
})
.attr('opacity', blurredOpacity);
chart.selectAll('.vvvv-link').filter(function(d) {
return focusedNodes.indexOf(d.fromPin.node)<0 || focusedNodes.indexOf(d.toPin.node)<0;
})
.attr('opacity', blurredOpacity);
}
function unfocusSubGraph() {
chart.selectAll('.vvvv-node').attr('opacity', defaultOpacity);
chart.selectAll('.vvvv-link').attr('opacity', defaultOpacity);
focusedNodes.length = 0;
}
var thatWin = this;
window.setTimeout(function() {
for (var i=0; i<patch.nodeList.length; i++) {
maxNodeId = Math.max(maxNodeId, patch.nodeList[i].id);
}
if (!selector) {
thatWin.window.document.title = p.nodename;
root = d3.select(thatWin.window.document.body);
}
else
root = d3.select($(selector).get(0));
if (!selector) {
$('body', thatWin.window.document).on('copy', function(e) {
var $patch = $('<PATCH>');
var links = [];
var selectedNodeIDs = _(selectedNodes).map(function(n) { return n.id });
for (var i=0; i<selectedNodes.length; i++) {
$patch.append(selectedNodes[i].serialize());
_(selectedNodes[i].inputPins).each(function(pin) {
for (var j=0; j<pin.links.length; j++) {
if (selectedNodeIDs.indexOf(pin.links[j].fromPin.node.id)>=0)
links.push(pin.links[j]);
}
});
}
for (var i=0; i<links.length; i++) {
$patch.append(links[i].serialize());
}
var xml = '<!DOCTYPE PATCH SYSTEM "http://vvvv.org/versions/vvvv45beta28.1.dtd" >'+$patch.wrapAll('<d></d>').parent().html();
xml = xml.replace(/<patch/g, "<PATCH");
xml = xml.replace(/<\/patch>/g, "\n </PATCH>");
xml = xml.replace(/<node/g, "\n <NODE");
xml = xml.replace(/<\/node>/g, "\n </NODE>");
xml = xml.replace(/<bounds/g, "\n <BOUNDS");
xml = xml.replace(/<\/bounds>/g, "\n </BOUNDS>");
xml = xml.replace(/<pin/g, "\n <PIN");
xml = xml.replace(/<\/pin>/g, "\n </PIN>");
xml = xml.replace(/<lonk/g, "\n <LINK");
xml = xml.replace(/<\/lonk>/g, "\n </LINK>");
e.originalEvent.clipboardData.setData("text/plain", xml);
e.preventDefault();
return false;
});
var mouseX = 0;
var mouseY = 0;
$('body', thatWin.window.document).mousemove(function(e) {
mouseX = e.pageX;
mouseY = e.pageY;
});
$('body', thatWin.window.document).on('paste', function(e) {
var xml = e.originalEvent.clipboardData.getData("text/plain");
if (!xml.match(/^<!DOCTYPE PATCH/))
return false;
$patch = $(xml);
var boundsLeft = undefined;
var boundsTop = undefined;
$patch.find('node bounds').each(function() {
var left = parseInt($(this).attr("left"));
var top = parseInt($(this).attr("top"));
if (!boundsLeft || left<boundsLeft)
boundsLeft = left;
if (!boundsTop || top<boundsTop)
boundsTop = top;
});
var oldNewNodeIdMap = {};
$patch.find('node').each(function() {
maxNodeId++;
oldNewNodeIdMap[$(this).attr("id")] = maxNodeId;
$(this).attr("createme", "pronto");
$(this).attr("id", maxNodeId);
var left = parseInt($(this).find('bounds').attr("left")) - boundsLeft + mouseX*15;
var top = parseInt($(this).find('bounds').attr("top")) - boundsTop + mouseY*15;
$(this).find('bounds').attr("left", left);
$(this).find('bounds').attr("top", top);
});
$patch.find('link').each(function() {
$(this).attr("createme", "pronto");
var fromNodeId = $(this).attr("srcnodeid");
var toNodeId = $(this).attr("dstnodeid");
$(this).attr("srcnodeid", oldNewNodeIdMap[fromNodeId]);
$(this).attr("dstnodeid", oldNewNodeIdMap[toNodeId]);
});
var xml = $patch.wrapAll('<d></d>').parent().html();
xml = xml.replace(/<patch/g, "<PATCH");
xml = xml.replace(/<\/patch>/g, "\n </PATCH>");
xml = xml.replace(/<node/g, "\n <NODE");
xml = xml.replace(/<\/node>/g, "\n </NODE>");
xml = xml.replace(/<bounds/g, "\n <BOUNDS");
xml = xml.replace(/<\/bounds>/g, "\n </BOUNDS>");
xml = xml.replace(/<pin/g, "\n <PIN");
xml = xml.replace(/<\/pin>/g, "\n </PIN>");
xml = xml.replace(/<link/g, "\n <LINK");
xml = xml.replace(/<\/link>/g, "\n </LINK>");
editor.update(patch, xml);
resetSelection();
var newNodeIDs = _(oldNewNodeIdMap).map(function(node_id) { return node_id});
chart.selectAll('.vvvv-node').each(function(node) {
if (newNodeIDs.indexOf(parseInt(node.id))>=0) {
selectedNodes.push(node);
d3.select(this).attr("class", "vvvv-node selected");
}
})
});
}
chart = root
.append('svg:svg')
.attr('class','chart')
.attr('width', Math.max(patch.windowWidth, patch.boundingBox.width))
.attr('height', Math.max(patch.windowHeight, patch.boundingBox.height))
var background = chart.append('svg:rect')
.attr('class','background')
.attr('width', Math.max(patch.windowWidth, patch.boundingBox.width))
.attr('height', Math.max(patch.windowHeight, patch.boundingBox.height))
if (!selector) {
chart.on('mousemove', function() {
if (thatWin.state==UIState.Connecting) {
chart.select('.vvvv-link.current-link')
.attr('x2', function(d) { return d3.event.pageX + (d3.select(this).attr('x1')<d3.event.pageX ? -1 : 1) })
.attr('y2', function(d) { return d3.event.pageY + (d3.select(this).attr('y1')<d3.event.pageY ? -1 : 1) })
}
else if (thatWin.state==UIState.Moving) {
var dx = d3.event.pageX - dragStart.x;
var dy = d3.event.pageY - dragStart.y;
// during drag, data and visualization are out of sync, as node.x/node.y do not match the position in the graphics
chart.selectAll('.vvvv-node.selected')
.attr('transform', function(d) { return 'translate('+(d.x+dx)+','+(d.y+dy)+')' })
for (var i=0; i<selectedNodes.length; i++) {
var n = selectedNodes[i];
chart.selectAll('.vvvv-link path')
.filter(function(d) { return d.fromPin.node.id == n.id || d.toPin.node.id == n.id })
.attr('d', function(d) {
var dx1 = dx * (selectedNodes.indexOf(d.fromPin.node)>=0);
var dy1 = dy * (selectedNodes.indexOf(d.fromPin.node)>=0);
var dx2 = dx * (selectedNodes.indexOf(d.toPin.node)>=0);
var dy2 = dy * (selectedNodes.indexOf(d.toPin.node)>=0);
var deltaY = d.toPin.node.y + dy2 - (d.fromPin.node.y + dy1) - d.fromPin.node.getHeight();
var cy = Math.min(Math.max(deltaY * 0.2, 6), 30);
if (deltaY<12 && deltaY>3)
cy = deltaY * 0.5;
var smooth = Math.max(0, Math.min(7, (deltaY - 2*cy)/2));
return 'M'+(d.fromPin.x + d.fromPin.node.x + 2 + .5 + dx1)+','+(d.fromPin.y + d.fromPin.node.y+ 4 + .5 + dy1)
+' L'+(d.fromPin.x + d.fromPin.node.x + 2 + .5 + dx1)+','+(d.fromPin.y + d.fromPin.node.y + 4 + dy1 + cy)
+' C'+(d.fromPin.x + d.fromPin.node.x + 2 + .5 + dx1)+','+(d.fromPin.y + d.fromPin.node.y + 4 + dy1 + cy + smooth)
+' '+(d.toPin.x + d.toPin.node.x + 2 + .5 + dx2)+','+(d.toPin.y + d.toPin.node.y + dy2 - (cy + smooth))
+' '+(d.toPin.x + d.toPin.node.x + 2 + .5 + dx2)+','+(d.toPin.y + d.toPin.node.y + .5 + dy2 -cy)
+' L'+(d.toPin.x + d.toPin.node.x + 2 + .5 + dx2)+','+(d.toPin.y + d.toPin.node.y + .5 + dy2)
})
}
}
else if (thatWin.state==UIState.AreaSelecting) {
selectionBB.x2 = d3.event.pageX;
selectionBB.y2 = d3.event.pageY;
chart.select('.selection-area')
.attr('transform', 'translate('+Math.min(selectionBB.x1, selectionBB.x2)+', '+Math.min(selectionBB.y1, selectionBB.y2)+')')
.attr('width', Math.abs(selectionBB.x2 - selectionBB.x1))
.attr('height', Math.abs(selectionBB.y2 - selectionBB.y1))
resetSelection();
chart.selectAll('.vvvv-node').each(function(d) {
var bounds = {x: [d.x, d.x + d.getWidth()], y: [d.y, d.y + d.getHeight()]};
var inArea= false;
for (var i=0; i<2; i++) {
for (var j=0; j<2; j++) {
if (bounds.x[i] >= Math.min(selectionBB.x1, selectionBB.x2)
&& bounds.x[i] <= Math.max(selectionBB.x1, selectionBB.x2)
&& bounds.y[j] >= Math.min(selectionBB.y1, selectionBB.y2)
&& bounds.y[j] <= Math.max(selectionBB.y1, selectionBB.y2)) {
inArea = true;
}
}
}
if (inArea) {
d3.select(this).attr('class', 'vvvv-node selected');
selectedNodes.push(d);
}
})
}
else if(thatWin.state==UIState.Resizing) {
var dx = d3.event.pageX - dragStart.x;
var cmd = {syncmode: 'diff', nodes: {}, links: []};
var n = selectedNodes[0];
var width = Math.max(n.getWidth()+dx, Math.max((_(n.inputPins).size()-1)*12+4, (n.label().length+2)*6))
cmd.nodes[n.id] = {x: n.x*15, y: n.y*15, width: width*15, height: n.height};
dragStart.x = d3.event.pageX;
editor.update(patch, cmd);
}
})
.on('contextmenu', function() {
if (thatWin.state==UIState.Connecting) {
chart.select('.vvvv-link.current-link').remove();
$('.resettable', thatWin.window.document).remove();
chart.selectAll('.vvvv-input-pin rect, .vvvv-output-pin rect')
.attr('width', 4)
.attr('height', 4)
.attr('x', 0)
.attr('y', 0)
.attr('class', '')
thatWin.state = UIState.Idle;
}
d3.event.stopPropagation();
d3.event.preventDefault();
return false;
})
.on('mouseup', function() {
if (thatWin.state==UIState.Moving) {
thatWin.state=UIState.Idle;
var dx = d3.event.pageX - dragStart.x;
var dy = d3.event.pageY - dragStart.y;
var cmd = {syncmode: 'diff', nodes: {}, links: []};
for (var i=0; i<selectedNodes.length; i++) {
var n = selectedNodes[i];
cmd.nodes[n.id] = {x: (dx+n.x)*15, y: (dy+n.y)*15, width: n.width, height: n.height};
}
editor.update(patch, cmd);
}
if (thatWin.state==UIState.AreaSelecting) {
chart.select('.selection-area').remove();
thatWin.state = UIState.Idle;
}
if (thatWin.state==UIState.Resizing) {
thatWin.state = UIState.Idle;
}
})
.on('dblclick', function() {
unfocusSubGraph();
$('#node_selection', thatWin.window.document).remove();
var x = d3.event.pageX;
var y = d3.event.pageY;
var $nodeselection = $('<div id="node_selection"><input type="text" id="node_filter"/></div>');
var $nodeselectionlist = $('<select id="new_node" size="8">');
$nodeselection.append($nodeselectionlist);
$nodeselection.css('left', x);
$nodeselection.css('top', y);
$('body', thatWin.window.document).append($nodeselection)
$nodeselection.find('input').bind('paste', function(e) {
e.stopPropagation();
});
$nodeselection.find('#node_filter').get(0).focus();
function filterNodes(e) {
$nodeselectionlist.empty();
var filter = $nodeselection.find('#node_filter').val().toLowerCase();
if (filter!="") {
$('.makro', thatWin.window.document).remove();
$('.subpatch_controls', thatWin.window.document).remove();
}
var available_nodes = VVVV.NodeNames.concat(_(p.executionContext.ShaderCodeResources).map(function(s,k) { return k.replace("%VVVV%/effects/", ""); }));
var matchingNodes = _(_(available_nodes).filter(function(n) { return VVVV.Helpers.translateOperators(n).toLowerCase().indexOf(filter)>=0 })).sortBy(function(n) { return n.toLowerCase().indexOf(filter); });
for (var i=0; i<matchingNodes.length; i++) {
$nodeselectionlist.append($('<option>'+matchingNodes[i]+'</option>'));
}
$nodeselectionlist.find('option').first().attr('selected', true);
}
filterNodes();
$nodeselection.find('#node_filter').keyup(function(e) {
if (e.which==13) {
tryAddNode();
return;
}
if (e.which==40) {
$nodeselectionlist.find('option:selected').first().next().attr('selected', true);
return false;
}
if (e.which==38) {
$nodeselectionlist.find('option:selected').first().prev().attr('selected', true);
return false;
}
filterNodes();
});
function tryAddNode() {
var filtertext = $nodeselection.find('#node_filter').val();
var nodename = $nodeselection.find('#new_node option:selected').val();
var filename = "";
if (!nodename && (filtertext.match(/\.fx$/) || filtertext.match(/\.v4p$/)) ) // if no option from the dropdown matches
nodename = filtertext;
if (nodename) {
var match;
// ./some/path/to/SomeShader.fx with path
if (match = nodename.match(/^(.*\/([^\.]+))(\.vvvvjs)?\.fx$/)) {
nodename = match[2]+" (EX9.Effect)";
filename = match[1]+".fx";
}
// SomeShader.fx -> system shader in VVVV.js' effects directory
else if (match = nodename.match("([^\.]+)(\.vvvvjs)?\.fx$")) {
nodename = match[1]+" (EX9.Effect)";
filename = "%VVVV%/effects/"+match[1]+".fx";
}
else if (match = nodename.match("[^\/]\.v4p$")) {
nodename = filtertext;
filename = filtertext;
}
maxNodeId++;
var cmd = {syncmode: 'diff', nodes: {}, links: []};
cmd.nodes[maxNodeId] = {nodename: nodename, x: x*15, y: y*15, width: 100, height: 100};
if (filename!="")
cmd.nodes[maxNodeId].filename = filename;
editor.update(patch, cmd);
$nodeselection.remove();
}
else {
maxNodeId++;
var cmd = "<PATCH>";
cmd += "<NODE componentmode='Hidden' id='"+maxNodeId+"' nodename='IOBox (String)' systemname='IOBox (String)'>";
cmd += "<BOUNDS type='Node' left='"+x*15+"' top='"+y*15+"' width='100' height='100'/>";
cmd += "<PIN pinname='Input String' values='|"+filtertext+"|'/>";
cmd += "</NODE>";
// TODO: Why does this not work with a cmd object?
//var cmd = {syncmode: 'diff', nodes: {}, links: []};
//cmd.nodes[maxNodeId] = {nodename: "IOBox (String)", x: x*15, y: y*15, width: 100, height: 100};
//cmd.nodes[maxNodeId].pins = {"Input String": filtertext};
editor.update(patch, cmd);
$nodeselection.remove();
}
$('.makro', thatWin.window.document).remove();
}
$nodeselection.find('#new_node').click(tryAddNode);
var makros = chart.selectAll('g.makro')
.data(Makros)
.enter().append('svg:g')
.attr('class', 'makro resettable')
.attr('transform', function(d, i) { return "translate("+(x-85-(i%2)*85)+", "+(y+Math.floor(i/2)*25)+")"; })
.on('click', function(d) {
var command = d.command.replace("{left}", x*15).replace("{top}", y*15).replace("{id}", ++maxNodeId);
editor.update(patch, command);
$nodeselection.remove();
$('.makro', thatWin.window.document).remove();
$('.resettable', thatWin.window.document).remove();
})
makros.append('svg:rect')
.attr('width', 80)
.attr('height', 20)
.attr('fill', '#AAA')
makros.append('svg:text')
.text(function(d) { return d.name })
.attr('text-anchor', 'middle')
.attr('fill', '#333')
.attr('font-size', 10)
.attr('font-family', "'Lucida Sans Unicode', sans-serif")
.attr('dy', 12)
.attr('dx', 40)
if (patch.serverSync) {
var subpatch_controls = chart.selectAll('g.subpatch_controls')
.data(["New Subpatch ..."])
.enter().append('svg:g')
.attr('class', 'subpatch_controls resettable')
.attr('transform', function(d, i) { return 'translate('+x+', '+(y-25*(i+1))+')'; })
.on('click', function(d, i) {
$nodeselection.remove();
$('.resettable', thatWin.window.document).remove();
var modal = $('<div class="modal resettable"><div class="modal-contents"><h1>New Supatch</h1><label>Filename:</label><input onload="this.focus()" type="text" id="new_subpatch_name" value="supersubsub.v4p"/></div></div>');
var create_subpatch_button = $('<input class="button" type="button" value="OK"/>');
var cancel_button = $('<input class="button cancel" type="button" value="X"/>');
modal.find('.modal-contents').append(create_subpatch_button);
modal.find('.modal-contents').append(cancel_button);
$('body', thatWin.window.document).append(modal);
modal.find('#new_subpatch_name').on('focus', function() { this.setSelectionRange(0, 11) });
cancel_button.click(function(e) {
$('.resettable', thatWin.window.document).remove();
});
create_subpatch_button.click(function() {
$('.resettable', thatWin.window.document).remove();
var filename = modal.find('#new_subpatch_name').val();
if (filename=="")
return;
$.ajax({
url: '/vvvvjs-service/create_subpatch',
type: 'get',
dataType: 'json',
data: {filename: location.pathname+"/"+VVVV.Helpers.prepareFilePath(filename, patch)},
success: function(response) {
if (response.status!="OK") {
alert(response.message);
return;
}
maxNodeId++;
var cmd = {syncmode: 'diff', nodes: {}, links: []};
cmd.nodes[maxNodeId] = {nodename: filename, filename: filename, x: x*15, y: y*15, width: 100, height: 100};
editor.update(patch, cmd);
},
error: function(response) {
alert(response.message);
}
})
});
})
subpatch_controls.append('svg:rect')
.attr('width', 90)
.attr('height', 20)
.attr('fill', '#AAA')
subpatch_controls.append('svg:text')
.text(function(d) { return d })
.attr('text-anchor', 'middle')
.attr('fill', '#333')
.attr('font-size', 10)
.attr('font-family', "'Lucida Sans Unicode', sans-serif")
.attr('dy', 12)
.attr('dx', 43)
}
})
.on('mousedown', function() {
if (thatWin.state!=UIState.Idle || d3.event.which!=1)
return;
thatWin.state = UIState.AreaSelecting;
selectionBB.x1 = selectionBB.x2 = d3.event.pageX+1;
selectionBB.y1 = selectionBB.y2 = d3.event.pageY+1;
chart.append('svg:rect')
.attr('class', 'selection-area')
.attr('stroke', '#000')
.attr('stroke-dasharray', '2,2')
.attr('stroke-width', 1)
.attr('fill', 'rgba(0,0,0,0)')
.attr('transform', 'translate('+selectionBB.x1+', '+selectionBB.y1+')')
.attr('width', 0)
.attr('height', 0)
resetSelection();
unfocusSubGraph();
})
background.on('click', function() {
thatWin.state = UIState.Idle;
$('.resettable', thatWin.window.document).remove();
chart.selectAll('.vvvv-input-pin rect, .vvvv-output-pin rect')
.attr('width', 4)
.attr('height', 4)
.attr('x', 0)
.attr('y', 0)
.attr('class', '')
$('#node_selection', thatWin.window.document).remove();
resetSelection();
unfocusSubGraph();
linkStart = undefined;
})
// set modifier keys
function setModifierKeys(e) {
modKeyPressed.CTRL = e.ctrlKey;
modKeyPressed.ALT = e.altKey;
modKeyPressed.SHIFT = e.shiftKey;
}
$(thatWin.window.document).keydown(setModifierKeys);
$(thatWin.window.document).keyup(setModifierKeys);
$(thatWin.window.document).keydown(function(e) {
// DELETE key
if ((e.which==46 || e.which==8) && selectedNodes.length>0) {
var cmd = {syncmode: 'diff', nodes: {}, links: []};
for (var i=0; i<selectedNodes.length; i++) {
var n = selectedNodes[i];
cmd.nodes[selectedNodes[i].id] = {delete: true};
_(n.inputPins).each(function(pin) {
_(pin.links).each(function(l) {
cmd.links.push({delete: true, srcnodeid: l.fromPin.node.id, srcpinname: l.fromPin.pinname, dstnodeid: l.toPin.node.id, dstpinname: l.toPin.pinname})
});
})
_(n.outputPins).each(function(pin) {
_(pin.links).each(function(l) {
cmd.links.push({delete: true, srcnodeid: l.fromPin.node.id, srcpinname: l.fromPin.pinname, dstnodeid: l.toPin.node.id, dstpinname: l.toPin.pinname})
});
})
}
editor.update(patch, cmd);
selectedNodes = [];
}
// CTRL + S / Save
else if ((e.which==115 || e.which==83) && e.ctrlKey) {
editor.save(patch);
e.preventDefault();
return false;
}
// CTRL + I / Open Inspector
else if ((e.which==73) && e.ctrlKey) {
if (editor.inspector)
editor.inspector.win.focus();
else {
editor.openInspector(".");
}
e.preventDefault();
return false;
}
// CTRL + A / Select All
else if ((e.which==65) && e.ctrlKey) {
resetSelection();
chart.selectAll('.vvvv-node').each(function(d) {
selectedNodes.push(d);
d3.select(this).attr("class", "vvvv-node selected");
});
e.preventDefault();
return false;
}
})
$(thatWin.window).resize(function() {
patch.windowWidth = $(this).width();
patch.windowHeight = $(this).height();
patch.boundingBox.width = Math.max(patch.windowWidth, patch.boundingBox.width);
patch.boundingBox.height = Math.max(patch.windowHeight, patch.boundingBox.height);
patch.afterUpdate();
})
}
thatWin.drawComplete();
//graph.afterEvaluate = this.redraw;
patch.afterUpdate = function() {
thatWin.drawComplete();
if (editor.inspector)
window.setTimeout(editor.inspector.update, 100); // TODO: why has this to be delayed?
}
patch.afterEvaluate = function() {
nodes.filter(function(d) {
return d.isIOBox && VVVV.PinTypes[d.IOBoxInputPin().typeName].makeLabel;
})
.each(function(d) {
VVVV.PinTypes[d.IOBoxInputPin().typeName].makeLabel(this, d);
});
}
}, 1000);
this.close = function() {
this.window.close();
}
this.drawComplete = function() {
if (patch.disposing)
return;
if (nodes)
nodes.remove();
if (links)
links.remove();
if (!selector && (patch.windowWidth != $(thatWin.window).width() || patch.windowHeight != $(thatWin.window).height()))
thatWin.window.resizeTo(patch.windowWidth, patch.windowHeight);
chart
.attr('width', Math.max(patch.windowWidth, patch.boundingBox.width))
.attr('height', Math.max(patch.windowHeight, patch.boundingBox.height))
chart.selectAll('.background')
.attr('width', Math.max(patch.windowWidth, patch.boundingBox.width))
.attr('height', Math.max(patch.windowHeight, patch.boundingBox.height))
var link_group = chart.append('svg:g')
.attr('class', 'link-group');
// NODES
nodes = chart.selectAll('g.vvvv-node')
.data(patch.nodeList)
.enter().append('svg:g')
.attr('class', function(d) {
var c = 'vvvv-node';
if (d.isIOBox)
c += ' vvvv-iobox';
if (selectedNodes.indexOf(d)>=0) {
c += ' selected';
}
return c;
})
.attr('id', function(d) { return 'vvvv-node-'+d.id})
.attr('transform', function(d) { return 'translate('+d.x+','+d.y+')' })
.attr('opacity', function(d) { return (focusedNodes.length>1 && focusedNodes.indexOf(d)<0) ? blurredOpacity : defaultOpacity })
nodes.append('svg:rect')
.attr('class', 'vvvv-node-background')
.attr('height', function(d) { return d.getHeight(); })
.attr('width', function(d) { return d.getWidth() + 4; })
.attr('x', -2)
.attr('rx', 2)
.attr('ry', 2)
.attr('fill', function(d) {
if (d.isComment())
return 'rgba(0,0,0,0)';
else if (d.not_implemented)
return 'rgba(255,0,0,1)';
else if (d.isIOBox)
return '#ddd';
//else if (d.inCluster)
//return 'rgba(255, 255, 0, 1)';
else
return '#999';
})
.attr('stroke', function(d) { return d.isIOBox ? '#999' : 'none'})
.attr('stroke-width', 1)
nodes.append('svg:rect')
.attr('class', 'resize-handle')
.attr('height', function(d) { return d.getHeight() - 4; })
.attr('x', function(d) { return d.getWidth(); })
.attr('y', 2)
.attr('width', 4)
.attr('fill', 'rgba(0,0,0,0)')
.attr('cursor', 'e-resize')
/*nodes.append('svg:rect')
.attr('class', 'vvvv-node-pinbar')
.attr('height', function (d) { return d.isIOBox? 2 : 4 })
.attr('fill', function(d) { return d.isIOBox? "#dddddd" : "#9a9a9a"; })
.attr('width', function(d) { return d.getWidth(); })
nodes.append('svg:rect')
.attr('class', 'vvvv-node-pinbar')
.attr('y',function(d) { return d.isIOBox? d.getHeight() -2 : d.getHeight()-4; })
.attr('height', function (d) { return d.isIOBox? 2 : 4 })
.attr('fill', function(d) { return d.isIOBox? "#dddddd" : "#9a9a9a"; })
.attr('width', function(d) { return d.getWidth(); })*/
nodes.append('svg:g')
.attr('class', 'descriptive-name-bg')
nodes.append('svg:text')
.text(function(d) { return (d.invisiblePins["Descriptive Name"]) ? d.invisiblePins["Descriptive Name"].getValue(0) : null })
.attr('class', 'vvvv-node-descriptive-name')
.attr('shape-rendering', 'crispEdges')
.attr('dy', function(d) { return d.getHeight()+12 })
.attr('dx', 2)
.attr('font-size', 10)
.attr('font-family', "'Lucida Sans Unicode', sans-serif")
.attr('fill', 'white')
nodes.append('svg:text')
.text(function(d) {
if (d.invisiblePins["Node Name"] && d.nodename=="DefineNode (System)")
return d.invisiblePins["Node Name"].getValue(0);
if (d.invisiblePins["Effect Descriptor"] && d.nodename=="DefineEffect (DX9)")
return d.invisiblePins["Effect Descriptor"].getValue(0);
return null;
})
.attr('class', 'vvvv-node-descriptive-name')
.attr('shape-rendering', 'crispEdges')
.attr('dy', function(d) { return d.getHeight()+12 })
.attr('dx', 2)
.attr('font-size', 10)
.attr('font-family', "'Lucida Sans Unicode', sans-serif")
.attr('fill', 'blue')
nodes.selectAll('g.descriptive-name-bg')
.append('svg:rect')
.attr('fill', function(d) {
if (d.invisiblePins["Descriptive Name"]==undefined || d.invisiblePins["Descriptive Name"].getValue(0)=="")
return 'rgba(0,0,0,0)';
return 'rgba(0,0,0,1)';
})
.attr('y', function(d) { return d.getHeight() })
.attr('width', 5)
.attr('height', 14)
nodes.each(function(d) {
if (d.isIOBox && VVVV.PinTypes[d.IOBoxInputPin().typeName].makeLabel) {
VVVV.PinTypes[d.IOBoxInputPin().typeName].makeLabel(this, d);
var rowCount = d.IOBoxRows();
var sliceCount = d.IOBoxInputPin().getSliceCount();
var element = this;
for (var i=0; i<rowCount; i++) {
(function(j) {
var l = d3.select(element)
.append('svg:rect')
.attr('width', d.getWidth() +2)
.attr('height', d.getHeight()/rowCount - 4)
.attr('y', j*12 + 2)
.attr('fill', 'rgba(0,0,0,0)');
if (!selector) {
l.on('contextmenu', function(d) {
if (d.IOBoxInputPin().getValue(0)!=undefined && !d.IOBoxInputPin().isConnected()) {
$('.resettable', thatWin.window.document).remove();
var $inputbox = $("<input type='text'/>");
$('body', thatWin.window.document).append($inputbox);
$inputbox.css('position', 'absolute');
$inputbox.css('left', $(this).offset().left);
$inputbox.css('top', $(this).offset().top + 2);
$inputbox.css('width', d.getWidth());
$inputbox.css('height', d.getHeight()/rowCount-4);
VVVV.PinTypes[d.IOBoxInputPin().typeName].openInputBox(thatWin.window, $inputbox, d.IOBoxInputPin(), j);
}
d3.event.stopPropagation();
d3.event.preventDefault();
return false;
})
}
})(i);
}
}
else {
d3.select(this)
.append('svg:text')
.text(function(d) { return d.label(); })
.attr('class', 'vvvv-node-label')
.attr('shape-rendering', 'crispEdges')
.attr('dy', function(d, i) {
return i*12+12;
})
.attr('dx', 4)
.attr('font-size', 10)
.attr('font-family', "'Lucida Sans Unicode', sans-serif")
}
});
// INPUT PINS
inputPins = nodes.selectAll('g.vvvv-input-pin')
.data(function(d) {
if (d.isSubpatch)
return _(d.inputPins).sortBy(function(p) { return p.slavePin ? p.slavePin.node.x : 1 }).map(function(p,k) { return p });
else
return _(d.inputPins).map(function(p,k) { return p });
})
.enter().append('svg:g')
.attr('class', 'vvvv-input-pin')
.attr('transform', function(d, i) {
pinOffset = 0;
if (_(d.node.inputPins).size()>1)
pinOffset = (d.node.getWidth()-4)/(_(d.node.inputPins).size()-1);
d.y = -1;
d.x = i*pinOffset;
//if (d.node.isIOBox)
// d.x = d.node.getWidth() - d.x - 4;
return 'translate('+d.x+', '+d.y+')';
})
inputPins.append('svg:rect')
.attr('width', 4)
.attr('height', 4)
.attr('ry', 1)
.attr('rx', 1)
.attr('fill', function(d) { return d.node.isComment() ? 'rgba(0,0,0,0)' : (d.clusterEdge ? '#FFFF00' : '#666666') })
.on('mouseover', function(d, i) {
chart.selectAll('#vvvv-node-'+d.node.id+' g.vvvv-input-pin').filter(function(d, j) { return j==i }).each(function() {
d3.select(this).append('svg:rect')
.attr('class', 'vvvv-input-pin-highlight')
.attr('width', 4)
.attr('height', 4)
.attr('fill', 'rgba(0,0,0,1)')
.attr('y', -4)
d3.select(this).append('svg:text')
.text(function(d) {
//Truncate preview string to avoid crashes with big strings
var LabelContent = ""+d.getValue(0);
if(LabelContent.length > 30)
LabelContent = LabelContent.substring(0,30)+'...';
if (d.getSliceCount()>1)
return d.pinname+"("+d.getSliceCount()+"): "+LabelContent;
else
return d.pinname+": "+LabelContent;
})
.attr('dy', 30)
.attr('font-size', 10)
.attr('font-family', "'Lucida Sans Unicode', sans-serif")
.attr('fill', 'rgba(0,0,0,1)');
});
})
.on('mouseout', function(d, i) {
chart.selectAll('#vvvv-node-'+d.node.id+' g.vvvv-input-pin text').remove();
chart.selectAll('#vvvv-node-'+d.node.id+' g.vvvv-input-pin rect.vvvv-input-pin-highlight').remove();
})
outputPins = nodes.selectAll('g.vvvv-output-pin')
.data(function(d) { return _(d.outputPins).map(function(p,k) { return p }); })
.enter().append('svg:g')
.attr('class', 'vvvv-output-pin')
.attr('transform', function(d, i) {
pinOffset = 0;
if (_(d.node.outputPins).size()>1)
pinOffset = (d.node.getWidth()-4)/(_(d.node.outputPins).size()-1);
d.y = d.node.getHeight()-4+1;
d.x = i*pinOffset;
//if (d.node.isIOBox)
// d.x = d.node.getWidth() - d.x - 4;
return 'translate('+d.x+', '+d.y+')';
});
outputPins.append('svg:rect')
.attr('width', 4)
.attr('height', 4)
.attr('ry', 1)
.attr('rx', 1)
.attr('fill', function(d) { return d.node.isComment() ? 'rgba(0,0,0,0)' : (d.clusterEdge ? '#FFFF00' : '#666666') })
.on('mouseover', function(d, i) {
chart.selectAll('#vvvv-node-'+d.node.id+' g.vvvv-output-pin').filter(function(d, j) { return j==i }).each(function() {
d3.select(this).append('svg:rect')
.attr('class', 'vvvv-output-pin-highlight')
.attr('width', 4)
.attr('height', 4)
.attr('fill', 'rgba(0,0,0,1)')
.attr('y', -4)
if (d.values.code) {
var f = new Function("patch", d.generateStaticCode(false));
f(d.node.parentPatch);
}
d3.select(this).append('svg:text')
.text(function(d) {
//Truncate preview string to avoid crashes with big strings
var LabelContent = ""+d.getValue(0);
if(LabelContent.length > 30)
LabelContent = LabelContent.substring(0,30)+'...';
if (d.getSliceCount()>1)
return d.pinname+"("+d.getSliceCount()+"): "+LabelContent;
else
return d.pinname+": "+LabelContent;
})
.attr('dy', 30)
.attr('font-size', 10)
.attr('font-family', "'Lucida Sans Unicode', sans-serif")
.attr('fill', 'rgba(0,0,0,1)');
});
})
.on('mouseout', function(d, i) {
chart.selectAll('#vvvv-node-'+d.node.id+' g.vvvv-output-pin text').remove();
chart.selectAll('#vvvv-node-'+d.node.id+' g.vvvv-output-pin rect.vvvv-output-pin-highlight').remove();
})
links = link_group.selectAll('g.vvvv-link')
.data(patch.linkList)
.enter().append('svg:g')
.attr('class', 'vvvv-link')
.attr('opacity', function(d) { return (focusedNodes.length>0 && (focusedNodes.indexOf(d.fromPin.node)<0 || focusedNodes.indexOf(d.toPin.node)<0)) ? blurredOpacity : 1.0 })
links.append('svg:path')
.attr('stroke', 'rgba(0, 0, 0, 0)')
.attr('fill', 'none')
.attr('stroke-width', 4)
.attr('d', function(d) {
var deltaY = d.toPin.node.y - d.fromPin.node.y - d.fromPin.node.getHeight();
var cy = Math.min(Math.max(deltaY * 0.2, 6), 30);
if (deltaY<12 && deltaY>3)
cy = deltaY * 0.5;
var smooth = Math.max(0, Math.min(7, (deltaY - 2*cy)/2));
return 'M'+(d.fromPin.x + d.fromPin.node.x + 2 + .5)+','+(d.fromPin.y + d.fromPin.node.y+ 4 + .5)
+' L'+(d.fromPin.x + d.fromPin.node.x + 2 + .5)+','+(d.fromPin.y + d.fromPin.node.y + 4 + cy)
+' C'+(d.fromPin.x + d.fromPin.node.x + 2 + .5)+','+(d.fromPin.y + d.fromPin.node.y + 4 + cy + smooth)
+' '+(d.toPin.x + d.toPin.node.x + 2 + .5)+','+(d.toPin.y + d.toPin.node.y - (cy + smooth))
+' '+(d.toPin.x + d.toPin.node.x + 2 + .5)+','+(d.toPin.y + d.toPin.node.y + .5 - cy)
+' L'+(d.toPin.x + d.toPin.node.x + 2 + .5)+','+(d.toPin.y + d.toPin.node.y + .5)
})
.on('mouseenter', function() {
d3.select(this)
.attr('stroke-width', 4)
.attr('stroke', 'rgba(0, 0, 0, 0.35)')
})
.on('mouseleave', function() {
d3.select(this)
.attr('stroke-width', 4)
.attr('stroke', 'rgba(0, 0, 0, 0)')
})
links.append('svg:path')
.attr('stroke', '#000')
.attr('fill', 'none')
.attr('stroke-width', 1)
.attr('stroke-dasharray', function(d) { return (d.fromPin.node.inCluster ? !d.toPin.node.inCluster : d.toPin.node.inCluster) ? '2,2' : 'none' })
.attr('d', function(d) {
var deltaY = d.toPin.node.y - d.fromPin.node.y - d.fromPin.node.getHeight();
var cy = Math.min(Math.max(deltaY * 0.2, 6), 30);
if (deltaY<12 && deltaY>3)
cy = deltaY * 0.5;
var smooth = Math.max(0, Math.min(7, (deltaY - 2*cy)/2));
return 'M'+(d.fromPin.x + d.fromPin.node.x + 2 + .5)+','+(d.fromPin.y + d.fromPin.node.y+ 4 + .5)
+' L'+(d.fromPin.x + d.fromPin.node.x + 2 + .5)+','+(d.fromPin.y + d.fromPin.node.y + 4 + cy)
+' C'+(d.fromPin.x + d.fromPin.node.x + 2 + .5)+','+(d.fromPin.y + d.fromPin.node.y + 4 + cy + smooth)
+' '+(d.toPin.x + d.toPin.node.x + 2 + .5)+','+(d.toPin.y + d.toPin.node.y - (cy + smooth))
+' '+(d.toPin.x + d.toPin.node.x + 2 + .5)+','+(d.toPin.y + d.toPin.node.y + .5 - cy)
+' L'+(d.toPin.x + d.toPin.node.x + 2 + .5)+','+(d.toPin.y + d.toPin.node.y + .5)
})
// set descriptive name widths after rendering
$('.descriptive-name-bg rect', thatWin.window.document).each(function() {
$(this, thatWin.window.document).attr('width', $(this, thatWin.window.document).parent().next().get(0).getBBox().width+4);
})
// Editing Functionality starts here ...
if (!selector) {
chart.selectAll('g.vvvv-input-pin')
.on('contextmenu', function(d, i) {
if (VVVV.PinTypes[d.typeName].openInputBox) {
if (d.getValue(0)!=undefined && !d.isConnected()) {
$('.resettable', thatWin.window.document).remove();
var $inputbox = $("<input type='text'/>");
$('body', thatWin.window.document).append($inputbox);
$inputbox.css('position', 'absolute');
$inputbox.css('left', $(this).offset().left);
$inputbox.css('top', $(this).offset().top - 3);
$inputbox.css('width', 50);
$inputbox.css('height', 14);
VVVV.PinTypes[d.typeName].openInputBox(thatWin, $inputbox, d, 0);
}
}
d3.event.stopPropagation();
d3.event.preventDefault();
return false;
})
chart.selectAll('g.vvvv-input-pin, g.vvvv-output-pin')
.on('click', function(d, i) {
if (thatWin.state!=UIState.Connecting) {
unfocusSubGraph();
linkStart = d;
thatWin.state = UIState.Connecting;
var that = this;
chart.append('svg:line')
.attr('class', 'vvvv-link current-link resettable')
.attr('stroke', '#000')
.attr('stroke-width', 1)
.attr('x1', d.x + d.node.x + 2 + .5)
.attr('y1', d.y + d.node.y + 2 + .5)
.attr('x2', d.x + d.node.x + 2 + .5)
.attr('y2', d.y + d.node.y + 2 + .5)
if (linkStart.direction==VVVV.PinDirection.Output)
var targetDir = 'input';
else
var targetDir = 'output';
var upnodes = [];
if (!linkStart.node.delays_output)
upnodes = getAllUpstreamNodes(linkStart.node);
chart.selectAll('g.vvvv-'+targetDir+'-pin rect')
.filter(function(d) {
if (!(d.typeName==linkStart.typeName || (linkStart.typeName=="Node" && !VVVV.PinTypes[d.typeName].primitive) || (d.typeName=="Node" && !VVVV.PinTypes[linkStart.typeName].privimive)))
return false;
var browserOnly = linkStart.node.environments && linkStart.node.environments.indexOf('browser')>=0;
if (!VVVV.PinTypes[d.typeName].primitive && d.node.inCluster && browserOnly)
return false;
browserOnly = d.node.environments && d.node.environments.indexOf('browser')>=0;
if (!VVVV.PinTypes[d.typeName].primitive && linkStart.node.inCluster && browserOnly)
return false;
if (upnodes.indexOf(d.node)>=0 || d.node==linkStart.node)
return false;
return true;
})
.attr('width', 6)
.attr('height', 6)
.attr('x', -1)
.attr('y', function(d) { return d.direction==VVVV.PinDirection.Input ? -2 : 0})
.attr('class', 'vvvv-connection-highlight')
}
else {
if (linkStart.direction==VVVV.PinDirection.Input) {
var srcPin = d;
var dstPin = linkStart;
}
else {
var srcPin = linkStart;
var dstPin = d;
}
if ($(this).find('.vvvv-connection-highlight').length==1) {
var cmd = {syncmode: 'diff', nodes: {}, links: []};
_(dstPin.links).each(function(l) {
cmd.links.push({delete: true, srcnodeid: l.fromPin.node.id, srcpinname: l.fromPin.pinname, dstnodeid: l.toPin.node.id, dstpinname: l.toPin.pinname});
});
cmd.links.push({srcnodeid: srcPin.node.id, srcpinname: srcPin.pinname, dstnodeid: dstPin.node.id, dstpinname: dstPin.pinname});
editor.update(patch, cmd);
chart.select('.vvvv-link.current-link').remove();
chart.select('.vvvv-connection-highlight').remove();
thatWin.state = UIState.Idle;
patch.afterUpdate();
}
}
d3.event.stopPropagation();
})
.on('mousedown', function() {
d3.event.stopPropagation();
})
chart.selectAll('g.vvvv-link path')
.on('mousedown', function(d) {
if (thatWin.state == UIState.Idle && d3.event.which==3 || (d3.event.which==1 && d3.event.ctrlKey))
editor.update(patch, {syncmode: 'diff', nodes: {}, links: [{delete: true, srcnodeid: d.fromPin.node.id, srcpinname: d.fromPin.pinname, dstnodeid: d.toPin.node.id, dstpinname: d.toPin.pinname}]});
d3.event.preventDefault();
})
nodes
// node selection
.on('mousedown', function(d) {
$('.resettable', thatWin.window.document).remove();
thatWin.state = UIState.Moving;
if (selectedNodes.indexOf(d)<0) {
if (!modKeyPressed.SHIFT) {
resetSelection();
}
d3.select(this).attr('class', 'vvvv-node selected');
selectedNodes.push(d);
}
dragStart.x = d3.event.pageX;
dragStart.y = d3.event.pageY;
if (editor.inspector)
editor.inspector.setNode(d);
focusSubGraph(d);
d3.event.preventDefault();
d3.event.stopPropagation();
return false;
})
// open subpatch or UI window
.on('contextmenu', function(d) {
if (d.isSubpatch) {
editor.openPatch(d);
}
else if (d.openUIWindow) {
d.openUIWindow();
}
d3.event.preventDefault();
d3.event.stopPropagation();
return false;
})
nodes.selectAll('.resize-handle')
.on('mousedown', function(d) {
$('.resettable', thatWin.window.document).remove();
dragStart.x = d3.event.pageX;
dragStart.y = d3.event.pageY;
resetSelection();
selectedNodes.push(d);
thatWin.state = UIState.Resizing;
d3.event.stopPropagation();
return false;
})
}
}
}
BrowserEditor.Inspector = function(VVVVRoot) {
this.win = window.open(location.protocol+'//'+location.host+(VVVVContext.Root[0]=='/' ? '' : location.pathname.replace(/\/[^\/]*$/, '')+'/')+VVVVContext.Root+'/inspektor.html', 'inspektor', "location=no, width=250, height=600, toolbar=no" );
var node;
var pin;
insw = this.win;
var that = this;
function showOverview() {
pin = undefined;
$(that.win.document).find('#pins, #values').empty()
$(that.win.document).find('#pins').append('<div class="row heading">Configuration</div>')
$(that.win.document).find('#values').append('<div class="row heading"></div>')
_(node.invisiblePins).each(function(p) {
addPin(p);
})
$(that.win.document).find('#pins').append('<div class="row heading">Input Pins</div>')
$(that.win.document).find('#values').append('<div class="row heading"></div>')
_(node.inputPins).each(function(p) {
addPin(p);
})
$(that.win.document).find('#pins').append('<div class="row heading">Output Pins</div>')
$(that.win.document).find('#values').append('<div class="row heading"></div>')
_(node.outputPins).each(function(p) {
addPin(p);
})
}
function addPin(p) {
var $pinlink = $('<a class="row pin" href="#">'+p.pinname+'</a>');
$(that.win.document).find('#pins').append($pinlink);
$pinlink.click(function(e) {
$(that.win.document).find('a.pin.active').removeClass('active');
$(this).addClass('active');
if (VVVV.PinTypes[p.typeName].openInputBox)
showAllSlices(p);
e.preventDefault();
return false;
})
if (VVVV.PinTypes[p.typeName].openInputBox && p.direction!=VVVV.PinDirection.Output && !p.isConnected() && p.getValue(0)!=undefined) {
var $iobox = $('<div class="row value"><div style="height:100%">'+p.getValue(0).toString().replace(/</g, '&lt;').replace(/>/g, '&gt;')+'</div></div>');
$iobox.find('div').click(function() {
VVVV.PinTypes[p.typeName].openInputBox(that.win, $(this), p, 0);
})
}
else
var $iobox = $('<div class="row value readonlyvalue"><div style="height:100%">'+(p.getValue(0)==undefined ? "undefined" : p.getValue(0).toString().replace(/</g, '&lt;').replace(/>/g, '&gt;'))+'</div></div>');
if (p.typeName=="Color") {
$iobox.find('div').css('background-color', 'rgba('+_(p.getValue(0).rgba).map(function(c) { return parseInt(c*255) }).join(',')+')');
}
$(that.win.document).find('#values').append($iobox);
}
function showAllSlices(p) {
var pinChanged = (pin!=p);
pin = p;
var sliceCount = p.getSliceCount();
var $sliceCountBox = $('<div class="row heading"><input type="text" value="'+sliceCount+'"/ size="3"/> Slices</div>');
if (p.direction!=VVVV.PinDirection.Input)
$sliceCountBox.find('input').attr('disabled', true);
var e = $(that.win.document).find('#values .row').first();
if (e.length>0)
e.replaceWith($sliceCountBox);
else
$(that.win.document).find('#values').append($sliceCountBox);
$sliceCountBox.find('input').change(function(e) {
var newSliceCount = Math.max(1, parseInt($(this).val()));
for (var i=pin.getSliceCount(); i<newSliceCount; i++) {
pin.setValue(i, VVVV.PinTypes[pin.typeName].defaultValue());
}
pin.setSliceCount(newSliceCount);
pin.node.parentPatch.afterUpdate();
e.preventDefault();
return false;
})
var i = 0;
for (i=0; i<sliceCount; i++) {
var $currentElement = $(that.win.document).find('#values .row').eq(i+1);
if (!pinChanged && $currentElement.children().first().hasClass('pininputbox')) // leave open iobox alone ...
continue;
if (VVVV.PinTypes[p.typeName].openInputBox && p.direction!=VVVV.PinDirection.Output && !p.isConnected() && p.getValue(0)!=undefined) {
var $iobox = $('<div class="row value"><div style="height:100%">'+p.getValue(i).toString().replace(/</g, '&lt;').replace(/>/g, '&gt;')+'</div></div>');
(function(sliceIdx) {
$iobox.find('div').click(function() {
VVVV.PinTypes[p.typeName].openInputBox(that.win, $(this), p, sliceIdx);
})
})(i);
}
else
var $iobox = $('<div class="row value readonlyvalue"><div style="height:100%">'+p.getValue(i).toString().replace(/</g, '&lt;').replace(/>/g, '&gt;')+'</div></div>');
if ($currentElement.length>0) {
$currentElement.replaceWith($iobox);
}
else {
$(that.win.document).find('#values').append($iobox);
}
if (p.typeName=="Color") {
$iobox.find('div').css('background-color', 'rgba('+_(p.getValue(i).rgba).map(function(c) { return parseInt(c*255) }).join(',')+')');
}
}
i++;
var e = $(that.win.document).find('#values .row');
for (; i<e.length; i++) {
e.eq(i).remove();
}
}
this.update = function() {
if (!node)
return;
if (pin)
showAllSlices(pin);
else
showOverview();
}
this.setNode = function(n) {
node = n;
showOverview();
}
this.close = function() {
this.win.close();
}
}
BrowserEditor.Interface = function() {
var patchWindows = [];
var patches = {};
this.inspector = undefined;
function confirmLeave() {
return "Are you sure you want to leave? Unsaved changes in your patches will be lost.";
}
this.enable = function(p, opts) {
opts = opts || {}
this.addPatch(p);
this.openPatch(p, opts.selector);
if (!opts.selector) {
var that = this;
$(window).bind('beforeunload', confirmLeave);
$(window).unload(function() {
that.disable();
})
this.openInspector(VVVVContext.Root);
if (patchWindows[0].window) {
if (opts && opts.success)
opts.success();
}
else {
if (opts && opts.error)
opts.error();
}
}
if (!p.serverSync.isConnected()) {
p.serverSync.connect();
}
}
this.openInspector = function(VVVVRoot, node) {
if (this.inspector)
return;
this.inspector = new BrowserEditor.Inspector(VVVVRoot);
var that = this;
$(this.inspector.win).bind('beforeunload', function() {
console.log('closing inspektor');
that.inspector = undefined;
})
}
var patch_signatures = [];
this.addPatch = function(p) {
var patch_signature = p.id;
var pp = p;
while ((pp = pp.parentPatch)!=undefined) {
patch_signature += '-'+pp.id;
}
if (patch_signatures.indexOf(patch_signature)>=0)
return;
patch_signatures.push(patch_signature);
p.editor = this;
var path = VVVV.Helpers.prepareFilePath(p.nodename, p.parentPatch);
if (patches[path]==undefined)
patches[path] = [];
patches[path].push(p);
var subpatches = p.getSubPatches();
for (var i=0; i<subpatches.length; i++) {
this.addPatch(subpatches[i]);
}
}
this.removePatch = function(p) {
var subpatches = p.getSubPatches();
for (var i=0; i<subpatches.length; i++) {
this.removePatch(subpatches[i]);
}
var path = VVVV.Helpers.prepareFilePath(p.nodename, p.parentPatch);
patches[path].splice(patches[path].indexOf(p), 1);
if (patches[path].length == 0)
delete patches[path];
console.log(patches);
}
this.openPatch = function(p, selector) {
patchWindows.push(new BrowserEditor.PatchWindow(p, this, selector));
}
this.update = function(node, cmd) {
var path = VVVV.Helpers.prepareFilePath(node.nodename, node.parentPatch)
var n = patches[path].length;
for (var i=0; i<n; i++) {
patches[path][i].doLoad(cmd);
patches[path][i].afterUpdate();
}
if (patches[path][0].serverSync.isConnected()) {
if (typeof cmd == 'object')
cmd = JSON.stringify(cmd);
patches[path][0].serverSync.sendPatchUpdate(patches[path][0], cmd);
}
}
this.disable = function() {
for (var i=0; i<patchWindows.length; i++) {
patchWindows[i].close();
}
if (this.inspector)
this.inspector.close();
$(window).unbind('beforeunload', confirmLeave);
}
this.save = function(node) {
var path = VVVV.Helpers.prepareFilePath(node.nodename, node.parentPatch)
if (patches[path][0].serverSync.isConnected()) {
if (!patches[path][0].isPersisted && window.confirm("Do you want to save the patch "+node.nodename+"?")) {
patches[path][0].serverSync.sendPatchSave(patches[path][0]);
for (var i=0; i<patches[path].length; i++) {
patches[path][i].isPersisted = true;
}
}
var i = patches[path][0].nodeList.length;
while (i--) {
if (patches[path][0].nodeList[i].isSubpatch)
this.save(patches[path][0].nodeList[i]);
}
}
else {
var $dl = $("<a>save</a>");
$('body').append($dl);
$dl.attr('href', "data:application/octet-stream;charset=utf-8,"+encodeURIComponent(node.toXML()));
$dl.attr('download', node.nodename.replace( /.*\//, ''));
$dl[0].click();
$dl.remove();
}
}
this.sendUndo = function() {
}
};
// Convinience function to easily inject a patch into the page
VVVV.VVVViewer = function(patch, selector) {
var editor = new BrowserEditor.Interface();
editor.enable(patch, {selector: selector});
}
return BrowserEditor;
});