UNPKG

rpd

Version:

RPD is a minimal framework for building Node-Based User Interfaces, powered by Reactive Programming

408 lines (377 loc) 17.4 kB
;(function(global) { "use strict"; var Rpd = global.Rpd; if (typeof Rpd === "undefined" && typeof require !== "undefined") { Rpd = require('rpd'); } var ƒ = Rpd.unit; // TODO: nodes have different size depending on their content var defaultSize = { width: 50, height: 18 }; var view = new PdView(defaultSize, function() { return document.body; }); function _createSvgElement(name) { return document.createElementNS(d3.namespaces.svg, name); } Rpd.noderenderer('wpd/number', 'svg', function(node) { var path, text, handle; var size = defaultSize; var spinner; return { size: size, first: function(bodyElm) { path = d3.select(_createSvgElement('path')) .attr('d', 'M 0,0 h ' + (size.width - 5) + ' ' + 'l ' + 5 + ' ' + 5 + ' ' + 'v ' + (size.height - 5) + ' ' + 'h ' + (-size.width) + ' v ' + (-size.height) + ' z'); handle = d3.select(_createSvgElement('rect')) .classed('rpd-wpd-handle', true) .attr('x', 2).attr('y', 2) .attr('width', size.height - 2) .attr('height', Math.floor(size.width * 0.7)); text = d3.select(_createSvgElement('text')) .attr('x', 2).attr('y', size.height / 2) .text('0'); spinner = view.addSpinner(handle.node()); var changes = spinner.getChangesStream(); view.addSelection(bodyElm, node); d3.select(bodyElm).call(function(body) { body.append(ƒ(path.node())); body.append(ƒ(text.node())); body.append(ƒ(handle.node())); }); return { 'receive': { valueOut: changes.map(function(val) { return PdValue.from([ parseFloat(val) ]); }) } }; }, always: function(bodyElm, inlets) { var newVal = (inlets.receive || inlets.spinner); if (!newVal.isBang()) text.text(newVal.getByIndex(0)); } } }); Rpd.noderenderer('wpd/symbol', 'svg', function(node) { var path, editor, text; var size = defaultSize; return { size: size, first: function(bodyElm) { path = d3.select(_createSvgElement('path')) .attr('d', 'M 0,0 h ' + (size.width - 5) + ' ' + 'l ' + 5 + ' ' + 5 + ' ' + 'v ' + (size.height - 5) + ' ' + 'h ' + (-size.width) + ' v ' + (-size.height) + ' z'); text = d3.select(_createSvgElement('text')) .attr('x', 2).attr('y', size.height / 2) .text('symbol'); view.addSelection(bodyElm, node); /* view.addEditor(bodyElm, text.node()); */ d3.select(bodyElm).call(function(body) { body.append(ƒ(path.node())); body.append(ƒ(text.node())); }); } } }); Rpd.noderenderer('wpd/message', 'svg', function(node) { var path, text; var size = defaultSize; var lastValue; var initialized = false; return { size: size, first: function(bodyElm) { path = d3.select(_createSvgElement('path')) .attr('d', 'M 0,0 h ' + size.width + ' ' + 'l ' + (-4) + ' ' + 4 + ' ' + 'v ' + (size.height - 8) + ' ' + 'l ' + 4 + ' ' + 4 + ' ' + 'h ' + (-size.width) + ' v ' + (-size.height) + ' z'); text = d3.select(_createSvgElement('text')) .attr('x', 2).attr('y', size.height / 2); text.text(''); view.addSelection(bodyElm, node); //view.addResize() view.addEditor(bodyElm, text.node(), function(value) { lastValue = PdValue.extract(value); }); view.addValueSend(bodyElm, this, 'receive', function() { return lastValue.get(); }); d3.select(bodyElm).call(function(body) { body.append(ƒ(path.node())); body.append(ƒ(text.node())); }); }, always: function(bodyElm, inlets) { if (inlets.init && !initialized) { lastValue = inlets.init; text.text(lastValue.get().join(' ')); initialized = true; } else if (inlets.receive && !inlets.receive.isBang()) { lastValue = inlets.receive; } } } }); Rpd.noderenderer('wpd/comment', 'svg', function(node) { var size = defaultSize; return { size: size, first: function(bodyElm) { var rect = d3.select(_createSvgElement('rect')) .attr('width', size.width) .attr('height', size.height); var text = d3.select(_createSvgElement('text')) .attr('y', size.height / 2) .text('comment'); view.addSelection(bodyElm, node); view.addEditor(bodyElm, text.node()); d3.select(bodyElm).call(function(body) { body.append(ƒ(rect.node())); body.append(ƒ(text.node())); }); } } }); Rpd.noderenderer('wpd/object', 'svg', function(node) { var size = defaultSize; var model = node.patch.model; return { size: size, first: function(bodyElm) { var rect = d3.select(_createSvgElement('rect')) .classed('rpd-wpd-erratic', true); rect.attr('width', size.width).attr('height', size.height); var text = d3.select(_createSvgElement('text')) .attr('x', 2).attr('y', size.height / 2); view.addSelection(bodyElm, node); view.addEditor(bodyElm, text.node(), function(value) { var commandAndArgs = PdValue.extract(value).get(); model.requestResolve(node, commandAndArgs[0], commandAndArgs.slice(1)); }); var lastCommand; model.whenResolved(node, function(value) { var newCommand; if (value.command) { newCommand = value.command + (value.arguments.length ? ' ' + value.arguments.join(' ') : ''); } if (lastCommand && (newCommand === lastCommand)) return; text.text(newCommand || ''); var newSize = view.measureText(text); rect.attr('width', (newSize.width > 30) ? (newSize.width + 6) : 30); rect.classed('rpd-wpd-erratic', !value.webPdObject || !value.command); lastCommand = newCommand; return true; }); d3.select(bodyElm).call(function(body) { body.append(ƒ(rect.node())); body.append(ƒ(text.node())); }); } } }); function isToggled(val) { return (val.getByIndex(0) > 0); } function toggledOff() { return PdValue.from([0]); } function toggledOn() { return PdValue.from([1]); } function switchToggle(from) { return isToggled(from) ? toggledOff() : toggledOn(); } Rpd.noderenderer('wpd/toggle', 'svg', function(node) { var rect; var mark; var size = { width: defaultSize.height, height: defaultSize.height }; var toggle = toggledOff(); var changes = Kefir.emitter(); var initialized = false; return { size: size, first: function(bodyElm) { rect = d3.select(_createSvgElement('rect')) .attr('width', size.width).attr('height', size.height); mark = d3.select(_createSvgElement('g')) .classed('rpd-wpd-mark', true) .call(function(group) { group.append(ƒ(d3.select(_createSvgElement('line')) .attr('x1', 0).attr('y1', 0) .attr('x2', size.width).attr('y2', size.height) .node())); group.append(ƒ(d3.select(_createSvgElement('line')) .attr('x1', size.width).attr('y1', 0) .attr('x2', 0).attr('y2', size.height) .node())); }); view.addSelection(bodyElm, node); view.addValueSend(rect.node(), this, 'receive', function() { toggle = switchToggle(toggle); changes.emit(toggle); return toggle.get(); }); mark.classed('rpd-wpd-enabled', isToggled(toggle)); d3.select(bodyElm).append(ƒ(mark.node())); d3.select(bodyElm).append(ƒ(rect.node())); changes.emit(toggle); return { 'receive': { valueOut: changes.toProperty(function() { return toggledOff(); }) } }; }, always: function(bodyElm, inlets) { var incomingValue; if (inlets.init && !initialized) { incomingValue = inlets.init; initialized = true; } else if (inlets.receive) { incomingValue = inlets.receive; } if (incomingValue.isBang()) incomingValue = switchToggle(toggle); toggle = incomingValue; mark.classed('rpd-wpd-enabled', isToggled(toggle)); } } }); Rpd.noderenderer('wpd/bang', 'svg', function(node) { var rect, circle; var size = { width: defaultSize.height, height: defaultSize.height }; return { size: size, first: function(bodyElm) { rect = d3.select(_createSvgElement('rect')) .attr('width', size.width).attr('height', size.height); circle = d3.select(_createSvgElement('circle')) .attr('cx', size.width / 2).attr('cy', size.width / 2) .attr('r', size.width / 2); view.addSelection(bodyElm, node); view.addValueSend(bodyElm, this, 'receive', function() { return PdValue.bang().get(); }); d3.select(bodyElm).call(function(body) { body.append(ƒ(rect.node())); body.append(ƒ(circle.node())); }); } } }); Rpd.noderenderer('wpd/toolbar', 'svg', function(node) { var mWidth = 325, mHeight = 70; var commandToType = PdModel.COMMAND_TO_TYPE; var editModeKeyLabel = PdView.EDIT_MODE_KEY_LABEL; var nodeTypeToKeyLabel = PdView.NODE_TYPE_TO_KEY_LABEL; var names = { 'obj': 'object', 'msg': 'message', 'floatatom': 'number', 'symbolatom': 'symbol', 'text': 'comment', 'bng': 'bang', 'tgl': 'toggle', 'nbx': 'number2', 'vsl': 'vslider', 'hsl': 'hslider', 'vradio': 'vradio', 'hradio': 'hradio', 'vu': 'vumeter', 'cnv': 'canvas', 'graph': 'graph', 'array': 'array' }; return { size: { width: mWidth + 100, height: mHeight }, first: function(bodyElm) { var group = d3.select(_createSvgElement('g')) .classed('rpd-wpd-toolbar-group', true); group.append('rect').classed('rpd-wpd-toolbar-border', true) .attr('height', mHeight).attr('width', mWidth + 100) .attr('rx', 5).attr('ry', 5); // edit mode switch group.append('g').call(function(editGroup) { editGroup.attr('transform', 'translate(12, ' + (mHeight / 2) + ')') .classed('rpd-wpd-edit-mode', true); var outerCircle = d3.select(_createSvgElement('circle')).attr('r', 7); var innerCircle = d3.select(_createSvgElement('circle')).attr('r', 5); editGroup.append(ƒ(outerCircle.node())); editGroup.append(ƒ(innerCircle.node())).style('pointer-events', 'none'); var text = d3.select(_createSvgElement('text')).text('Edit mode').attr('x', 10); var keyLabel = d3.select(_createSvgElement('text')).text('(' + editModeKeyLabel + ')').attr('x', 55).attr('y', 1) .classed('rpd-wpd-key-label', true); editGroup.append(ƒ(text.node())); editGroup.append(ƒ(keyLabel.node())); view.addEditModeSwitch(outerCircle.node(), editGroup.node()); view.addEditModeSwitch(text.node(), editGroup.node()); }); // node buttons group.append('g').call(function(buttons) { buttons.attr('transform', 'translate(95, 0)') .classed('rpd-wpd-buttons', true); var bWidth = mWidth / 4, bHeight = mHeight / 4; var xPos, yPos; Object.keys(commandToType).forEach(function(command, idx) { xPos = (Math.floor(idx / 4)) * bWidth; yPos = (idx % 4) * bHeight; var nodeName = names[command]; var nodeType = commandToType[command]; var keyLabel = nodeType ? nodeTypeToKeyLabel[nodeType] : null; var button = d3.select(_createSvgElement('g')) .attr('transform', 'translate(' + xPos + ',' + yPos + ')') .classed('rpd-wpd-accessible', nodeType ? true : false); button.append('rect').attr('width', bWidth).attr('height', bHeight); button.append('text').attr('x', 10).attr('y', bHeight / 2).text(nodeName); if (keyLabel) { button.append('text').attr('x', bWidth - 3).attr('y', bHeight / 2) .text('(' + keyLabel + ')') .classed('rpd-wpd-key-label', true); } if (commandToType[command]) view.addNodeAppender(button.node(), nodeType, node.patch); buttons.append(ƒ(button.node())); }); }); d3.select(bodyElm).append(ƒ(group.node())); } }; }); Rpd.noderenderer('wpd/edit-switch', 'svg', function(node) { return { first: function(bodyElm) { var group = d3.select(_createSvgElement('g')) .classed('rpd-wpd-edit-switch-group', true); var outerCircle = d3.select(_createSvgElement('circle')).attr('r', 7); var innerCircle = d3.select(_createSvgElement('circle')).attr('r', 5); group.append(ƒ(outerCircle.node())); group.append(ƒ(innerCircle.node())).style('pointer-events', 'none'); var text = d3.select(_createSvgElement('text')).attr('x', 10).text('Edit mode'); group.append(ƒ(text.node())); view.addEditModeSwitch(outerCircle.node(), group.node()); view.addEditModeSwitch(text.node(), group.node()); d3.select(bodyElm).append(ƒ(group.node())); } } }); Rpd.noderenderer('wpd/audio-control', 'svg', function(node) { var model = node.patch.model; return { first: function(bodyElm) { var ch0switch = d3.select(_createSvgElement('text')) .classed('rpd-wpd-channel', true); Kefir.fromEvents(ch0switch.node(), 'click') .toProperty(function() { return model.isAudioChannelOn(0); }) .scan(function(prev, next) { return !prev; }) .onValue(function(val) { model.switchAudioChannel(0, val); ch0switch.classed('rpd-wpd-channel-on', val) .text('CH0:' + (val ? '✔' : '✘')); }); d3.select(bodyElm).append(ƒ(ch0switch.node())); d3.select(bodyElm).append(ƒ(d3.select(_createSvgElement('text')) .text('/').attr('x', 35).node())); var ch1switch = d3.select(_createSvgElement('text')).attr('x', 45) .classed('rpd-wpd-channel', true); Kefir.fromEvents(ch1switch.node(), 'click') .toProperty(function() { return model.isAudioChannelOn(1); }) .scan(function(prev, next) { return !prev; }) .onValue(function(val) { model.switchAudioChannel(1, val); ch1switch.classed('rpd-wpd-channel-on', val) .text('CH1:' + (val ? '✔' : '✘')); }); d3.select(bodyElm).append(ƒ(ch1switch.node())); } }; }); })(this);