rpd
Version:
RPD is a minimal framework for building Node-Based User Interfaces, powered by Reactive Programming
185 lines (147 loc) • 7.35 kB
JavaScript
;(function(global) {
"use strict";
var Rpd = global.Rpd;
if (typeof Rpd === "undefined" && typeof require !== "undefined") {
Rpd = require('rpd');
}
Rpd.style('plain', 'svg', (function() {
var ƒ = Rpd.unit;
var δ = Rpd.Render.data;
var socketPadding = 30, // distance between inlets/outlets in SVG units
socketsMargin = 5; // distance between first/last inlet/outlet and body edge
function _createSvgElement(name) {
return document.createElementNS(d3.namespaces.svg, name);
}
function getPos(elm) { var bounds = elm.getBoundingClientRect();
return { x: bounds.left, y: bounds.top } };
return function(config) {
var lastCanvas;
var inletToConnector = {},
outletToConnector = {},
nodeToInlets = {},
nodeToOutlets = {};
return {
edgePadding: { horizontal: 20, vertical: 40 },
boxPadding: { horizontal: 20, vertical: 80 },
createCanvas: function(patch, parent) {
return {
element: d3.select(_createSvgElement('g'))
.classed('rpd-patch', true).node()
};
},
createNode: function(node, render, description, icon) {
var contentSize = render.size ? { width: render.size.width || 60,
height: render.size.height || 25 }
: { width: 60, height: 25 };
var width = contentSize.width, height = contentSize.height;
var pivot = render.pivot || { x: 0.5, y: 0.5 };
var nodeElm = d3.select(_createSvgElement('g')).attr('class', 'rpd-node');
// append node header
nodeElm.append('rect').attr('class', 'rpd-header')
.attr('x', 0).attr('y', -20)
.attr('width', 50).attr('height', 20);
if (node.def.title || node.type) {
nodeElm.append('g').attr('class', 'rpd-name-holder')
.append('text').attr('class', 'rpd-name').text(node.def.title || node.type)
.attr('x', 0).attr('y', -22)
.style('pointer-events', 'none');
}
// append node body
nodeElm.append('rect').attr('class', 'rpd-body')
.classed('rpd-drag-handle', true)
.attr('width', width).attr('height', height);
// append tooltip with description
nodeElm.select('.rpd-header')
.append(ƒ(_createSvgElement('title')))
.text(description ? (description + ' (' + node.type + ')') : node.type);
// append remove button
nodeElm.append('g').attr('class', 'rpd-remove-button')
.attr('transform', 'translate(' + (width-12) + ',-12)')
.call(function(button) {
button.append('rect').attr('width', 12).attr('height', 12)
.attr('class', 'rpd-remove-button-handle');
button.append('text').text('x').attr('x', 12).attr('y', 2)
.style('pointer-events', 'none');
});
// append placeholders for inlets, outlets and a target element to render body into
var inletsGroup = nodeElm.append('g').attr('class', 'rpd-inlets');
var processGroup = nodeElm.append('g').attr('class', 'rpd-process')
.attr('transform', 'translate(' + (width * pivot.x) + ',' + (height * pivot.y) + ')');
var outletsGroup = nodeElm.append('g').attr('class', 'rpd-outlets')
.attr('transform', 'translate(' + 0 + ',' + height + ')');
δ(inletsGroup, { position: { x: 0, y: 0 } });
δ(outletsGroup, { position: { x: 0, y: height } });
nodeElm.classed('rpd-'+node.type.slice(0, node.type.indexOf('/'))+'-toolkit-node', true)
.classed('rpd-'+node.type.replace('/','-'), true);
nodeToInlets[node.id] = [];
nodeToOutlets[node.id] = [];
return {
element: nodeElm.node(),
size: contentSize
};
},
createInlet: function(inlet, render) {
var inletElm = d3.select(_createSvgElement('g')).attr('class', 'rpd-inlet');
var nextX = socketsMargin + (nodeToInlets[inlet.node.id].length * socketPadding);
inletElm.call(function(group) {
group.attr('transform', 'translate(' + nextX + ',' + 0 + ')');
group.append('circle').attr('class', 'rpd-connector').attr('r', 3);
group.append('g').attr('class', 'rpd-value-holder')
.attr('transform', 'translate(-3,-13)')
.append('text').attr('class', 'rpd-value');
group.append('text').attr('class', 'rpd-name').text(inlet.def.label || inlet.alias)
.attr('x', -3).attr('y', -6);
});
inletToConnector[inlet.id] = inletElm.select('.rpd-connector');
nodeToInlets[inlet.node.id].push(inlet);
return { element: inletElm.node() };
},
createOutlet: function(outlet, render) {
var outletElm = d3.select(_createSvgElement('g')).attr('class', 'rpd-outlet');
var nextX = socketsMargin + (nodeToOutlets[outlet.node.id].length * socketPadding);
outletElm.call(function(group) {
group.attr('transform', 'translate(' + nextX + ',' + 0 + ')');
group.append('circle').attr('class', 'rpd-connector').attr('r', 3);
group.append('g').attr('class', 'rpd-value-holder')
.append('text').attr('class', 'rpd-value')
.attr('x', 0).attr('y', 18)
.style('pointer-events', 'none');
group.append('text').attr('class', 'rpd-name').text(outlet.def.label || outlet.alias)
.attr('x', 0).attr('y', 10);
});
outletToConnector[outlet.id] = outletElm.select('.rpd-connector');
nodeToOutlets[outlet.node.id].push(outlet);
return { element: outletElm.node() };
},
createLink: function(link) {
var linkElm = d3.select(_createSvgElement('line'))
.attr('class', 'rpd-link');
return { element: linkElm.node(),
rotate: function(x0, y0, x1, y1) {
linkElm.attr('x1', x0).attr('y1', y0)
.attr('x2', x1).attr('y2', y1);
},
noPointerEvents: function() {
linkElm.style('pointer-events', 'none');
} };
},
getInletPos: function(inlet) {
var connectorPos = getPos(inletToConnector[inlet.id].node());
return { x: connectorPos.x + 3, y: connectorPos.y + 3 };
},
getOutletPos: function(outlet) {
var connectorPos = getPos(outletToConnector[outlet.id].node());
return { x: connectorPos.x + 3, y: connectorPos.y + 3 };
},
getLocalPos: function(pos) {
if (!lastCanvas) return pos;
// calculate once on patch switch?
var canvasPos = getPos(lastCanvas.node());
return { x: pos.x - canvasPos.x, y: pos.y - canvasPos.y };
},
onPatchSwitch: function(patch, canvas) {
lastCanvas = d3.select(canvas);
}
};
} })());
})(this);