363 lines
11 KiB
JavaScript
363 lines
11 KiB
JavaScript
|
|
ElmRuntime.Render.Collage = function() {
|
|
'use strict';
|
|
|
|
var Render = ElmRuntime.use(ElmRuntime.Render.Element);
|
|
var Matrix = Elm.Matrix2D({});
|
|
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( Matrix.matrix, scale, 0, 0, scale, form.x, form.y );
|
|
|
|
var theta = form.theta
|
|
if (theta !== 0)
|
|
matrix = A2( Matrix.multiply, matrix, Matrix.rotation(theta) );
|
|
|
|
return matrix;
|
|
}
|
|
|
|
function makeTransform(w, h, form, matrices) {
|
|
var props = form.form._0.props;
|
|
var m = A6( Matrix.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( Matrix.multiply, m, matrices[i] ); }
|
|
m = A2( Matrix.multiply, m, formToMatrix(form) );
|
|
|
|
return 'matrix(' + m[0] + ',' + m[3] + ',' +
|
|
(-m[1]) + ',' + (-m[4]) + ',' +
|
|
m[2] + ',' + 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(Matrix.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 div;
|
|
}
|
|
|
|
return { render:render, update:update };
|
|
|
|
};
|