Skip to content

Instantly share code, notes, and snippets.

@yelouafi yelouafi/frp-toy.js
Last active Aug 29, 2015

Embed
What would you like to do?
/*************************************************/
/************* EventStream ***********************/
/*************************************************/
function EventStream(binder) {
this.binder = binder;
this.reset();
}
EventStream.prototype.reset = function() {
this.lastTime = 0;
this.times = [];
this.events = [];
}
EventStream.prototype.activate = function() {
var me = this;
this.off = binder(function(ev) {
me.lastTime = Date.now();
me.times.push(me.lastTime);
me.events.push(ev);
});
}
EventStream.prototype.deactivate = function() {
this.off();
this.reset();
}
EventStream.prototype.map = function(f) {
var parent = this,
str = new EventStream();
str.before = function(t) {
var parOcc = parent.before(t);
return parOcc ? f(parOcc) : null;
}
return str;
}
EventStream.prototype.before = function(t) {
if(t <= this.times[0])
return null;
else {
var idx = this.searchEv(t, 0, this.times.length-1);
return { time: this.times[idx], data: this.events[idx] };
}
}
EventStream.prototype.searchEv = function(t, start, end) {
if(end-start < 2)
return start;
var mid = start + Math.floor((end-start)/2);
return (t <= this.times[mid]) ? this.searchEv(start, mid) : this.searchEv(mid, end);
}
/*************************************************/
/************* Behavior **************************/
/*************************************************/
function curry(fn) {
var len = fn.length, ctx;
return makeLambda(0, []);
function makeLambda(i, args, ctx) {
return i < len ?
function(a) {
var newCtx = (this && this !== window) ? this : ctx,
newArgs = Array.prototype.slice.call(arguments);
return makeLambda(i+newArgs.length, args.concat(newArgs), newCtx);
} :
fn.apply(ctx, args)
}
}
function toRadians (angle) {
return angle * (Math.PI / 180);
}
function Behavior(getter) {
this.getter = getter;
}
Behavior.prototype.at = function(t) {
return this.getter(t);
}
Behavior.prototype.ap = function(xb) {
return new Behavior( function(t) {
return b.at(t)( xb.at(t) );
});
}
Behavior.prototype.until = function until(evStream) {
var me = this;
return new Behavior(function(t) {
var occ = evStream.before(t);
return occ ? occ.data.at(t) : me.at(t);
});
}
function B(getter) {
return new Behavior(getter);
}
B.const = function(x) {
return B(function() { return x; });
}
B.tmap = function(b, tb) {
return B(function(t) {
return b.at( tb.at(t) );
});
}
B.lift0 = function(a) {
return (a instanceof Behavior) ? a : B.const(a);
};
B.lift = function(fn) {
var me = this;
fn = curry(fn);
return function(a,b,c,d) {
switch (arguments.length) {
case 0: return lift0(fn);
case 1: return lift0(fn).ap(a);
case 2: return lift0(fn).ap(a).ap(b);
case 3: return lift0(fn).ap(a).ap(b).ap(c);
case 4: return lift0(fn).ap(a).ap(b).ap(c).ap(d);
}
}
}
B.liftO = function(obj) {
var res = {},
keys = Object.keys(obj);
for(var i = 0, len = keys.length; i < len; i++) {
var key = keys[i];
res[key] = this.lift0( obj[key] );
}
return res;
}
B.timeB = B(function(t) { return t; });
B.sin = function(deg) {
return Math.sin( toRadians(deg) )
}
B.cos = function(deg) {
return Math.cos( toRadians(deg) )
}
/*************************************************/
/************* Toy *******************************/
/*************************************************/
function Toy( canvas, graphics ) {
this.time = B.time;
this.canvas = canvas instanceof Element ? canvas : document.getElementById(canvas);
this.ctx = this.canvas.getContext('2d');;
this.eventMap = {};
this.graphics = new graphics();
}
Toy.prototype.on = function(ev) {
var canvas = this.canvas;
return this.eventMap[event] || (this.eventMap[event] = new EventStream(bindEvStrem(event)))
function bindEvStrem(ev) {
return function(cb) {
canvas.addEventListener(ev, cb);
}
}
}
Toy.prototype.start = function() {
var me = this,
ctx = this.ctx;
me.stopped = false;
me.startTime = Date.now();
update();
function update() {
var t = ( Date.now() - me.startTime );
ctx.globalCompositeOperation = 'destination-over';
ctx.clearRect(0,0, me.canvas.width , me.canvas.height);
ctx.fillStyle = '#FFFFFF';
ctx.strokeStyle = '#000000';
var keys = Object.keys(me.graphics);
for(var i = 0, len = keys.length; i < len; i++) {
var g = me.graphics[keys[i]];
g.render(me.ctx, t);
}
if(!me.stopped)
requestAnimationFrame(update);
}
}
Toy.prototype.stop = function() {
this.stopped = true;
var keys = Object.keys(this.eventMap);
for(var i = 0, len = keys.length; i < len; i++) {
var key = keys[i];
this.eventMap[key].reset();
}
}
function graphicB(cfg) {
return new GraphicB( B.liftO(cfg) );
}
var G = {};
G.rectB = function(cfg) {
cfg.type = 'r';
return graphicB( cfg );
}
G.circleB = function(cfg) {
cfg.type = 'c';
return graphicB( cfg );
}
G.textB = function(cfg) {
cfg.type = 't';
cfg.fill = cfg.fill || 'black';
cfg.stroke = cfg.stroke || 'black';
return graphicB( cfg );
}
G.pathB = function(cfg) {
cfg.type = 'path';
return graphicB( cfg );
}
function GraphicB(config) {
this.config = config;
}
GraphicB.prototype.bigger = function(scale) {
var cfg = Object.create(this.config);
cfg.type = 'bigger';
cfg.graphic = this;
cfg.scale = scale;
return graphicB( cfg );
}
GraphicB.prototype.rotate = function(angle, origin) {
var cfg = Object.create(this.config);
cfg.type = 'rotate';
cfg.graphic = this;
cfg.angle = angle;
cfg.origin = origin;
return graphicB( cfg );
}
GraphicB.prototype.render = function(ctx, t) {
var cfg = this.config,
type = cfg.type.at(t),
x = cfg.x ? cfg.x.at(t) : 0,
y = cfg.y ? cfg.y.at(t) : 0;
ctx.save();
cfg.stroke && (ctx.strokeStyle = cfg.stroke.at(t));
cfg.fill && (ctx.fillStyle = cfg.fill.at(t));
if(type == 'r') {
var width = cfg.width ? cfg.width.at(t) : 50,
height = cfg.height ? cfg.height.at(t) : 50;
cfg.fill && ctx.fillRect(x, y, width, height);
cfg.stroke && ctx.strokeRect(x, y, width, height);
} else if(type == 'c') {
var r = cfg.radius ? cfg.radius.at(t) : 50;
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
cfg.fill && ctx.fill();
cfg.stroke && ctx.stroke();
} else if (type == 'p') {
var path = new Path2D( cfg.path.at(t) );
cfg.fill && ctx.fill(path);
cfg.stroke && ctx.stroke(path);
} else if(type == 't') {
var text = cfg.text.at(t),
w = cfg.maxWidth ? cfg.x.maxWidth(t) : 100;
cfg.font && (ctx.font = cfg.font.at(t));
ctx.fillText(text, x, y, w);
ctx.strokeText(text, x, y, w);
} else if(type == 'bigger') {
var graphic = cfg.graphic.at(t),
scale = cfg.scale.at(t)
ctx.scale(scale, scale);
graphic.render(ctx, t);
}
else if(type == 'rotate') {
var graphic = cfg.graphic.at(t),
angle = cfg.angle.at(t),
origin = cfg.origin ? cfg.origin.at(t) : null;
if(origin)
ctx.translate(origin[0], origin[1]);
ctx.rotate(angle);
graphic.render(ctx, t);
}
ctx.restore();
}
<!doctype html>
<html>
<head>
<script src="cfrp.js"></script>
</head>
<body>
<p>
<button onclick="toy.start();">Start</button>
<button onclick="toy.stop();">Stop</button>
</p>
<canvas id="myCanvas" width="700" height="600" style="border:1px solid #000000;"></canvas>
<script>
function wiggleRange(low, high) {
return B(function(t) {
var wiggle = B.sin(t);
return low + (high - low) * (wiggle+1)/2;
})
}
var toy = new Toy('myCanvas', function() {
var cx = 350, cy = 300
var pBall = G.circleB({x: cx, y: cy, fill: 'red', radius: wiggleRange(0,100)});
var rotation = B(function(t) {
return ((2*Math.PI)/6) * t/1000 + ((2*Math.PI)/6000)*t
})
this.rBall = pBall.bigger(0.2).rotate( rotation, [cx, cy] );
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.