dcbe2fc245
Currently, it's only possible to create modules at the leafs of the namespace structure. This CL lifts that restriction. Get rid of "use strict" in many cases.
367 lines
11 KiB
JavaScript
367 lines
11 KiB
JavaScript
|
|
ElmRuntime.Render.Collage = function() {
|
|
|
|
var Render = ElmRuntime.use(ElmRuntime.Render.Element);
|
|
var Transform = Elm.Transform2D.make({});
|
|
var Utils = ElmRuntime.use(ElmRuntime.Render.Utils);
|
|
var newElement = Utils.newElement,
|
|
extract = Utils.extract, fromList = Utils.fromList,
|
|
fromString = Utils.fromString, addTransform = Utils.addTransform;
|
|
|
|
function trace(ctx, path) {
|
|
var points = fromList(path);
|
|
var i = points.length - 1;
|
|
if (i <= 0) return;
|
|
ctx.moveTo(points[i]._0, points[i]._1);
|
|
while (i--) { ctx.lineTo(points[i]._0, points[i]._1); }
|
|
if (path.closed) {
|
|
i = points.length - 1;
|
|
ctx.lineTo(points[i]._0, points[i]._1);
|
|
}
|
|
}
|
|
|
|
function line(ctx,style,path) {
|
|
style.dashing.ctor === 'Nil' ? trace(ctx, path) : customLineHelp(ctx, style, path);
|
|
ctx.scale(1,-1);
|
|
ctx.stroke();
|
|
}
|
|
|
|
function customLineHelp(ctx, style, path) {
|
|
var points = fromList(path);
|
|
if (path.closed) points.push(points[0]);
|
|
var pattern = fromList(style.dashing);
|
|
var i = points.length - 1;
|
|
if (i <= 0) return;
|
|
var x0 = points[i]._0, y0 = points[i]._1;
|
|
var x1=0, y1=0, dx=0, dy=0, remaining=0, nx=0, ny=0;
|
|
var pindex = 0, plen = pattern.length;
|
|
var draw = true, segmentLength = pattern[0];
|
|
ctx.moveTo(x0,y0);
|
|
while (i--) {
|
|
x1 = points[i]._0; y1 = points[i]._1;
|
|
dx = x1 - x0; dy = y1 - y0;
|
|
remaining = Math.sqrt(dx * dx + dy * dy);
|
|
while (segmentLength <= remaining) {
|
|
x0 += dx * segmentLength / remaining;
|
|
y0 += dy * segmentLength / remaining;
|
|
ctx[draw ? 'lineTo' : 'moveTo'](x0, y0);
|
|
// update starting position
|
|
dx = x1 - x0; dy = y1 - y0;
|
|
remaining = Math.sqrt(dx * dx + dy * dy);
|
|
// update pattern
|
|
draw = !draw;
|
|
pindex = (pindex + 1) % plen;
|
|
segmentLength = pattern[pindex];
|
|
}
|
|
if (remaining > 0) {
|
|
ctx[draw ? 'lineTo' : 'moveTo'](x1, y1);
|
|
segmentLength -= remaining;
|
|
}
|
|
x0 = x1; y0 = y1;
|
|
}
|
|
}
|
|
|
|
function drawLine(ctx, style, path) {
|
|
ctx.lineWidth = style.width;
|
|
var cap = style.cap.ctor;
|
|
ctx.lineCap = cap === 'Flat' ? 'butt' :
|
|
cap === 'Round' ? 'round' : 'square';
|
|
var join = style.join.ctor;
|
|
ctx.lineJoin = join === 'Smooth' ? 'round' :
|
|
join === 'Sharp' ? 'miter' : 'bevel';
|
|
ctx.miterLimit = style.join._0 || 10;
|
|
ctx.strokeStyle = extract(style.color);
|
|
return line(ctx, style, path);
|
|
}
|
|
|
|
function texture(redo, ctx, src) {
|
|
var img = new Image();
|
|
img.src = fromString(src);
|
|
img.onload = redo;
|
|
return ctx.createPattern(img, 'repeat');
|
|
}
|
|
|
|
function gradient(ctx, grad) {
|
|
var g;
|
|
var stops = [];
|
|
if (grad.ctor === 'Linear') {
|
|
var p0 = grad._0, p1 = grad._1;
|
|
g = ctx.createLinearGradient(p0._0, -p0._1, p1._0, -p1._1);
|
|
stops = fromList(grad._2);
|
|
} else {
|
|
var p0 = grad._0, p2 = grad._2;
|
|
g = ctx.createRadialGradient(p0._0, -p0._1, grad._1, p2._0, -p2._1, grad._3);
|
|
stops = fromList(grad._4);
|
|
}
|
|
var len = stops.length;
|
|
for (var i = 0; i < len; ++i) {
|
|
var stop = stops[i];
|
|
g.addColorStop(stop._0, extract(stop._1));
|
|
}
|
|
return g;
|
|
}
|
|
|
|
function drawShape(redo, ctx, style, path) {
|
|
trace(ctx, path);
|
|
var sty = style.ctor;
|
|
ctx.fillStyle =
|
|
sty === 'Solid' ? extract(style._0) :
|
|
sty === 'Texture' ? texture(redo, ctx, style._0) : gradient(ctx, style._0);
|
|
ctx.scale(1,-1);
|
|
ctx.fill();
|
|
}
|
|
|
|
function drawImage(redo, ctx, form) {
|
|
var img = new Image();
|
|
img.onload = redo;
|
|
img.src = fromString(form._3);
|
|
var w = form._0,
|
|
h = form._1,
|
|
pos = form._2,
|
|
srcX = pos._0,
|
|
srcY = pos._1,
|
|
srcW = w,
|
|
srcH = h,
|
|
destX = -w/2,
|
|
destY = -h/2,
|
|
destW = w,
|
|
destH = h;
|
|
|
|
ctx.scale(1,-1);
|
|
ctx.drawImage(img, srcX, srcY, srcW, srcH, destX, destY, destW, destH);
|
|
}
|
|
|
|
function renderForm(redo, ctx, form) {
|
|
ctx.save();
|
|
var x = form.x, y = form.y, theta = form.theta, scale = form.scale;
|
|
if (x !== 0 || y !== 0) ctx.translate(x, y);
|
|
if (theta !== 0) ctx.rotate(theta);
|
|
if (scale !== 1) ctx.scale(scale,scale);
|
|
if (form.alpha !== 1) ctx.globalAlpha = ctx.globalAlpha * form.alpha;
|
|
ctx.beginPath();
|
|
var f = form.form;
|
|
switch(f.ctor) {
|
|
case 'FPath' : drawLine(ctx, f._0, f._1); break;
|
|
case 'FImage': drawImage(redo, ctx, f); break;
|
|
case 'FShape':
|
|
if (f._0.ctor === 'Left') {
|
|
f._1.closed = true;
|
|
drawLine(ctx, f._0._0, f._1);
|
|
} else {
|
|
drawShape(redo, ctx, f._0._0, f._1);
|
|
}
|
|
break;
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function formToMatrix(form) {
|
|
var scale = form.scale;
|
|
var matrix = A6( Transform.matrix, scale, 0, 0, scale, form.x, form.y );
|
|
|
|
var theta = form.theta
|
|
if (theta !== 0)
|
|
matrix = A2( Transform.multiply, matrix, Transform.rotation(theta) );
|
|
|
|
return matrix;
|
|
}
|
|
|
|
function str(n) {
|
|
if (n < 0.00001 && n > -0.00001) return 0;
|
|
return n;
|
|
}
|
|
|
|
function makeTransform(w, h, form, matrices) {
|
|
var props = form.form._0.props;
|
|
var m = A6( Transform.matrix, 1, 0, 0, 1,
|
|
(w - props.width)/2,
|
|
(h - props.height)/2 );
|
|
var len = matrices.length;
|
|
for (var i = 0; i < len; ++i) { m = A2( Transform.multiply, m, matrices[i] ); }
|
|
m = A2( Transform.multiply, m, formToMatrix(form) );
|
|
|
|
return 'matrix(' + str( m[0]) + ',' + str( m[3]) + ',' +
|
|
str(-m[1]) + ',' + str(-m[4]) + ',' +
|
|
str( m[2]) + ',' + str( m[5]) + ')';
|
|
}
|
|
|
|
function stepperHelp(list) {
|
|
var arr = fromList(list);
|
|
var i = 0;
|
|
function peekNext() {
|
|
return i < arr.length ? arr[i].form.ctor : '';
|
|
}
|
|
// assumes that there is a next element
|
|
function next() {
|
|
var out = arr[i];
|
|
++i;
|
|
return out;
|
|
}
|
|
return { peekNext:peekNext, next:next };
|
|
}
|
|
|
|
function stepper(forms) {
|
|
var ps = [stepperHelp(forms)];
|
|
var matrices = [];
|
|
var alphas = [];
|
|
function peekNext() {
|
|
var len = ps.length;
|
|
var formType = '';
|
|
for (var i = 0; i < len; ++i ) {
|
|
if (formType = ps[i].peekNext()) return formType;
|
|
}
|
|
return '';
|
|
}
|
|
// assumes that there is a next element
|
|
function next(ctx) {
|
|
while (!ps[0].peekNext()) {
|
|
ps.shift();
|
|
matrices.pop();
|
|
alphas.shift();
|
|
if (ctx) { ctx.restore(); }
|
|
}
|
|
var out = ps[0].next();
|
|
var f = out.form;
|
|
if (f.ctor === 'FGroup') {
|
|
ps.unshift(stepperHelp(f._1));
|
|
var m = A2(Transform.multiply, f._0, formToMatrix(out));
|
|
ctx.save();
|
|
ctx.transform(m[0], m[3], m[1], m[4], m[2], m[5]);
|
|
matrices.push(m);
|
|
|
|
var alpha = (alphas[0] || 1) * out.alpha;
|
|
alphas.unshift(alpha);
|
|
ctx.globalAlpha = alpha;
|
|
}
|
|
return out;
|
|
}
|
|
function transforms() { return matrices; }
|
|
function alpha() { return alphas[0] || 1; }
|
|
return { peekNext:peekNext, next:next, transforms:transforms, alpha:alpha };
|
|
}
|
|
|
|
function makeCanvas(w,h) {
|
|
var canvas = newElement('canvas');
|
|
canvas.style.width = w + 'px';
|
|
canvas.style.height = h + 'px';
|
|
canvas.style.display = "block";
|
|
canvas.style.position = "absolute";
|
|
canvas.width = w;
|
|
canvas.height = h;
|
|
return canvas;
|
|
}
|
|
|
|
function render(model) {
|
|
var div = newElement('div');
|
|
update(div, model, model);
|
|
return div;
|
|
}
|
|
|
|
function updateTracker(w,h,div) {
|
|
var kids = div.childNodes;
|
|
var i = 0;
|
|
function transform(transforms, ctx) {
|
|
ctx.translate(w/2, h/2);
|
|
ctx.scale(1,-1);
|
|
var len = transforms.length;
|
|
for (var i = 0; i < len; ++i) {
|
|
var m = transforms[i];
|
|
ctx.save();
|
|
ctx.transform(m[0], m[3], m[1], m[4], m[2], m[5]);
|
|
}
|
|
return ctx;
|
|
}
|
|
function getContext(transforms) {
|
|
while (i < kids.length) {
|
|
var node = kids[i];
|
|
if (node.getContext) {
|
|
node.width = w;
|
|
node.height = h;
|
|
node.style.width = w + 'px';
|
|
node.style.height = h + 'px';
|
|
++i;
|
|
return transform(transforms, node.getContext('2d'));
|
|
}
|
|
div.removeChild(node);
|
|
}
|
|
var canvas = makeCanvas(w,h);
|
|
div.appendChild(canvas);
|
|
// we have added a new node, so we must step our position
|
|
++i;
|
|
return transform(transforms, canvas.getContext('2d'));
|
|
}
|
|
function element(matrices, alpha, form) {
|
|
var container = kids[i];
|
|
if (!container || container.getContext) {
|
|
container = newElement('div');
|
|
container.style.overflow = 'hidden';
|
|
container.style.position = 'absolute';
|
|
addTransform(container.style, 'scaleY(-1)');
|
|
|
|
var kid = kids[i];
|
|
kid ? div.insertBefore(container, kid)
|
|
: div.appendChild(container);
|
|
}
|
|
// we have added a new node, so we must step our position
|
|
++i;
|
|
|
|
container.style.width = w + 'px';
|
|
container.style.height = h + 'px';
|
|
container.style.opacity = alpha * form.alpha;
|
|
|
|
var elem = form.form._0;
|
|
var node = container.firstChild;
|
|
if (node) {
|
|
Render.update(node, node.oldElement, elem);
|
|
node = container.firstChild;
|
|
} else {
|
|
node = Render.render(elem);
|
|
container.appendChild(node);
|
|
}
|
|
node.oldElement = elem;
|
|
addTransform(node.style, makeTransform(w, h, form, matrices));
|
|
}
|
|
function clearRest() {
|
|
while (i < kids.length) {
|
|
div.removeChild(kids[i]);
|
|
}
|
|
}
|
|
return { getContext:getContext, element:element, clearRest:clearRest };
|
|
}
|
|
|
|
|
|
function update(div, _, model) {
|
|
var w = model.w;
|
|
var h = model.h;
|
|
div.style.width = w + 'px';
|
|
div.style.height = h + 'px';
|
|
if (model.forms.ctor === 'Nil') {
|
|
while (div.hasChildNodes()) {
|
|
div.removeChild(div.lastChild);
|
|
}
|
|
}
|
|
var stpr = stepper(model.forms);
|
|
var tracker = updateTracker(w,h,div);
|
|
var ctx = null;
|
|
var formType = '';
|
|
|
|
while (formType = stpr.peekNext()) {
|
|
if (ctx === null && formType !== 'FElement') {
|
|
ctx = tracker.getContext(stpr.transforms());
|
|
ctx.globalAlpha = stpr.alpha();
|
|
}
|
|
var form = stpr.next(ctx);
|
|
if (formType === 'FElement') {
|
|
tracker.element(stpr.transforms(), stpr.alpha(), form);
|
|
ctx = null;
|
|
} else if (formType !== 'FGroup') {
|
|
renderForm(function() { update(div, model, model); }, ctx, form);
|
|
}
|
|
}
|
|
tracker.clearRest();
|
|
return false;
|
|
}
|
|
|
|
return { render:render, update:update };
|
|
|
|
};
|