rpd
Version:
RPD is a minimal framework for building Node-Based User Interfaces, powered by Reactive Programming
330 lines (281 loc) • 12.7 kB
JavaScript
function applyCodeExample1() {
Rpd.renderNext('svg', document.getElementById('example-one'),
{ style: 'compact-v' });
var patch = Rpd.addPatch('Generate Random Numbers').resizeCanvas(800, 110);
// add Metro Node, it may generate `bang` signal with the requested time interval
var metroNode = patch.addNode('util/metro', 'Metro').move(40, 10);
// add Random Generator Node that will generate random numbers on every `bang` signal
var randomGenNode = patch.addNode('util/random', 'Random').move(130, 20);
randomGenNode.inlets['max'].receive(26); // set maximum value of the generated numbers
// add Log Node, which will log last results of the Random Generator Node
var logRandomNode = patch.addNode('util/log', 'Log').move(210, 60);
randomGenNode.outlets['random'].connect(logRandomNode.inlets['what']);
// define the type of the node which multiplies the incoming value on two
var multiplyTwoNode = patch.addNode('core/basic', '* 2', {
process: function(inlets) {
return {
'result': (inlets.multiplier || 0) * 2
}
}
}).move(240, 10);
var multiplierInlet = multiplyTwoNode.addInlet('util/number', 'multiplier');
var resultOutlet = multiplyTwoNode.addOutlet('util/number', 'result');
// connect Random Generator output to the multiplying node
var logMultiplyNode = patch.addNode('util/log', 'Log').move(370, 20);
resultOutlet.connect(logMultiplyNode.inlets['what']);
// connect Random Generator output to the multiplying node
randomGenNode.outlets['random'].connect(multiplierInlet);
// finally connect Metro node to Random Generator, so the sequence starts
metroNode.outlets['bang'].connect(randomGenNode.inlets['bang']);
}
function applyCodeExample2() {
Rpd.renderNext('svg', document.getElementById('example-two'),
{ style: 'compact-v' });
var patch = Rpd.addPatch('Flag Generator').resizeCanvas(800, 200);
var metro1 = patch.addNode('util/metro').move(50, 30);
var metro2 = patch.addNode('util/metro').move(50, 90);
metro1.inlets['period'].receive(2000);
metro2.inlets['period'].receive(3000);
var random1 = patch.addNode('util/random').move(170, 10);
random1.inlets['max'].receive(26);
var random2 = patch.addNode('util/random').move(170, 120);
random2.inlets['max'].receive(26);
var letter1 = patch.addNode('util/letter').move(300, 10);
var letter2 = patch.addNode('util/letter').move(300, 110);
metro1.outlets['bang'].connect(random1.inlets['bang']);
metro2.outlets['bang'].connect(random2.inlets['bang']);
random1.outlets['random'].connect(letter1.inlets['code']);
random2.outlets['random'].connect(letter2.inlets['code']);
Rpd.nodetype('user/maybe-flag', {
title: 'May be a flag?',
inlets: {
'letterA': { type: 'core/any' },
'letterB': { type: 'core/any' }
},
outlets: {
'char': { type: 'core/any' },
'code': { type: 'core/any' }
},
process: function(inlets) {
if (!inlets.letterA || !inlets.letterB) return;
return { 'code': String.fromCharCode(inlets.letterA.charCodeAt(0) - 32) + String.fromCharCode(inlets.letterB.charCodeAt(0) - 32),
'char' : fromCodePoint(55356) + fromCodePoint(inlets.letterA.charCodeAt(0) - 97 + 56806) +
fromCodePoint(55356) + fromCodePoint(inlets.letterB.charCodeAt(0) - 97 + 56806) };
}
});
Rpd.noderenderer('user/maybe-flag', 'svg', function() {
var textElm;
return {
first: function(bodyElm) {
textElm = d3.select(bodyElm).append('text')
.style('text-anchor', 'middle');
},
always: function(bodyElm, inlets, outlets) {
if (!outlets) return;
textElm.text(outlets.char + ' (' + outlets.code + ')');
}
}
});
var maybeFlag = patch.addNode('user/maybe-flag', 'Maybe<Flag>').move(430, 70);
letter1.outlets['letter'].connect(maybeFlag.inlets['letterA']);
letter2.outlets['letter'].connect(maybeFlag.inlets['letterB']);
var logNode = patch.addNode('util/log', {}, {
'svg': {
size: { width: 210, height: 30 }
}
}).move(550, 70);
maybeFlag.outlets['char'].connect(logNode.inlets['what']);
}
function applyCodeExample3() {
/* ============== Coordinates Channel Type ============== */
Rpd.channeltype('my/coords', {
show: function(val) {
// nicely show a received pair of coordinates, floored to an integer
return '<' + Math.floor(val.x) + ':' + Math.floor(val.y) + '>';
}
});
/* ============== Coordinates Node Type ============== */
Rpd.nodetype('my/coords', {
inlets: {
x: { type: 'util/number', default: 0 },
y: { type: 'util/number', default: 0 }
},
outlets: {
coords: { type: 'my/coords' }
},
// joins received `x` and `y` into one object
process: function(inlets) {
return { coords: { x: inlets.x, y: inlets.y } };
}
});
//* ============== Angle (radians) Channel Type ============== */
Rpd.channeltype('my/angle', {
allow: [ 'util/number '], // outlets of `util/number` type are allowed to be
// connected to inlets of `my/angle` type
accept: function(v) { return (v >= 0) && (v <= 360); },
show: function(v) { return v + '˚'; }
});
/* ============== Canvas-driven Scene Node Type ============== */
var defaultConfig = {
count: 7,
from: { r: 0, g: 0, b: 0 },
to: { r: 255, g: 0, b: 0 },
shift: { x: 25, y: 0 },
rotate: 15
};
Rpd.nodetype('my/scene', {
inlets: {
from: { type: 'util/color', 'default': defaultConfig.from },
to: { type: 'util/color', 'default': defaultConfig.to },
count: { type: 'util/number', 'default': defaultConfig.count,
adapt: function(v) { return Math.floor(v); } },
shift: { type: 'my/coords', 'default': defaultConfig.shift },
rotate: { type: 'my/angle', 'default': defaultConfig.rotate },
},
process: function() {}
});
/* ============== Renderer for Canvas-driven Scene ============== */
var SVG_XMLNS = 'http://www.w3.org/2000/svg';
function lerp(v1, v2, pos) {
return (v1 + ((v2 - v1) * pos));
}
Rpd.noderenderer('my/scene', 'svg', function() {
var width = 100, height = 100;
var context;
var particles = [];
var lastCount = 0;
var config = defaultConfig;
// function to render current state of the scene using requestAnimationFrame
function draw() {
if (context) {
context.save();
context.fillStyle = '#fff';
context.fillRect(0, 0, width, height);
context.fillStyle = '#000';
particles.forEach(function(particle, i) {
context.fillStyle = 'rgb(' +
Math.floor(lerp(config.from.r, config.to.r, 1 / (particles.length - 1) * i)) + ',' +
Math.floor(lerp(config.from.g, config.to.g, 1 / (particles.length - 1) * i)) + ',' +
Math.floor(lerp(config.from.b, config.to.b, 1 / (particles.length - 1) * i)) + ')';
context.fillRect(0, 0, 15, 15);
context.translate(config.shift.x, config.shift.y);
context.rotate(config.rotate * Math.PI / 180);
});
context.restore();
}
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
// return actual renderer definition
return {
size: { width: width + 10, height: height + 10 },
pivot: { x: 0, y: 0 },
// on creation, add canvas to the node body
first: function(bodyElm) {
var group = document.createElementNS(SVG_XMLNS, 'g');
group.setAttributeNS(null, 'transform', 'translate(5, 5)');
var foreign = document.createElementNS(SVG_XMLNS, 'foreignObject');
canvas = document.createElement('canvas');
canvas.setAttributeNS(null, 'width', width + 'px');
canvas.setAttributeNS(null, 'height', height + 'px');
canvas.style.position = 'fixed';
foreign.appendChild(canvas);
group.appendChild(foreign);
bodyElm.appendChild(group);
context = canvas.getContext('2d');
},
// update config values using values from inlets
always: function(bodyElm, inlets) {
if (!isNaN(inlets.count) && (inlets.count != lastCount)) {
particles = [];
for (var i = 0; i < inlets.count; i++) {
particles.push({});
}
lastCount = inlets.count;
}
if (inlets.from) config.from = inlets.from;
if (inlets.to) config.to = inlets.to;
if (inlets.shift) config.shift = inlets.shift;
if (!isNaN(inlets.rotate)) config.rotate = inlets.rotate;
}
};
});
/* ============== Patch Structure ============== */
Rpd.renderNext('svg', document.getElementById('example-three'),
{ style: 'compact-v' });
var patch = Rpd.addPatch('Generate Canvas Shapes').resizeCanvas(800, 205);
var scene = patch.addNode('my/scene').move(570, 5);
var color1 = patch.addNode('util/color').move(120, 5);
var color2 = patch.addNode('util/color').move(100, 80);
var coords = patch.addNode('my/coords').move(305, 90);
var knob1 = patch.addNode('util/knob').move(25, 5);
var knob2 = patch.addNode('util/knob').move(490, 110);
var knob3 = patch.addNode('util/knob').move(210, 105);
var knob4 = patch.addNode('util/knob').move(400, 110);
var mouse = patch.addNode('util/mouse-pos').move(0, 70);
var modulus = patch.addNode('util/mod').move(20, 150);
var comment = patch.addNode('util/comment').move(80, 100);
knob1.inlets['max'].receive(256);
knob2.inlets['max'].receive(180);
knob4.inlets['max'].receive(15);
coords.inlets['x'].receive(25);
modulus.inlets['b'].receive(256);
comment.inlets['text'].receive('Try to connect "%" node output to inlet of "my/coords" node or one of the "color" nodes')
knob1.outlets['number'].connect(color1.inlets['r']);
knob3.outlets['number'].connect(coords.inlets['y']);
color1.outlets['color'].connect(scene.inlets['from']);
color2.outlets['color'].connect(scene.inlets['to']);
coords.outlets['coords'].connect(scene.inlets['shift']);
mouse.outlets['x'].connect(modulus.inlets['a']);
}
applyCodeExample1();
applyCodeExample2();
applyCodeExample3();
var logoPatchAdded = false;
document.getElementById('planets').addEventListener('click', function() {
if (logoPatchAdded) return;
logoPatchAdded = true;
applyRpdLogoPatch(document.getElementById('rpd-logo'),
document.getElementById('planets'),
document.getElementById('patch-target'));
});
function fromCodePoint(_) {
var stringFromCharCode = String.fromCharCode;
var floor = Math.floor;
// https://github.com/mathiasbynens/String.fromCodePoint/blob/master/fromcodepoint.js
var MAX_SIZE = 0x4000;
var codeUnits = [];
var highSurrogate;
var lowSurrogate;
var index = -1;
var length = arguments.length;
if (!length) {
return '';
}
var result = '';
while (++index < length) {
var codePoint = Number(arguments[index]);
if (
!isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity`
codePoint < 0 || // not a valid Unicode code point
codePoint > 0x10FFFF || // not a valid Unicode code point
floor(codePoint) != codePoint // not an integer
) {
throw RangeError('Invalid code point: ' + codePoint);
}
if (codePoint <= 0xFFFF) { // BMP code point
codeUnits.push(codePoint);
} else { // Astral code point; split in surrogate halves
// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
codePoint -= 0x10000;
highSurrogate = (codePoint >> 10) + 0xD800;
lowSurrogate = (codePoint % 0x400) + 0xDC00;
codeUnits.push(highSurrogate, lowSurrogate);
}
if (index + 1 == length || codeUnits.length > MAX_SIZE) {
result += stringFromCharCode.apply(null, codeUnits);
codeUnits.length = 0;
}
}
return result;
}