on 8da2bc0 help sign in
elk/runtime/Render/Collage.js
318 lines 9.7 kB history raw
2ElmRuntime.Render.Collage = function() {
3'use strict';
5var Render = ElmRuntime.use(ElmRuntime.Render.Element);
6var Matrix = Elm.Matrix2D({});
7var Utils = ElmRuntime.use(ElmRuntime.Render.Utils);
8var newElement = Utils.newElement,
9 extract = Utils.extract, fromList = Utils.fromList,
10 fromString = Utils.fromString, addTransform = Utils.addTransform;
12function trace(ctx, path) {
13 var points = fromList(path);
14 var i = points.length - 1;
15 if (i <= 0) return;
16 ctx.moveTo(points[i]._0, points[i]._1);
17 while (i--) { ctx.lineTo(points[i]._0, points[i]._1); }
18 if (path.closed) {
19 i = points.length - 1;
20 ctx.lineTo(points[i]._0, points[i]._1);
21 }
24function line(ctx,style,path) {
25 style.dashing.ctor === 'Nil' ? trace(ctx, path) : customLineHelp(ctx, style, path);
26 ctx.stroke();
29function customLineHelp(ctx, style, path) {
30 var points = fromList(path);
31 if (path.closed) points.push(points[0]);
32 var pattern = fromList(style.dashing);
33 var i = points.length - 1;
34 if (i <= 0) return;
35 var x0 = points[i]._0, y0 = points[i]._1;
36 var x1=0, y1=0, dx=0, dy=0, remaining=0, nx=0, ny=0;
37 var pindex = 0, plen = pattern.length;
38 var draw = true, segmentLength = pattern[0];
39 ctx.moveTo(x0,y0);
40 while (i--) {
41 x1 = points[i]._0; y1 = points[i]._1;
42 dx = x1 - x0; dy = y1 - y0;
43 remaining = Math.sqrt(dx * dx + dy * dy);
44 while (segmentLength <= remaining) {
45 x0 += dx * segmentLength / remaining;
46 y0 += dy * segmentLength / remaining;
47 ctx[draw ? 'lineTo' : 'moveTo'](x0, y0);
48 // update starting position
49 dx = x1 - x0; dy = y1 - y0;
50 remaining = Math.sqrt(dx * dx + dy * dy);
51 // update pattern
52 draw = !draw;
53 pindex = (pindex + 1) % plen;
54 segmentLength = pattern[pindex];
55 }
56 if (remaining > 0) {
57 ctx[draw ? 'lineTo' : 'moveTo'](x1, y1);
58 segmentLength -= remaining;
59 }
60 x0 = x1; y0 = y1;
61 }
64function drawLine(ctx, style, path) {
65 ctx.lineWidth = style.width;
66 ctx.lineCap = style.cap.ctor.toLowerCase();
67 var join = style.join.ctor;
68 ctx.lineJoin = join === 'Smooth' ? 'round' :
69 join === 'Sharp' ? 'miter' : 'bevel';
70 ctx.miterLimit = style.miterLimit;
71 ctx.strokeStyle = extract(style.color);
72 return line(ctx, style, path);
75function texture(redo, ctx, src) {
76 var img = new Image();
77 img.src = fromString(src);
78 img.onload = redo;
79 return ctx.createPattern(img, 'repeat');
82function gradient(ctx, grad) {
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;
102function drawShape(redo, ctx, style, path) {
103 trace(ctx, path);
104 var sty = style.ctor;
105 ctx.fillStyle =
106 sty === 'Solid' ? extract(style._0) :
107 sty === 'Texture' ? texture(redo, ctx, style._0) : gradient(ctx, style._0);
108 ctx.fill();
111function drawImage(redo, ctx, form) {
112 var img = new Image();
113 img.onload = redo;
114 img.src = fromString(form._3);
115 var w = form._0, h = form._1, pos = form._2;
116 ctx.drawImage(img, pos._0, pos._1, w, h, -w/2, -h/2, w, h);
119function renderForm(redo, ctx, form) {
120 ctx.save();
121 var x = form.x, y = form.y, theta = form.theta, scale = form.scale;
122 if (x !== 0 || y !== 0) ctx.translate(x, -y);
123 if (theta !== 0) ctx.rotate(theta);
124 if (scale !== 1) ctx.scale(scale, scale);
125 ctx.beginPath();
126 var f = form.form;
127 switch(f.ctor) {
128 case 'FPath' : drawLine(ctx, f._0, f._1); break;
129 case 'FImage': drawImage(redo, ctx, f); break;
130 case 'FShape':
131 if (f._0.ctor === 'Left') {
132 f._1.closed = true;
133 drawLine(ctx, f._0._0, f._1);
134 } else {
135 drawShape(redo, ctx, f._0._0, f._1);
136 }
137 break;
138 }
139 ctx.restore();
142function makeTransform(w, h, form, matrices) {
143 var props = form.form._0.props;
144 var m = A6( Matrix.matrix, 1, 0, 0, 1,
145 (w - props.width)/2,
146 (props.height - h)/2 );
147 var len = matrices.length;
148 for (var i = 0; i < len; ++i) { m = A2( Matrix.multiply, m, matrices[i] ); }
150 var x = form.x, y = form.y, theta = form.theta, scale = form.scale;
151 if (x !== 0 || y !== 0) m = A3( Matrix.move, x, y, m );
152 if (theta !== 0) m = A2( Matrix.rotate, theta, m );
153 if (scale !== 1) m = Matrix.scale( scale );
154 return 'matrix(' + m[0] + ',' + m[3] + ',' + m[1] + ',' +
155 m[4] + ',' + m[2] + ',' + (-m[5]) + ')';
158function stepperHelp(list) {
159 var arr = fromList(list);
160 var i = 0;
161 function peekNext() {
162 return i < arr.length ? arr[i].form.ctor : '';
163 }
164 // assumes that there is a next element
165 function next() {
166 var out = arr[i];
167 ++i;
168 return out;
169 }
170 return { peekNext:peekNext, next:next };
173function stepper(forms) {
174 var ps = [stepperHelp(forms)];
175 var matrices = [];
176 function peekNext() {
177 var len = ps.length;
178 var formType = '';
179 for (var i = 0; i < len; ++i ) {
180 if (formType = ps[i].peekNext()) return formType;
181 }
182 return '';
183 }
184 // assumes that there is a next element
185 function next(ctx) {
186 while (!ps[0].peekNext()) { ps.shift(); matrices.pop(); ctx.restore(); }
187 var out = ps[0].next();
188 var f = out.form;
189 if (f.ctor === 'FGroup') {
190 ps.unshift(stepperHelp(f._1));
191 var m = f._0;
192 matrices.push(m);
193 var x = out.x, y = out.y, theta = out.theta, scale = out.scale;
194 if (x !== 0 || y !== 0) ctx.translate(x, -y);
195 if (theta !== 0) ctx.rotate(theta);
196 if (scale !== 1) ctx.scale(scale, scale);
197 ctx.save();
198 ctx.transform(m[0], m[3], m[1], m[4], m[2], -m[5]);
199 }
200 return out;
201 }
202 function transforms() { return matrices; }
203 return { peekNext:peekNext, next:next, transforms:transforms };
206function makeCanvas(w,h) {
207 var canvas = newElement('canvas');
208 canvas.style.width = w + 'px';
209 canvas.style.height = h + 'px';
210 canvas.style.display = "block";
211 canvas.style.position = "absolute";
212 canvas.width = w;
213 canvas.height = h;
214 return canvas;
217function render(model) {
218 var div = newElement('div');
219 update(div, model, model);
220 return div;
223function updateTracker(w,h,div) {
224 var kids = div.childNodes;
225 var i = 0;
226 function transform(transforms, ctx) {
227 ctx.translate(w/2, h/2);
228 var len = transforms.length;
229 for (var i = 0; i < len; ++i) {
230 var m = transforms[i];
231 ctx.save();
232 ctx.transform(m[0], m[3], m[1], m[4], m[2], m[5]);
233 }
234 return ctx;
235 }
236 function getContext(transforms) {
237 while (i < kids.length) {
238 var node = kids[i++];
239 if (node.getContext) {
240 node.width = w;
241 node.height = h;
242 return transform(transforms, node.getContext('2d'));
243 }
244 div.removeChild(node);
245 }
246 var canvas = makeCanvas(w,h);
247 div.appendChild(canvas);
248 // we have added a new node, so we must step our position
249 ++i;
250 return transform(transforms, canvas.getContext('2d'));
251 }
252 function element(matrices, form) {
253 var container = kids[i];
254 if (!container) {
255 container = newElement('div');
256 container.style.overflow = 'hidden';
257 container.style.position = 'absolute';
258 div.appendChild(container);
259 } else if (container.getContext) {
260 container = newElement('div');
261 container.style.overflow = 'hidden';
262 container.style.position = 'absolute';
263 div.insertBefore(container, kids[i]);
264 }
265 // we have added a new node, so we must step our position
266 ++i;
268 container.style.width = w + 'px';
269 container.style.height = h + 'px';
271 var elem = form.form._0;
272 var node = container.firstChild;
273 if (node) {
274 Render.update(node, node.oldElement, elem);
275 } else {
276 node = Render.render(elem);
277 container.appendChild(node);
278 }
279 node.oldElement = elem;
280 addTransform(node.style, makeTransform(w, h, form, matrices));
281 }
282 return { getContext:getContext, element:element };
286function update(div, _, model) {
287 var w = model.w;
288 var h = model.h;
289 div.style.width = w + 'px';
290 div.style.height = h + 'px';
291 if (model.forms.ctor === 'Nil') {
292 while (div.hasChildNodes()) {
293 div.removeChild(div.lastChild);
294 }
295 }
296 var stpr = stepper(model.forms);
297 var tracker = updateTracker(w,h,div);
298 var ctx = null;
299 var formType = '';
301 while (formType = stpr.peekNext()) {
302 if (ctx === null && formType !== 'FElement') {
303 ctx = tracker.getContext(stpr.transforms());
304 }
305 var form = stpr.next(ctx);
306 if (formType === 'FElement') {
307 tracker.element(stpr.transforms(), form);
308 ctx = null;
309 } else if (formType !== 'FGroup') {
310 renderForm(function() { update(div, model, model); }, ctx, form);
311 }
312 }
313 return div;
316return { render:render, update:update };
318};