Last active
August 29, 2015 14:18
-
-
Save yelouafi/1462cc9da8ab70072051 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*************************************************/ | |
/************* 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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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