on master help sign in
Merge remote-tracking branch 'evan/dev' into lint

Conflicts: runtime/Init.js runtime/Render/Collage.js

commitacd2b61e0046945dc7de2ef1c71c83db26d37e43 authorColin Curtin <colin.t.curtin@gmail.com> dateTue 14 May 2013, 09:18 (13 years ago) parent06854254bb23bc
15 files changed+307−178
compiler/Gen/GenerateHtml.hs+2−2 libraries/Graphics/Collage.elm+6 libraries/Json.elm+2−2 libraries/Native/Show.js−19 libraries/Native/Signal/Keyboard.js+8−8 libraries/Native/Signal/Mouse.js+3−3 libraries/Native/Signal/Touch.js+12−8 libraries/Native/Signal/Window.js+9−5 libraries/Native/Utils.js+17 libraries/Signal.elm+5−4 libraries/Time.elm+1−1 runtime/Init.js+48−38 runtime/Render/Collage.js+185−81 runtime/Render/Element.js+7−7 runtime/Utils.js+2
compiler/Gen/GenerateHtml.hs+2−2
@@ -44,7 +44,7 @@ generateHtml libLoc title source =
4444 case buildFromSource True source of
4545 Right modul -> modulesToHtml Readable title libLoc [] True [modul]
4646 Left err -> createHtml Readable libLoc title (Right $ showErr err)
47− (H.noscript "") "Elm.Main"
47+ (H.noscript "") "Main"
4848
4949
5050modulesToHtml jsStyle title libLoc jss nscrpt modules =
@@ -71,5 +71,5 @@ createHtml jsStyle libLoc title js noscript moduleToLoad =
7171 makeScript Readable (Left libLoc)
7272 makeScript jsStyle js
7373 H.body $ do
74− H.script ! A.type_ "text/javascript" $ preEscapedToMarkup ("Elm.init(Elm." ++ moduleToLoad ++ ")" :: String)
74+ H.script ! A.type_ "text/javascript" $ preEscapedToMarkup ("Elm.fullscreen(Elm." ++ moduleToLoad ++ ")" :: String)
7575 H.noscript $ preEscapedToMarkup noscript
libraries/Graphics/Collage.elm+6
@@ -46,6 +46,7 @@ defaultLine = {
4646 miterLimit = 10
4747 }
4848
49+-- default LineStyles
4950solid : Color -> LineStyle
5051solid clr = { defaultLine | color <- clr }
5152dashed : Color -> LineStyle
@@ -79,6 +80,11 @@ outlined style shape = form (FShape (Left style) shape)
7980traced : LineStyle -> Path -> Form
8081traced style path = form (FPath style path)
8182
83+-- Draw a sprite
84+-- w width
85+-- h height
86+-- pos (px,py) coordinates into the sprite map
87+-- src The location of the image
8288sprite : Int -> Int -> (Int,Int) -> String -> Form
8389sprite w h pos src = form (FImage w h pos src)
8490
libraries/Json.elm+2−2
@@ -38,12 +38,12 @@ toJSString : Value -> JSString
3838toJSString v = Native.toPrettyJSString "" v
3939
4040-- Parse a string representation of a proper JSON object into
41−-- its Elm's representation.
41+-- its Elm representation.
4242fromString : String -> Maybe Value
4343fromString s = Native.fromJSString (JS.fromString s)
4444
4545-- Parse a JavaScript string representation of a proper JSON object into
46−-- its Elm's representation.
46+-- its Elm representation.
4747fromJSString : JSString -> Maybe Value
4848
4949
libraries/Native/Show.js−19
@@ -13,24 +13,6 @@ Elm.Native.Show = function(elm) {
1313 var Json = Elm.Json(elm);
1414 var Tuple2 = Elm.Native.Utils(elm).Tuple2;
1515
16− elm.node.addEventListener('log', function(e) { console.log(e.value); });
17− elm.node.addEventListener('title', function(e) {document.title = e.value;});
18− elm.node.addEventListener('redirect', function(e) {
19− if (e.value.length > 0) { window.location = e.value; }
20− });
21− elm.node.addEventListener('viewport', function(e) {
22− var node = document.getElementById('elm_viewport');
23− if (!node) {
24− node = document.createElement('meta');
25− node.id = 'elm_viewport';
26− node.name = 'viewport';
27− document.head.appendChild(node);
28− }
29− node.content = e.value;
30− Dispatcher.notify(elm.Window.dimensions.id,
31− Tuple2(elm.node.clientWidth, elm.node.clientHeight));
32− });
33−
3416 var toString = function(v) {
3517 if (typeof v === "function") {
3618 var name = v.func ? v.func.name : v.name;
@@ -44,7 +26,6 @@ Elm.Native.Show = function(elm) {
4426 } else if (typeof v === "object" && '_' in v) {
4527 var output = [];
4628 for (var k in v._) {
47− console.log(k,v._[k]);
4829 for (var i = v._[k].length; i--; ) {
4930 output.push(k + " = " + toString(v._[k][i]));
5031 }
libraries/Native/Signal/Keyboard.js+8−8
@@ -14,27 +14,27 @@ Elm.Native.Keyboard = function(elm) {
1414 if (NList.member(e.keyCode)(keysDown.value)) return;
1515 var list = NList.Cons(e.keyCode, keysDown.value);
1616 var hasListener = elm.notify(keysDown.id, list);
17− if (!hasListener) elm.node.removeEventListener('keydown', down);
17+ if (!hasListener) document.removeEventListener('keydown', down);
1818 }
1919 function up(e) {
2020 function notEq(kc) { return kc !== e.keyCode; }
2121 var codes = NList.filter(notEq)(keysDown.value);
2222 var hasListener = elm.notify(keysDown.id, codes);
23− if (!hasListener) elm.node.removeEventListener('keyup', up);
23+ if (!hasListener) document.removeEventListener('keyup', up);
2424 }
2525 function blur(e) {
2626 var hasListener = elm.notify(keysDown.id, NList.Nil);
27− if (!hasListener) elm.node.removeEventListener('blur', blur);
27+ if (!hasListener) document.removeEventListener('blur', blur);
2828 }
2929 function press(e) {
3030 var hasListener = elm.notify(lastKey.id, e.charCode || e.keyCode);
31− if (!hasListener) elm.node.removeEventListener('keypress', press);
31+ if (!hasListener) document.removeEventListener('keypress', press);
3232 }
3333
34− elm.node.addEventListener('keydown' , down );
35− elm.node.addEventListener('keyup' , up );
36− elm.node.addEventListener('blur' , blur );
37− elm.node.addEventListener('keypress', press);
34+ document.addEventListener('keydown' , down );
35+ document.addEventListener('keyup' , up );
36+ document.addEventListener('blur' , blur );
37+ document.addEventListener('keypress', press);
3838
3939 function keySignal(f) {
4040 var signal = A2( Signal.lift, f, keysDown );
libraries/Native/Signal/Mouse.js+3−3
@@ -21,7 +21,7 @@ Elm.Native.Mouse = function(elm) {
2121 var isDown = Signal.constant(false);
2222 var isClicked = Signal.constant(false);
2323 var clicks = Signal.constant(Utils.Tuple0);
24−
24+
2525 function getXY(e) {
2626 var posx = 0;
2727 var posy = 0;
@@ -35,10 +35,10 @@ Elm.Native.Mouse = function(elm) {
3535 posy = e.clientY + document.body.scrollTop +
3636 document.documentElement.scrollTop;
3737 }
38− return Utils.Tuple2(posx, posy);
38+ return Utils.Tuple2(posx-elm.node.offsetX, posy-elm.node.offsetY);
3939 }
4040
41− var node = elm.node.tagName === "BODY" ? document : elm.node;
41+ var node = elm.display === ElmRuntime.Display.FULLSCREEN ? document : elm.node;
4242
4343 function click(e) {
4444 var hasListener1 = elm.notify(isClicked.id, true);
libraries/Native/Signal/Touch.js+12−8
@@ -7,6 +7,7 @@ Elm.Native.Touch = function(elm) {
77
88 var Signal = Elm.Signal(elm);
99 var JS = Elm.JavaScript(elm);
10+ var _ = Elm.Native.Utils(elm);
1011
1112 function Dict() {
1213 this.keys = [];
@@ -44,24 +45,27 @@ Elm.Native.Touch = function(elm) {
4445 var r = dict.lookup(t.identifier);
4546 return {_ : {},
4647 id: t.identifier,
47− x : t.pageX,
48− y : t.pageY,
48+ x : t.pageX - elm.node.offsetX,
49+ y : t.pageY - elm.node.offsetY,
4950 x0: r.x,
5051 y0: r.y,
5152 t0: r.t
5253 };
5354 }
5455
55− var node = elm.node === document.body ? document : elm.node;
56+ var node = elm.display === ElmRuntime.Display.FULLSCREEN ? document : elm.node;
5657
5758 function start(e) {
58− dict.insert(e.identifier,{x:e.pageX,y:e.pageY,t:Date.now()});
59+ dict.insert(e.identifier,
60+ {x: e.pageX - elm.node.offsetX,
61+ y: e.pageY - elm.node.offsetY,
62+ t: Date.now()});
5963 }
6064 function end(e) {
6165 var t = dict.remove(e.identifier);
6266 if (Date.now() - t.t < tapTime) {
63− hasTap = true;
64− tap = {_:{}, x:t.x, y:t.y};
67+ hasTap = true;
68+ tap = {_:{}, x:t.x, y:t.y};
6569 }
6670 }
6771
@@ -87,8 +91,8 @@ Elm.Native.Touch = function(elm) {
8791 function move(e) {
8892 for (var i = root.value.length; i--; ) {
8993 if (root.value[i].id === mouseID) {
90− root.value[i].x = e.pageX;
91− root.value[i].y = e.pageY;
94+ root.value[i].x = e.pageX - elm.node.offsetX;
95+ root.value[i].y = e.pageY - elm.node.offsetY;
9296 elm.notify(root.id, root.value);
9397 break;
9498 }
libraries/Native/Signal/Window.js+9−5
@@ -10,7 +10,10 @@ Elm.Native.Window = function(elm) {
1010
1111 function getWidth() { return elm.node.clientWidth; }
1212 function getHeight() {
13− return document.body === elm.node ? window.innerHeight : elm.node.clientHeight;
13+ if (elm.display === ElmRuntime.Display.FULLSCREEN) {
14+ return window.innerHeight;
15+ }
16+ return elm.node.clientHeight;
1417 }
1518
1619 var dimensions = Signal.constant(Tuple2(getWidth(), getHeight()));
@@ -24,19 +27,20 @@ Elm.Native.Window = function(elm) {
2427 var height = A2(Signal.lift, function(p){return p._1;}, dimensions);
2528 height.defaultNumberOfKids = 0;
2629
27− function resize(e) {
30+ function resizeIfNeeded() {
2831 var w = getWidth();
2932 var h = getHeight();
3033 if (dimensions.value._0 === w && dimensions.value._1 === h) return;
3134 var hasListener = elm.notify(dimensions.id, Tuple2(w,h));
32− if (!hasListener) window.removeEventListener('resize', resize);
35+ if (!hasListener) window.removeEventListener('resize', resizeIfNeeded);
3336 }
34− window.addEventListener('resize', resize);
37+ window.addEventListener('resize', resizeIfNeeded);
3538
3639 return elm.Native.Window = {
3740 dimensions:dimensions,
3841 width:width,
39− height:height
42+ height:height,
43+ resizeIfNeeded:resizeIfNeeded
4044 };
4145
4246};
libraries/Native/Utils.js+17
@@ -113,6 +113,23 @@ Elm.Native.Utils = function(elm) {
113113 return Tuple2(w,h);
114114 }
115115
116+ function adjustOffset() {
117+ var node = elm.node;
118+ var offsetX = offsetY = 0;
119+ if (node.offsetParent) {
120+ do {
121+ offsetX += node.offsetLeft;
122+ offsetY += node.offsetTop;
123+ } while (node = node.offsetParent);
124+ }
125+ node.offsetX = offsetX;
126+ node.offsetY = offsetY;
127+ }
128+
129+ if (elm.display === ElmRuntime.Display.COMPONENT) {
130+ elm.node.addEventListener('mouseover', adjustOffset);
131+ }
132+
116133 return elm.Native.Utils = {
117134 eq:eq,
118135 cmp:compare,
libraries/Signal.elm+5−4
@@ -3,7 +3,7 @@
33-- working with time (e.g. setting FPS) and combining signals and time (e.g.
44-- delaying updates, getting timestamps) can be found in the
55-- [`Time`](/docs/Signal/Time.elm) library.
6−--
6+--
77-- Note: There are lift functions up to `lift8`.
88module Signal where
99
@@ -29,7 +29,7 @@ lift8 : (a -> b -> c -> d -> e -> f -> g -> h -> i) -> Signal a -> Signal b -> S
2929
3030-- Create a past-dependent signal. Each value given on the input signal will
3131-- be accumulated, producing a new output value.
32−--
32+--
3333-- For instance, `(foldp (\\t acc -> acc + 1) 0 (Time.every second))` increments every second.
3434foldp : (a -> b -> b) -> b -> Signal a -> Signal b
3535
@@ -41,6 +41,7 @@ merge : Signal a -> Signal a -> Signal a
4141-- signals update simultaneously.
4242merges : [Signal a] -> Signal a
4343
44+-- Combine many signals into one, with an array of results.
4445combine : [Signal a] -> Signal [a]
4546combine = L.foldr (S.lift2 (::)) (S.constant [])
4647
@@ -104,9 +105,9 @@ delay : Time -> Signal a -> Signal a
104105
105106-- Signal application. This takes two signals, holding a function and
106107-- a value. It applies the current function to the current value.
107−--
108+--
108109-- So the following expressions are equivalent:
109−--
110+--
110111-- scene <~ Mouse.x ~ Mouse.y
111112-- lift2 scene Mouse.x Mouse.y
112113(~) : Signal (a -> b) -> Signal a -> Signal b
libraries/Time.elm+1−1
@@ -39,7 +39,7 @@ inHours t = t / hour
3939fps : Number a -> Signal Time
4040
4141-- Same as the fps function, but you can turn it on and off. Allows you
42−-- to do brief animations based on user input without major ineffeciencies.
42+-- to do brief animations based on user input without major inefficiencies.
4343-- The first time delta after a pause is always zero, no matter how long
4444-- the pause was. This way summing the deltas will actually give the amount
4545-- of time that the output signal has been running.
runtime/Init.js+48−38
@@ -1,7 +1,28 @@
11
2−Elm.init = function(module, baseNode) {
3− 'use strict';
2+(function() {
3+'use strict';
4+
5+Elm.fullscreen = function(module) {
6+ var style = document.createElement('style');
7+ style.type = 'text/css';
8+ style.innerHTML = "html,head,body { padding:0; margin:0; }" +
9+ "body { font-family: calibri, helvetica, arial, sans-serif; }";
10+ document.head.appendChild(style);
11+ return init(ElmRuntime.Display.FULLSCREEN, document.body, module);
12+};
13+
14+Elm.node = function(width, height, module) {
15+ var container = document.createElement('div');
16+ container.style.width = width + 'px';
17+ container.style.height = height + 'px';
18+ return init(ElmRuntime.Display.COMPONENT, container, module);
19+};
420
21+Elm.worker = function(module) {
22+ return init(ElmRuntime.Display.NONE, {}, module);
23+};
24+
25+function init(display, container, module) {
526 // defining state needed for an instance of the Elm RTS
627 var signalGraph = null;
728 var inputs = [];
@@ -18,19 +39,17 @@ Elm.init = function(module, baseNode) {
1839 return hasListener;
1940 }
2041
21− // ensure that baseNode exists and is properly formatted.
22− if (typeof baseNode === 'undefined') {
23− baseNode = document.body;
24− var style = document.createElement('style');
25− style.type = 'text/css';
26− style.innerHTML = "html,head,body { padding:0; margin:0; }" +
27− "body { font-family: calibri, helvetica, arial, sans-serif; }";
28− document.head.appendChild(style);
29− }
42+ container.offsetX = 0;
43+ container.offsetY = 0;
3044
3145 // create the actual RTS. Any impure modules will attach themselves to this
3246 // object. This permits many Elm programs to be embedded per document.
33− var elm = {notify: notify, node: baseNode, id: ElmRuntime.guid(), inputs: inputs};
47+ var elm = { notify:notify,
48+ node:container,
49+ display:display,
50+ id:ElmRuntime.guid(),
51+ inputs:inputs
52+ };
3453
3554 // Set up methods to communicate with Elm program from JS.
3655 function send(name, value) {
@@ -44,15 +63,16 @@ Elm.init = function(module, baseNode) {
4463 document.addEventListener(name + '_' + elm.id, handler);
4564 }
4665
47− recv('elm_log', function(e) {console.log(e.value);});
48− recv('elm_title', function(e) {document.title = e.value;});
49− recv('elm_redirect', function(e) {
66+ recv('log', function(e) {console.log(e.value)});
67+ recv('title', function(e) {document.title = e.value});
68+ recv('redirect', function(e) {
5069 if (e.value.length > 0) { window.location = e.value; }
5170 });
5271
5372 // If graphics are not enabled, escape early, skip over setting up DOM stuff.
54− if (baseNode === null) return { send : send, recv : recv };
55−
73+ if (display === ElmRuntime.Display.NONE) {
74+ return { send : send, recv : recv };
75+ }
5676
5777 var Render = ElmRuntime.use(ElmRuntime.Render.Element);
5878
@@ -65,36 +85,26 @@ Elm.init = function(module, baseNode) {
6585 if (!('recv' in signalGraph)) signalGraph = Signal.constant(signalGraph);
6686 visualModel = signalGraph.value;
6787 inputs = ElmRuntime.filterDeadInputs(inputs);
68−
69− var tuple2 = Elm.Native.Utils(elm).Tuple2;
70− function adjustWindow() {
71− if ('Window' in elm) {
72− var w = baseNode.clientWidth;
73− if (w !== elm.Window.dimensions.value._0) {
74− notify(elm.Window.dimensions.id,
75− tuple2(w, document.body === baseNode ?
76− window.innerHeight : baseNode.clientHeight));
77− }
78− }
79− }
80−
81− // Add the visualModel to the DOM
82− var renderNode = Render.render(visualModel);
83− baseNode.appendChild(renderNode);
84− adjustWindow();
85−
88+
89+ // Add the visualModel to the DOM
90+ var renderNode = Render.render(visualModel)
91+ container.appendChild(renderNode);
92+ elm.Native.Window.resizeIfNeeded();
93+
8694 // set up updates so that the DOM is adjusted as necessary.
8795 var update = Render.update;
8896 function domUpdate(value) {
8997 ElmRuntime.draw(function(_) {
9098 update(renderNode, visualModel, value);
9199 visualModel = value;
92− adjustWindow();
100+ elm.Native.Window.resizeIfNeeded();
93101 });
94102 return value;
95103 }
96104
97105 signalGraph = A2(Signal.lift, domUpdate, signalGraph);
98−
99− return { send : send, recv : recv };
106+
107+ return { send : send, recv : recv, node : container };
100108};
109+
110+}());
runtime/Render/Collage.js+185−81
@@ -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 };
runtime/Render/Element.js+7−7
@@ -3,7 +3,7 @@ ElmRuntime.Render.Element = function() {
33'use strict';
44
55var Utils = ElmRuntime.use(ElmRuntime.Render.Utils);
6−var newElement = Utils.newElement, addTo = Utils.addTo, extract = Utils.extract,
6+var newElement = Utils.newElement, extract = Utils.extract,
77 addTransform = Utils.addTransform, removeTransform = Utils.removeTransform,
88 fromList = Utils.fromList, eq = Utils.eq;
99
@@ -18,7 +18,7 @@ function setProps(props, e) {
1818 if (props.href !== '') {
1919 var a = newElement('a');
2020 a.href = props.href;
21− addTo(a,e);
21+ a.appendChild(e);
2222 return a;
2323 }
2424 return e;
@@ -63,7 +63,7 @@ function fittedImage(w, h, src) {
6363 };
6464 img.src = src;
6565 img.name = src;
66− addTo(e,img);
66+ e.appendChild(img);
6767 return e;
6868}
6969
@@ -85,7 +85,7 @@ function croppedImage(elem, w, h, src) {
8585 };
8686 img.src = src;
8787 img.name = src;
88− addTo(e,img);
88+ e.appendChild(img);
8989 return e;
9090}
9191
@@ -95,7 +95,7 @@ function goRight(e) { e.style.styleFloat = e.style.cssFloat = "left"; return e;
9595function flowWith(f, array) {
9696 var container = newElement('div');
9797 for (var i = array.length; i--; ) {
98− addTo(container, f(render(array[i])));
98+ container.appendChild(f(render(array[i])));
9999 }
100100 return container;
101101}
@@ -143,7 +143,7 @@ function container(pos,elem) {
143143 var div = newElement('div');
144144 div.style.position = 'relative';
145145 div.style.overflow = 'hidden';
146− addTo(div,e);
146+ div.appendChild(e);
147147 return div;
148148}
149149
@@ -256,7 +256,7 @@ function updateProps(node, curr, next) {
256256 if (currP.href === '') {
257257 var a = newElement('a');
258258 a.href = props.href;
259− addTo(a,e);
259+ a.appendChild(e);
260260 e.parentNode.replaceChild(a,e);
261261 } else {
262262 node.parentNode.href = props.href;
runtime/Utils.js+2
@@ -2,6 +2,8 @@
22(function() {
33'use strict';
44
5+ElmRuntime.Display = { FULLSCREEN: 0, COMPONENT: 1, NONE: 2 };
6+
57ElmRuntime.counter = 0;
68ElmRuntime.guid = function() { return ElmRuntime.counter++; }
79