@@ -3,8 +3,9 @@ ElmRuntime.Render.Collage = function() {
33'use strict';
44
55var Render = ElmRuntime.use(ElmRuntime.Render.Element);
6+var Matrix = Elm.Matrix2D({});
67var Utils = ElmRuntime.use(ElmRuntime.Render.Utils);
7−var newElement = Utils.newElement, addTo = Utils.addTo,
8+var newElement = Utils.newElement,
89 extract = Utils.extract, fromList = Utils.fromList,
910 fromString = Utils.fromString, addTransform = Utils.addTransform;
1011
@@ -79,20 +80,23 @@ function texture(redo, ctx, src) {
7980}
8081
8182function gradient(ctx, grad) {
82− var g, p1, p2;
83− if (grad.ctor === 'Linear') {
84− p1 = grad._1; p2 = grad._2;
85− g = ctx.createLinearGradient(p1._0, p1._1, p2._0, p2._1);
86− } else {
87− p1 = grad._1; p2 = grad._3;
88− g = ctx.createRadialGradient(p1._0, p1._1, grad._2, p2._0, p2._1, grad._4);
89− }
90− var stops = fromList(grad._0);
91− for (var i = stops.length; i--; ) {
92− var stop = stops[i];
93− g.addColorStop(stop._0, extract(stop._1));
94− }
95− return g;
83+ var g;
84+ var stops = [];
85+ if (grad.ctor === 'Linear') {
86+ var p0 = grad._0, p1 = grad._1;
87+ g = ctx.createLinearGradient(p0._0, -p0._1, p1._0, -p1._1);
88+ stops = fromList(grad._2);
89+ } else {
90+ var p0 = grad._0, p2 = grad._2;
91+ g = ctx.createRadialGradient(p0._0, -p0._1, grad._1, p2._0, -p2._1, grad._3);
92+ stops = fromList(grad._4);
93+ }
94+ var len = stops.length;
95+ for (var i = 0; i < len; ++i) {
96+ var stop = stops[i];
97+ g.addColorStop(stop._0, extract(stop._1));
98+ }
99+ return g;
96100}
97101
98102function drawShape(redo, ctx, style, path) {
@@ -123,7 +127,7 @@ function drawImage(redo, ctx, form) {
123127 ctx.drawImage(img, srcX, srcY, srcW, srcH, destX, destY, destW, destH);
124128}
125129
126−function renderForm(stack, redo, ctx, form) {
130+function renderForm(redo, ctx, form) {
127131 ctx.save();
128132 var x = form.x, y = form.y, theta = form.theta, scale = form.scale;
129133 if (x !== 0 || y !== 0) ctx.translate(x, -y);
@@ -132,94 +136,194 @@ function renderForm(stack, redo, ctx, form) {
132136 ctx.beginPath();
133137 var f = form.form;
134138 switch(f.ctor) {
135− case 'FPath' : drawLine(ctx, f._0, f._1); break;
136− case 'FShape':
137− if (f._0.ctor === 'Left') {
138− f._1.closed = true;
139− drawLine(ctx, f._0._0, f._1);
140− } else {
141− drawShape(redo, ctx, f._0._0, f._1);
142− }
143− break;
144− case 'FImage': drawImage(redo, ctx, f); break;
145− case 'FGroup':
146− ctx.save();
147− var m = f._0;
148− // stack.push(makeTransform(x, y, theta, scale, m));
149− ctx.transform(m[0], m[3], m[1], m[4], m[2], m[5]);
150− renderForms(stack, redo, ctx, f._1);
151− // stack.pop();
152− ctx.restore();
153− break;
139+ case 'FPath' : drawLine(ctx, f._0, f._1); break;
140+ case 'FImage': drawImage(redo, ctx, f); break;
141+ case 'FShape':
142+ if (f._0.ctor === 'Left') {
143+ f._1.closed = true;
144+ drawLine(ctx, f._0._0, f._1);
145+ } else {
146+ drawShape(redo, ctx, f._0._0, f._1);
147+ }
148+ break;
154149 }
155150 ctx.restore();
156151}
157152
158−function makeTransform(x, y, theta, scale, matrix) {
159− return function(m) {
160− if (x !== 0 || y !== 0) m = A3( Matrix.move, x, y, m );
161− if (theta !== 0) m = A2( Matrix.rotate, theta, m );
162− if (scale !== 1) m = Matrix.scale( scale );
163− return Matrix.multiply( m, matrix );
164− };
153+function makeTransform(w, h, form, matrices) {
154+ var props = form.form._0.props;
155+ var m = A6( Matrix.matrix, 1, 0, 0, 1,
156+ (w - props.width)/2,
157+ (props.height - h)/2 );
158+ var len = matrices.length;
159+ for (var i = 0; i < len; ++i) { m = A2( Matrix.multiply, m, matrices[i] ); }
160+
161+ var x = form.x, y = form.y, theta = form.theta, scale = form.scale;
162+ if (x !== 0 || y !== 0) m = A3( Matrix.move, x, y, m );
163+ if (theta !== 0) m = A2( Matrix.rotate, theta, m );
164+ if (scale !== 1) m = Matrix.scale( scale );
165+ return 'matrix(' + m[0] + ',' + m[3] + ',' + m[1] + ',' +
166+ m[4] + ',' + m[2] + ',' + (-m[5]) + ')';
167+}
168+
169+function stepperHelp(list) {
170+ var arr = fromList(list);
171+ var i = 0;
172+ function peekNext() {
173+ return i < arr.length ? arr[i].form.ctor : '';
174+ }
175+ // assumes that there is a next element
176+ function next() {
177+ var out = arr[i];
178+ ++i;
179+ return out;
180+ }
181+ return { peekNext:peekNext, next:next };
165182}
166183
167−function renderForms(stack, redo, ctx, forms) {
168− var fs = fromList(forms);
169− for (var i = 0, len = fs.length; i < len; ++i) {
170− renderForm(stack, redo, ctx, fs[i]);
184+function stepper(forms) {
185+ var ps = [stepperHelp(forms)];
186+ var matrices = [];
187+ function peekNext() {
188+ var len = ps.length;
189+ var formType = '';
190+ for (var i = 0; i < len; ++i ) {
191+ if (formType = ps[i].peekNext()) return formType;
192+ }
193+ return '';
194+ }
195+ // assumes that there is a next element
196+ function next(ctx) {
197+ while (!ps[0].peekNext()) { ps.shift(); matrices.pop(); ctx.restore(); }
198+ var out = ps[0].next();
199+ var f = out.form;
200+ if (f.ctor === 'FGroup') {
201+ ps.unshift(stepperHelp(f._1));
202+ var m = f._0;
203+ matrices.push(m);
204+ var x = out.x, y = out.y, theta = out.theta, scale = out.scale;
205+ if (x !== 0 || y !== 0) ctx.translate(x, -y);
206+ if (theta !== 0) ctx.rotate(theta);
207+ if (scale !== 1) ctx.scale(scale, scale);
208+ ctx.save();
209+ ctx.transform(m[0], m[3], m[1], m[4], m[2], -m[5]);
210+ }
211+ return out;
171212 }
213+ function transforms() { return matrices; }
214+ return { peekNext:peekNext, next:next, transforms:transforms };
172215}
173216
174−function collageForms(w,h,forms) {
217+function makeCanvas(w,h) {
175218 var canvas = newElement('canvas');
176219 canvas.style.width = w + 'px';
177220 canvas.style.height = h + 'px';
178221 canvas.style.display = "block";
222+ canvas.style.position = "absolute";
179223 canvas.width = w;
180224 canvas.height = h;
181− function redo() { renderForms([], this, ctx, forms); }
182− if (canvas.getContext) {
183− var ctx = canvas.getContext('2d');
184− ctx.translate(w/2, h/2);
185− renderForms([], redo, ctx, forms);
186− return canvas;
187− }
188− canvas.innerHTML = "Your browser does not support canvas.";
189225 return canvas;
190226}
191227
192−function collageElement(stack, width, height, elem) {
193− var e = Render.render(elem),
194− w = elem.props.width,
195− h = elem.props.height,
196− m = Matrix.matrix(1,0,0,1,width/2,height/2),
197− len = stack.length;
198− for (var i = 0; i < len; ++i) { m = stack[i](m); }
199− var mtrx = 'matrix(' + m[0] + ',' + m[3] + ',' + m[1] + ',' +
200− m[4] + ',' + m[2] + ',' + m[5] + ')';
201− addTransform(e.style, 'translate(' + (-w/2) + 'px,'+ (-h/2) + 'px) ' + mtrx);
202− var div = newElement('div');
203− addTo(div,e);
204− div.style.width = width + 'px';
205− div.style.height = height + 'px';
206− div.style.overflow = 'hidden';
207− return div;
228+function render(model) {
229+ var div = newElement('div');
230+ update(div, model, model);
231+ return div;
208232}
209233
210−function render(model) {
211− return collageForms(model.w, model.h, model.forms);
234+function updateTracker(w,h,div) {
235+ var kids = div.childNodes;
236+ var i = 0;
237+ function transform(transforms, ctx) {
238+ ctx.translate(w/2, h/2);
239+ var len = transforms.length;
240+ for (var i = 0; i < len; ++i) {
241+ var m = transforms[i];
242+ ctx.save();
243+ ctx.transform(m[0], m[3], m[1], m[4], m[2], m[5]);
244+ }
245+ return ctx;
246+ }
247+ function getContext(transforms) {
248+ while (i < kids.length) {
249+ var node = kids[i++];
250+ if (node.getContext) {
251+ node.width = w;
252+ node.height = h;
253+ node.style.width = w + 'px';
254+ node.style.height = h + 'px';
255+ return transform(transforms, node.getContext('2d'));
256+ }
257+ div.removeChild(node);
258+ }
259+ var canvas = makeCanvas(w,h);
260+ div.appendChild(canvas);
261+ // we have added a new node, so we must step our position
262+ ++i;
263+ return transform(transforms, canvas.getContext('2d'));
264+ }
265+ function element(matrices, form) {
266+ var container = kids[i];
267+ if (!container) {
268+ container = newElement('div');
269+ container.style.overflow = 'hidden';
270+ container.style.position = 'absolute';
271+ div.appendChild(container);
272+ } else if (container.getContext) {
273+ container = newElement('div');
274+ container.style.overflow = 'hidden';
275+ container.style.position = 'absolute';
276+ div.insertBefore(container, kids[i]);
277+ }
278+ // we have added a new node, so we must step our position
279+ ++i;
280+
281+ container.style.width = w + 'px';
282+ container.style.height = h + 'px';
283+
284+ var elem = form.form._0;
285+ var node = container.firstChild;
286+ if (node) {
287+ Render.update(node, node.oldElement, elem);
288+ } else {
289+ node = Render.render(elem);
290+ container.appendChild(node);
291+ }
292+ node.oldElement = elem;
293+ addTransform(node.style, makeTransform(w, h, form, matrices));
294+ }
295+ return { getContext:getContext, element:element };
212296}
213297
214−function update(node, oldModel, newModel) {
215− if (!node.getContext) {
216− return node.parentNode.replaceChild(render(newModel), node);
298+
299+function update(div, _, model) {
300+ var w = model.w;
301+ var h = model.h;
302+ div.style.width = w + 'px';
303+ div.style.height = h + 'px';
304+ if (model.forms.ctor === 'Nil') {
305+ while (div.hasChildNodes()) {
306+ div.removeChild(div.lastChild);
307+ }
308+ }
309+ var stpr = stepper(model.forms);
310+ var tracker = updateTracker(w,h,div);
311+ var ctx = null;
312+ var formType = '';
313+
314+ while (formType = stpr.peekNext()) {
315+ if (ctx === null && formType !== 'FElement') {
316+ ctx = tracker.getContext(stpr.transforms());
317+ }
318+ var form = stpr.next(ctx);
319+ if (formType === 'FElement') {
320+ tracker.element(stpr.transforms(), form);
321+ ctx = null;
322+ } else if (formType !== 'FGroup') {
323+ renderForm(function() { update(div, model, model); }, ctx, form);
324+ }
217325 }
218− var ctx = node.getContext('2d');
219− var w = newModel.w, h = newModel.h;
220− ctx.clearRect(-w/2, -h/2, w, h);
221− var redo = function(){ renderForms( [], this, ctx, newModel.forms ); };
222− renderForms( [], redo, ctx, newModel.forms );
326+ return div;
223327}
224328
225329return { render:render, update:update };