Continue to move towards individual yoob subdirectories.
Chris Pressey
7 years ago
18 | 18 | </body> |
19 | 19 | <script src="fibonacci-spiral.js"></script> |
20 | 20 | <script> |
21 | launch('../common-yoob.js-0.11/', 'container'); | |
21 | launch('yoob/', 'container'); | |
22 | 22 | </script> |
0 | /* | |
1 | * This file is part of yoob.js version 0.7 | |
2 | * Available from https://github.com/catseye/yoob.js/ | |
3 | * This file is in the public domain. See http://unlicense.org/ for details. | |
4 | */ | |
5 | if (window.yoob === undefined) yoob = {}; | |
6 | ||
7 | /* | |
8 | * Pretty standard shim to get window.{request,cancelRequest}AnimationFrame | |
9 | * functions, synthesized from the theory and the many examples I've seen. | |
10 | */ | |
11 | ||
12 | window.requestAnimationFrame = | |
13 | window.requestAnimationFrame || | |
14 | window.webkitRequestAnimationFrame || | |
15 | window.mozRequestAnimationFrame || | |
16 | window.oRequestAnimationFrame || | |
17 | window.msRequestAnimationFrame || | |
18 | function(f, elem) { | |
19 | return setTimeout(function() { | |
20 | f(Date.now()); | |
21 | }, 1000 / 60); | |
22 | }; | |
23 | ||
24 | // it was called "cancelRequestAnimationFrame" in the editor's draft: | |
25 | // http://webstuff.nfshost.com/anim-timing/Overview.html | |
26 | // but "cancelAnimationFrame" in the Candidate Recommendation: | |
27 | // http://www.w3.org/TR/animation-timing/ | |
28 | window.cancelAnimationFrame = | |
29 | window.cancelAnimationFrame || | |
30 | window.webkitCancelAnimationFrame || | |
31 | window.mozCancelAnimationFrame || | |
32 | window.oCancelAnimationFrame || | |
33 | window.msCancelAnimationFrame || | |
34 | window.cancelRequestAnimationFrame || | |
35 | window.webkitCancelRequestAnimationFrame || | |
36 | window.mozCancelRequestAnimationFrame || | |
37 | window.oCancelRequestAnimationFrame || | |
38 | window.msCancelRequestAnimationFrame || | |
39 | clearTimeout; | |
40 | window.cancelRequestAnimationFrame = window.cancelAnimationFrame; | |
41 | ||
42 | /* | |
43 | * A yoob.Animation object manages an animation. | |
44 | * | |
45 | * How many things get animated by one yoob.Animation object is up to | |
46 | * you. For animated demos, it may be sufficient to have only one | |
47 | * Animation object which updates many independent graphical objects. | |
48 | * However, it may be useful to have multiple Animation objects, if | |
49 | * the program can be in different states (for example, one title screen | |
50 | * Animation, and a different Animation to use during gameplay.) | |
51 | */ | |
52 | yoob.Animation = function() { | |
53 | /* | |
54 | * Initialize a yoob.Animation. Takes a configuration dictionary: | |
55 | * | |
56 | * mode 'quantum' (default) or 'proportional' | |
57 | * object the object to call methods on | |
58 | * tickTime in msec. for quantum only. default = 1/60th sec | |
59 | * lastTime internal (but see below) | |
60 | * accumDelta internal, only used in quantum mode | |
61 | * | |
62 | * There are two modes that a yoob.Animation can be in, | |
63 | * 'quantum' (the default) and 'proportional'. | |
64 | * | |
65 | * Once the Animation has been started (by calling animation.start()): | |
66 | * | |
67 | * In the 'quantum' mode, the object's draw() method is called on | |
68 | * each animation frame, and the object's update() method is called as | |
69 | * many times as necessary to ensure it is called once for every tickTime | |
70 | * milliseconds that have passed. Neither method is passed any | |
71 | * parameters. | |
72 | * | |
73 | * update() (or draw(), in 'proportional' mode only) may return the | |
74 | * exact object 'false' to force the animation to stop immediately. | |
75 | * | |
76 | * In the 'proportional' mode, the object's draw() method is called on | |
77 | * each animation frame, and the amount of time (in milliseconds) that has | |
78 | * elapsed since the last time it was called (or 0 if it was never | |
79 | * previously called) is passed as the first and only parameter. | |
80 | */ | |
81 | this.init = function(cfg) { | |
82 | this.object = cfg.object; | |
83 | this.lastTime = cfg.lastTime || null; | |
84 | this.accumDelta = cfg.accumDelta || 0; | |
85 | this.tickTime = cfg.tickTime || (1000.0 / 60.0); | |
86 | this.mode = cfg.mode || 'quantum'; | |
87 | this.request = null; | |
88 | return this; | |
89 | }; | |
90 | ||
91 | this.start = function() { | |
92 | if (this.request) { | |
93 | return false; | |
94 | } | |
95 | var $this = this; | |
96 | if (this.mode === 'quantum') { | |
97 | var animFrame = function(time) { | |
98 | $this.object.draw(); | |
99 | if ($this.lastTime === null) { | |
100 | $this.lastTime = time; | |
101 | } | |
102 | $this.accumDelta += (time - $this.lastTime); | |
103 | while ($this.accumDelta > $this.tickTime) { | |
104 | $this.accumDelta -= $this.tickTime; | |
105 | var result = $this.object.update(); | |
106 | if (result === false) { | |
107 | $this.accumDelta = $this.tickTime; | |
108 | $this.request = null; | |
109 | } | |
110 | } | |
111 | $this.lastTime = time; | |
112 | if ($this.request) { | |
113 | $this.request = requestAnimationFrame(animFrame); | |
114 | } | |
115 | }; | |
116 | } else if (this.mode === 'proportional') { | |
117 | var animFrame = function(time) { | |
118 | var timeElapsed = ( | |
119 | $this.lastTime == null ? 0 : time - $this.lastTime | |
120 | ); | |
121 | $this.lastTime = time; | |
122 | var result = $this.object.draw(timeElapsed); | |
123 | if (result === false) { | |
124 | $this.request = null; | |
125 | } | |
126 | if ($this.request) { | |
127 | $this.request = requestAnimationFrame(animFrame); | |
128 | } | |
129 | }; | |
130 | } | |
131 | this.request = requestAnimationFrame(animFrame); | |
132 | return true; | |
133 | }; | |
134 | ||
135 | this.stop = function() { | |
136 | if (this.request) { | |
137 | cancelRequestAnimationFrame(this.request); | |
138 | } | |
139 | this.request = null; | |
140 | }; | |
141 | ||
142 | this.reset = function() { | |
143 | this.stop(); | |
144 | this.lastTime = null; | |
145 | }; | |
146 | }; |
0 | /* | |
1 | * This file is part of yoob.js version 0.10 | |
2 | * Available from https://github.com/catseye/yoob.js/ | |
3 | * This file is in the public domain. See http://unlicense.org/ for details. | |
4 | */ | |
5 | if (window.yoob === undefined) yoob = {}; | |
6 | ||
7 | /* | |
8 | * Functions for creating elements. | |
9 | */ | |
10 | ||
11 | yoob.makeCanvas = function(container, width, height) { | |
12 | var canvas = document.createElement('canvas'); | |
13 | if (width) { | |
14 | canvas.width = width; | |
15 | } | |
16 | if (height) { | |
17 | canvas.height = height; | |
18 | } | |
19 | container.appendChild(canvas); | |
20 | return canvas; | |
21 | }; | |
22 | ||
23 | yoob.makeButton = function(container, labelText, fun) { | |
24 | var button = document.createElement('button'); | |
25 | button.innerHTML = labelText; | |
26 | container.appendChild(button); | |
27 | if (fun) { | |
28 | button.onclick = fun; | |
29 | } | |
30 | return button; | |
31 | }; | |
32 | ||
33 | yoob.checkBoxNumber = 0; | |
34 | yoob.makeCheckbox = function(container, checked, labelText, fun) { | |
35 | var checkbox = document.createElement('input'); | |
36 | checkbox.type = "checkbox"; | |
37 | checkbox.id = 'cfzzzb_' + yoob.checkBoxNumber; | |
38 | checkbox.checked = checked; | |
39 | var label = document.createElement('label'); | |
40 | label.htmlFor = 'cfzzzb_' + yoob.checkBoxNumber; | |
41 | yoob.checkBoxNumber += 1; | |
42 | label.appendChild(document.createTextNode(labelText)); | |
43 | ||
44 | container.appendChild(checkbox); | |
45 | container.appendChild(label); | |
46 | ||
47 | if (fun) { | |
48 | checkbox.onchange = function(e) { | |
49 | fun(checkbox.checked); | |
50 | }; | |
51 | } | |
52 | return checkbox; | |
53 | }; | |
54 | ||
55 | yoob.makeTextInput = function(container, size, value) { | |
56 | var input = document.createElement('input'); | |
57 | input.size = "" + (size || 12); | |
58 | input.value = value || ""; | |
59 | container.appendChild(input); | |
60 | return input; | |
61 | }; | |
62 | ||
63 | yoob.makeSlider = function(container, min, max, value, fun) { | |
64 | var slider = document.createElement('input'); | |
65 | slider.type = "range"; | |
66 | slider.min = min; | |
67 | slider.max = max; | |
68 | slider.value = value || 0; | |
69 | if (fun) { | |
70 | slider.onchange = function(e) { | |
71 | fun(parseInt(slider.value, 10)); | |
72 | }; | |
73 | } | |
74 | container.appendChild(slider); | |
75 | return slider; | |
76 | }; | |
77 | ||
78 | yoob.makeParagraph = function(container, innerHTML) { | |
79 | var p = document.createElement('p'); | |
80 | p.innerHTML = innerHTML || ''; | |
81 | container.appendChild(p); | |
82 | return p; | |
83 | }; | |
84 | ||
85 | yoob.makeSpan = function(container, innerHTML) { | |
86 | var span = document.createElement('span'); | |
87 | span.innerHTML = innerHTML || ''; | |
88 | container.appendChild(span); | |
89 | return span; | |
90 | }; | |
91 | ||
92 | yoob.makeDiv = function(container, innerHTML) { | |
93 | var div = document.createElement('div'); | |
94 | div.innerHTML = innerHTML || ''; | |
95 | container.appendChild(div); | |
96 | return div; | |
97 | }; | |
98 | ||
99 | yoob.makePre = function(container, innerHTML) { | |
100 | var elem = document.createElement('pre'); | |
101 | elem.innerHTML = innerHTML || ''; | |
102 | container.appendChild(elem); | |
103 | return elem; | |
104 | }; | |
105 | ||
106 | yoob.makePanel = function(container, title, isOpen) { | |
107 | isOpen = !!isOpen; | |
108 | var panelContainer = document.createElement('div'); | |
109 | var button = document.createElement('button'); | |
110 | var innerContainer = document.createElement('div'); | |
111 | innerContainer.style.display = isOpen ? "block" : "none"; | |
112 | ||
113 | button.innerHTML = (isOpen ? "∇" : "⊳") + " " + title; | |
114 | button.onclick = function(e) { | |
115 | isOpen = !isOpen; | |
116 | button.innerHTML = (isOpen ? "∇" : "⊳") + " " + title; | |
117 | innerContainer.style.display = isOpen ? "block" : "none"; | |
118 | }; | |
119 | ||
120 | panelContainer.appendChild(button); | |
121 | panelContainer.appendChild(innerContainer); | |
122 | container.appendChild(panelContainer); | |
123 | return innerContainer; | |
124 | }; | |
125 | ||
126 | yoob.makeTextArea = function(container, cols, rows, initial) { | |
127 | var textarea = document.createElement('textarea'); | |
128 | textarea.rows = "" + rows; | |
129 | textarea.cols = "" + cols; | |
130 | if (initial) { | |
131 | textarea.value = initial; | |
132 | } | |
133 | container.appendChild(textarea); | |
134 | return textarea; | |
135 | }; | |
136 | ||
137 | yoob.makeLineBreak = function(container) { | |
138 | var br = document.createElement('br'); | |
139 | container.appendChild(br); | |
140 | return br; | |
141 | }; | |
142 | ||
143 | yoob.makeSelect = function(container, labelText, optionsArray, fun, def) { | |
144 | var label = document.createElement('label'); | |
145 | label.innerHTML = labelText; | |
146 | container.appendChild(label); | |
147 | ||
148 | var select = document.createElement("select"); | |
149 | ||
150 | for (var i = 0; i < optionsArray.length; i++) { | |
151 | var op = document.createElement("option"); | |
152 | op.value = optionsArray[i][0]; | |
153 | op.text = optionsArray[i][1]; | |
154 | if (optionsArray[i].length > 2) { | |
155 | op.selected = optionsArray[i][2]; | |
156 | } else { | |
157 | op.selected = false; | |
158 | } | |
159 | select.options.add(op); | |
160 | } | |
161 | ||
162 | if (fun) { | |
163 | select.onchange = function(e) { | |
164 | fun(optionsArray[select.selectedIndex][0]); | |
165 | }; | |
166 | } | |
167 | ||
168 | if (def) { | |
169 | var i = 0; | |
170 | var opt = select.options[i]; | |
171 | while (opt) { | |
172 | if (opt.value === def) { | |
173 | select.selectedIndex = i; | |
174 | if (fun) fun(def); | |
175 | break; | |
176 | } | |
177 | i++; | |
178 | opt = select.options[i]; | |
179 | } | |
180 | } | |
181 | ||
182 | container.appendChild(select); | |
183 | return select; | |
184 | }; | |
185 | ||
186 | var SliderPlusTextInput = function() { | |
187 | this.init = function(cfg) { | |
188 | this.slider = cfg.slider; | |
189 | this.textInput = cfg.textInput; | |
190 | this.callback = cfg.callback; | |
191 | return this; | |
192 | }; | |
193 | ||
194 | this.set = function(value) { | |
195 | this.slider.value = "" + value; | |
196 | this.textInput.value = "" + value; | |
197 | this.callback(value); | |
198 | }; | |
199 | }; | |
200 | ||
201 | yoob.makeSliderPlusTextInput = function(container, label, min_, max_, size, value, fun) { | |
202 | yoob.makeSpan(container, label); | |
203 | var slider = yoob.makeSlider(container, min_, max_, value); | |
204 | var s = "" + value; | |
205 | var textInput = yoob.makeTextInput(container, size, s); | |
206 | slider.onchange = function(e) { | |
207 | textInput.value = slider.value; | |
208 | fun(parseInt(slider.value, 10)); | |
209 | }; | |
210 | textInput.onchange = function(e) { | |
211 | var v = parseInt(textInput.value, 10); | |
212 | if (v !== NaN) { | |
213 | slider.value = "" + v; | |
214 | fun(v); | |
215 | } | |
216 | }; | |
217 | return new SliderPlusTextInput().init({ | |
218 | 'slider': slider, | |
219 | 'textInput': textInput, | |
220 | 'callback': fun | |
221 | }); | |
222 | }; | |
223 | ||
224 | yoob.makeSVG = function(container) { | |
225 | var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); | |
226 | /* <svg viewBox = "0 0 200 200" version = "1.1"> */ | |
227 | container.appendChild(svg); | |
228 | return svg; | |
229 | }; | |
230 | ||
231 | yoob.makeSVGElem = function(svg, tag, cfg) { | |
232 | var elem = document.createElementNS(svg.namespaceURI, tag); | |
233 | for (var key in cfg) { | |
234 | if (cfg.hasOwnProperty(key)) { | |
235 | elem.setAttribute(key, cfg[key]); | |
236 | } | |
237 | } | |
238 | svg.appendChild(elem); | |
239 | return elem; | |
240 | }; |
12 | 12 | </body> |
13 | 13 | <script src="fingerspelling.js"></script> |
14 | 14 | <script> |
15 | launch('../common-yoob.js-0.11/', 'container'); | |
15 | launch('yoob/', 'container'); | |
16 | 16 | </script> |
0 | /* | |
1 | * This file is part of yoob.js version 0.7 | |
2 | * Available from https://github.com/catseye/yoob.js/ | |
3 | * This file is in the public domain. See http://unlicense.org/ for details. | |
4 | */ | |
5 | if (window.yoob === undefined) yoob = {}; | |
6 | ||
7 | /* | |
8 | * Pretty standard shim to get window.{request,cancelRequest}AnimationFrame | |
9 | * functions, synthesized from the theory and the many examples I've seen. | |
10 | */ | |
11 | ||
12 | window.requestAnimationFrame = | |
13 | window.requestAnimationFrame || | |
14 | window.webkitRequestAnimationFrame || | |
15 | window.mozRequestAnimationFrame || | |
16 | window.oRequestAnimationFrame || | |
17 | window.msRequestAnimationFrame || | |
18 | function(f, elem) { | |
19 | return setTimeout(function() { | |
20 | f(Date.now()); | |
21 | }, 1000 / 60); | |
22 | }; | |
23 | ||
24 | // it was called "cancelRequestAnimationFrame" in the editor's draft: | |
25 | // http://webstuff.nfshost.com/anim-timing/Overview.html | |
26 | // but "cancelAnimationFrame" in the Candidate Recommendation: | |
27 | // http://www.w3.org/TR/animation-timing/ | |
28 | window.cancelAnimationFrame = | |
29 | window.cancelAnimationFrame || | |
30 | window.webkitCancelAnimationFrame || | |
31 | window.mozCancelAnimationFrame || | |
32 | window.oCancelAnimationFrame || | |
33 | window.msCancelAnimationFrame || | |
34 | window.cancelRequestAnimationFrame || | |
35 | window.webkitCancelRequestAnimationFrame || | |
36 | window.mozCancelRequestAnimationFrame || | |
37 | window.oCancelRequestAnimationFrame || | |
38 | window.msCancelRequestAnimationFrame || | |
39 | clearTimeout; | |
40 | window.cancelRequestAnimationFrame = window.cancelAnimationFrame; | |
41 | ||
42 | /* | |
43 | * A yoob.Animation object manages an animation. | |
44 | * | |
45 | * How many things get animated by one yoob.Animation object is up to | |
46 | * you. For animated demos, it may be sufficient to have only one | |
47 | * Animation object which updates many independent graphical objects. | |
48 | * However, it may be useful to have multiple Animation objects, if | |
49 | * the program can be in different states (for example, one title screen | |
50 | * Animation, and a different Animation to use during gameplay.) | |
51 | */ | |
52 | yoob.Animation = function() { | |
53 | /* | |
54 | * Initialize a yoob.Animation. Takes a configuration dictionary: | |
55 | * | |
56 | * mode 'quantum' (default) or 'proportional' | |
57 | * object the object to call methods on | |
58 | * tickTime in msec. for quantum only. default = 1/60th sec | |
59 | * lastTime internal (but see below) | |
60 | * accumDelta internal, only used in quantum mode | |
61 | * | |
62 | * There are two modes that a yoob.Animation can be in, | |
63 | * 'quantum' (the default) and 'proportional'. | |
64 | * | |
65 | * Once the Animation has been started (by calling animation.start()): | |
66 | * | |
67 | * In the 'quantum' mode, the object's draw() method is called on | |
68 | * each animation frame, and the object's update() method is called as | |
69 | * many times as necessary to ensure it is called once for every tickTime | |
70 | * milliseconds that have passed. Neither method is passed any | |
71 | * parameters. | |
72 | * | |
73 | * update() (or draw(), in 'proportional' mode only) may return the | |
74 | * exact object 'false' to force the animation to stop immediately. | |
75 | * | |
76 | * In the 'proportional' mode, the object's draw() method is called on | |
77 | * each animation frame, and the amount of time (in milliseconds) that has | |
78 | * elapsed since the last time it was called (or 0 if it was never | |
79 | * previously called) is passed as the first and only parameter. | |
80 | */ | |
81 | this.init = function(cfg) { | |
82 | this.object = cfg.object; | |
83 | this.lastTime = cfg.lastTime || null; | |
84 | this.accumDelta = cfg.accumDelta || 0; | |
85 | this.tickTime = cfg.tickTime || (1000.0 / 60.0); | |
86 | this.mode = cfg.mode || 'quantum'; | |
87 | this.request = null; | |
88 | return this; | |
89 | }; | |
90 | ||
91 | this.start = function() { | |
92 | if (this.request) { | |
93 | return false; | |
94 | } | |
95 | var $this = this; | |
96 | if (this.mode === 'quantum') { | |
97 | var animFrame = function(time) { | |
98 | $this.object.draw(); | |
99 | if ($this.lastTime === null) { | |
100 | $this.lastTime = time; | |
101 | } | |
102 | $this.accumDelta += (time - $this.lastTime); | |
103 | while ($this.accumDelta > $this.tickTime) { | |
104 | $this.accumDelta -= $this.tickTime; | |
105 | var result = $this.object.update(); | |
106 | if (result === false) { | |
107 | $this.accumDelta = $this.tickTime; | |
108 | $this.request = null; | |
109 | } | |
110 | } | |
111 | $this.lastTime = time; | |
112 | if ($this.request) { | |
113 | $this.request = requestAnimationFrame(animFrame); | |
114 | } | |
115 | }; | |
116 | } else if (this.mode === 'proportional') { | |
117 | var animFrame = function(time) { | |
118 | var timeElapsed = ( | |
119 | $this.lastTime == null ? 0 : time - $this.lastTime | |
120 | ); | |
121 | $this.lastTime = time; | |
122 | var result = $this.object.draw(timeElapsed); | |
123 | if (result === false) { | |
124 | $this.request = null; | |
125 | } | |
126 | if ($this.request) { | |
127 | $this.request = requestAnimationFrame(animFrame); | |
128 | } | |
129 | }; | |
130 | } | |
131 | this.request = requestAnimationFrame(animFrame); | |
132 | return true; | |
133 | }; | |
134 | ||
135 | this.stop = function() { | |
136 | if (this.request) { | |
137 | cancelRequestAnimationFrame(this.request); | |
138 | } | |
139 | this.request = null; | |
140 | }; | |
141 | ||
142 | this.reset = function() { | |
143 | this.stop(); | |
144 | this.lastTime = null; | |
145 | }; | |
146 | }; |
0 | /* | |
1 | * This file is part of yoob.js version 0.9 | |
2 | * Available from https://github.com/catseye/yoob.js/ | |
3 | * This file is in the public domain. See http://unlicense.org/ for details. | |
4 | */ | |
5 | if (window.yoob === undefined) yoob = {}; | |
6 | ||
7 | /* | |
8 | * This class provides objects that resize a canvas to fill (or be centered in) | |
9 | * an area in the viewport, with several options. | |
10 | * | |
11 | * See here for the main use cases I wanted to address: | |
12 | * https://gist.github.com/cpressey/0e2d7f8f9a9a28c863ec | |
13 | * | |
14 | * You don't really need this if all you want is a full-viewport canvas; | |
15 | * that's easy enough to do with CSS and a simple onresize handler (see | |
16 | * above article.) But it does accomodate that if you want. | |
17 | */ | |
18 | yoob.CanvasResizer = function() { | |
19 | /* | |
20 | * Initializes this CanvasResizer and returns it. Does not hook into | |
21 | * any DOM events, so generally you want to call .register() afterwards. | |
22 | * | |
23 | * `canvas`: the canvas to resize | |
24 | * | |
25 | * `onResizeStart`: an optional function which, if supplied, will be | |
26 | * called, passing the new width and height as parameters, after the | |
27 | * new canvas size has been computed, but before the canvas is actually | |
28 | * resized. It may return the exact object `false` to cancel the resize. | |
29 | * | |
30 | * `onResizeEnd`: an optional function which, if supplied, will be | |
31 | * called after the canvas has actually been resized. This can be | |
32 | * used to, for example, redraw the canvas contents. | |
33 | * | |
34 | * `onResizeFail`: an optional function which, if supplied, will be | |
35 | * called after the canvas has failed to be resized (because | |
36 | * allowContraction is false and there is no room for it.) | |
37 | * | |
38 | * `desired{Width,Height}`: the desired width and height of the canvas | |
39 | * | |
40 | * `redimensionCanvas`: should we set the canvas's width and height | |
41 | * properties to the clientWidth and clientHeight of the element | |
42 | * after it has been resized? defaults to true. | |
43 | * | |
44 | * `preserveAspectRatio`: should we try to preserve the aspect ratio | |
45 | * of the canvas after resizing? defaults to true. | |
46 | * | |
47 | * `allowExpansion`: should we ever resize the canvas to a size larger | |
48 | * than the desired width & height? defaults to false. | |
49 | * | |
50 | * `allowContraction`: should we ever resize the canvas to a size smaller | |
51 | * than the desired width & height? defaults to true. | |
52 | * | |
53 | * `missingCanvasElement`: if allowContraction is false, this should be | |
54 | * a DOM element whose `display` style will be changed from `none` to | |
55 | * `inline-block` when the viewport is too small to display the canvas. | |
56 | * | |
57 | * `centerVertically`: should we apply a top margin to the canvas | |
58 | * element, to equal half the available space below it, after resizing | |
59 | * it? defaults to defaults to true. | |
60 | */ | |
61 | this.init = function(cfg) { | |
62 | var nop = function() {}; | |
63 | this.canvas = cfg.canvas; | |
64 | this.redraw = cfg.redraw || nop; | |
65 | this.onResizeStart = cfg.onResizeStart || nop; | |
66 | this.onResizeEnd = cfg.onResizeEnd || nop; | |
67 | this.onResizeFail = cfg.onResizeFail || nop; | |
68 | this.desiredWidth = cfg.desiredWidth || null; | |
69 | this.desiredHeight = cfg.desiredHeight || null; | |
70 | this.redimensionCanvas = cfg.redimensionCanvas === false ? false : true; | |
71 | this.preserveAspectRatio = cfg.preserveAspectRatio === false ? false : true; | |
72 | this.allowExpansion = !!cfg.allowExpansion; | |
73 | this.allowContraction = cfg.allowContraction === false ? false : true; | |
74 | this.missingCanvasElement = cfg.missingCanvasElement; | |
75 | this.centerVertically = cfg.centerVertically === false ? false : true; | |
76 | return this; | |
77 | }; | |
78 | ||
79 | this.register = function(w) { | |
80 | var $this = this; | |
81 | var resizeCanvas = function(e) { | |
82 | $this.resizeCanvas(e); | |
83 | }; | |
84 | window.addEventListener("load", resizeCanvas); | |
85 | window.addEventListener("resize", resizeCanvas); | |
86 | // TODO: orientationchange? | |
87 | return this; | |
88 | }; | |
89 | ||
90 | /* | |
91 | * Returns a two-element list, containing the width and height of the | |
92 | * available space in the viewport, measured from the upper-left corner | |
93 | * of the given element. | |
94 | */ | |
95 | this.getAvailableSpace = function(elem) { | |
96 | var rect = elem.getBoundingClientRect(); | |
97 | var absTop = Math.round(rect.top + window.pageYOffset); | |
98 | var absLeft = Math.round(rect.left + window.pageXOffset); | |
99 | var html = document.documentElement; | |
100 | var availWidth = html.clientWidth - absLeft * 2; | |
101 | var availHeight = html.clientHeight - (absTop + absLeft * 2); | |
102 | return [availWidth, availHeight]; | |
103 | }; | |
104 | ||
105 | /* | |
106 | * Given a destination width and height, return the scaling factor | |
107 | * which is needed to scale the desired width and height to that | |
108 | * destination rectangle. | |
109 | */ | |
110 | this.getFitScale = function(destWidth, destHeight) { | |
111 | var widthFactor = this.desiredWidth / destWidth; | |
112 | var heightFactor = this.desiredHeight / destHeight; | |
113 | return 1 / Math.max(widthFactor, heightFactor); | |
114 | }; | |
115 | ||
116 | this.resizeCanvas = function() { | |
117 | var avail = this.getAvailableSpace(this.canvas.parentElement); | |
118 | var availWidth = avail[0]; | |
119 | var availHeight = avail[1]; | |
120 | var newWidth = availWidth; | |
121 | var newHeight = availHeight; | |
122 | ||
123 | if (this.preserveAspectRatio) { | |
124 | var scale = this.getFitScale(availWidth, availHeight); | |
125 | if (!this.allowExpansion) { | |
126 | scale = Math.min(scale, 1); | |
127 | } | |
128 | if (!this.allowContraction) { | |
129 | scale = Math.max(scale, 1); | |
130 | } | |
131 | newWidth = Math.trunc(this.desiredWidth * scale); | |
132 | newHeight = Math.trunc(this.desiredHeight * scale); | |
133 | } else { | |
134 | // if we don't care about preserving the aspect ratio but do | |
135 | // care about preserving the size, clamp each dimension | |
136 | if (!this.allowExpansion) { | |
137 | newWidth = Math.min(newWidth, this.desiredWidth); | |
138 | newHeight = Math.min(newHeight, this.desiredHeight); | |
139 | } | |
140 | if (!this.allowContraction) { | |
141 | newWidth = Math.max(newWidth, this.desiredWidth); | |
142 | newHeight = Math.max(newHeight, this.desiredHeight); | |
143 | } | |
144 | } | |
145 | ||
146 | if (newWidth > availWidth || newHeight > availHeight) { | |
147 | // due to not allowing contraction, our canvas is still | |
148 | // too big to display. hide it and show the other thing | |
149 | this.canvas.style.display = 'none'; | |
150 | if (this.missingCanvasElement) { | |
151 | this.missingCanvasElement.style.display = 'inline-block'; | |
152 | } | |
153 | this.onResizeFail(); | |
154 | return; | |
155 | } | |
156 | ||
157 | this.canvas.style.display = 'inline-block'; | |
158 | if (this.missingCanvasElement) { | |
159 | this.missingCanvasElement.style.display = 'none'; | |
160 | } | |
161 | ||
162 | var result = this.onResizeStart(newWidth, newHeight); | |
163 | if (result === false) { | |
164 | return; | |
165 | } | |
166 | ||
167 | if (true) { | |
168 | // TODO: add an option to skip this part...? | |
169 | // you might want to skip it if you have these as %'s | |
170 | this.canvas.style.width = newWidth + "px"; | |
171 | this.canvas.style.height = newHeight + "px"; | |
172 | } | |
173 | ||
174 | this.canvas.style.marginTop = "0"; | |
175 | if (this.centerVertically) { | |
176 | if (availHeight > newHeight) { | |
177 | this.canvas.style.marginTop = | |
178 | Math.trunc((availHeight - newHeight) / 2) + "px"; | |
179 | } | |
180 | } | |
181 | ||
182 | var changed = false; | |
183 | if (this.redimensionCanvas) { | |
184 | if (this.canvas.width !== newWidth || this.canvas.height !== newHeight) { | |
185 | this.canvas.width = newWidth; | |
186 | this.canvas.height = newHeight; | |
187 | changed = true; | |
188 | } | |
189 | } | |
190 | ||
191 | this.onResizeEnd(newWidth, newHeight, changed); | |
192 | }; | |
193 | }; |
0 | /* | |
1 | * This file is part of yoob.js version 0.10 | |
2 | * Available from https://github.com/catseye/yoob.js/ | |
3 | * This file is in the public domain. See http://unlicense.org/ for details. | |
4 | */ | |
5 | if (window.yoob === undefined) yoob = {}; | |
6 | ||
7 | /* | |
8 | * Functions for creating elements. | |
9 | */ | |
10 | ||
11 | yoob.makeCanvas = function(container, width, height) { | |
12 | var canvas = document.createElement('canvas'); | |
13 | if (width) { | |
14 | canvas.width = width; | |
15 | } | |
16 | if (height) { | |
17 | canvas.height = height; | |
18 | } | |
19 | container.appendChild(canvas); | |
20 | return canvas; | |
21 | }; | |
22 | ||
23 | yoob.makeButton = function(container, labelText, fun) { | |
24 | var button = document.createElement('button'); | |
25 | button.innerHTML = labelText; | |
26 | container.appendChild(button); | |
27 | if (fun) { | |
28 | button.onclick = fun; | |
29 | } | |
30 | return button; | |
31 | }; | |
32 | ||
33 | yoob.checkBoxNumber = 0; | |
34 | yoob.makeCheckbox = function(container, checked, labelText, fun) { | |
35 | var checkbox = document.createElement('input'); | |
36 | checkbox.type = "checkbox"; | |
37 | checkbox.id = 'cfzzzb_' + yoob.checkBoxNumber; | |
38 | checkbox.checked = checked; | |
39 | var label = document.createElement('label'); | |
40 | label.htmlFor = 'cfzzzb_' + yoob.checkBoxNumber; | |
41 | yoob.checkBoxNumber += 1; | |
42 | label.appendChild(document.createTextNode(labelText)); | |
43 | ||
44 | container.appendChild(checkbox); | |
45 | container.appendChild(label); | |
46 | ||
47 | if (fun) { | |
48 | checkbox.onchange = function(e) { | |
49 | fun(checkbox.checked); | |
50 | }; | |
51 | } | |
52 | return checkbox; | |
53 | }; | |
54 | ||
55 | yoob.makeTextInput = function(container, size, value) { | |
56 | var input = document.createElement('input'); | |
57 | input.size = "" + (size || 12); | |
58 | input.value = value || ""; | |
59 | container.appendChild(input); | |
60 | return input; | |
61 | }; | |
62 | ||
63 | yoob.makeSlider = function(container, min, max, value, fun) { | |
64 | var slider = document.createElement('input'); | |
65 | slider.type = "range"; | |
66 | slider.min = min; | |
67 | slider.max = max; | |
68 | slider.value = value || 0; | |
69 | if (fun) { | |
70 | slider.onchange = function(e) { | |
71 | fun(parseInt(slider.value, 10)); | |
72 | }; | |
73 | } | |
74 | container.appendChild(slider); | |
75 | return slider; | |
76 | }; | |
77 | ||
78 | yoob.makeParagraph = function(container, innerHTML) { | |
79 | var p = document.createElement('p'); | |
80 | p.innerHTML = innerHTML || ''; | |
81 | container.appendChild(p); | |
82 | return p; | |
83 | }; | |
84 | ||
85 | yoob.makeSpan = function(container, innerHTML) { | |
86 | var span = document.createElement('span'); | |
87 | span.innerHTML = innerHTML || ''; | |
88 | container.appendChild(span); | |
89 | return span; | |
90 | }; | |
91 | ||
92 | yoob.makeDiv = function(container, innerHTML) { | |
93 | var div = document.createElement('div'); | |
94 | div.innerHTML = innerHTML || ''; | |
95 | container.appendChild(div); | |
96 | return div; | |
97 | }; | |
98 | ||
99 | yoob.makePre = function(container, innerHTML) { | |
100 | var elem = document.createElement('pre'); | |
101 | elem.innerHTML = innerHTML || ''; | |
102 | container.appendChild(elem); | |
103 | return elem; | |
104 | }; | |
105 | ||
106 | yoob.makePanel = function(container, title, isOpen) { | |
107 | isOpen = !!isOpen; | |
108 | var panelContainer = document.createElement('div'); | |
109 | var button = document.createElement('button'); | |
110 | var innerContainer = document.createElement('div'); | |
111 | innerContainer.style.display = isOpen ? "block" : "none"; | |
112 | ||
113 | button.innerHTML = (isOpen ? "∇" : "⊳") + " " + title; | |
114 | button.onclick = function(e) { | |
115 | isOpen = !isOpen; | |
116 | button.innerHTML = (isOpen ? "∇" : "⊳") + " " + title; | |
117 | innerContainer.style.display = isOpen ? "block" : "none"; | |
118 | }; | |
119 | ||
120 | panelContainer.appendChild(button); | |
121 | panelContainer.appendChild(innerContainer); | |
122 | container.appendChild(panelContainer); | |
123 | return innerContainer; | |
124 | }; | |
125 | ||
126 | yoob.makeTextArea = function(container, cols, rows, initial) { | |
127 | var textarea = document.createElement('textarea'); | |
128 | textarea.rows = "" + rows; | |
129 | textarea.cols = "" + cols; | |
130 | if (initial) { | |
131 | textarea.value = initial; | |
132 | } | |
133 | container.appendChild(textarea); | |
134 | return textarea; | |
135 | }; | |
136 | ||
137 | yoob.makeLineBreak = function(container) { | |
138 | var br = document.createElement('br'); | |
139 | container.appendChild(br); | |
140 | return br; | |
141 | }; | |
142 | ||
143 | yoob.makeSelect = function(container, labelText, optionsArray, fun, def) { | |
144 | var label = document.createElement('label'); | |
145 | label.innerHTML = labelText; | |
146 | container.appendChild(label); | |
147 | ||
148 | var select = document.createElement("select"); | |
149 | ||
150 | for (var i = 0; i < optionsArray.length; i++) { | |
151 | var op = document.createElement("option"); | |
152 | op.value = optionsArray[i][0]; | |
153 | op.text = optionsArray[i][1]; | |
154 | if (optionsArray[i].length > 2) { | |
155 | op.selected = optionsArray[i][2]; | |
156 | } else { | |
157 | op.selected = false; | |
158 | } | |
159 | select.options.add(op); | |
160 | } | |
161 | ||
162 | if (fun) { | |
163 | select.onchange = function(e) { | |
164 | fun(optionsArray[select.selectedIndex][0]); | |
165 | }; | |
166 | } | |
167 | ||
168 | if (def) { | |
169 | var i = 0; | |
170 | var opt = select.options[i]; | |
171 | while (opt) { | |
172 | if (opt.value === def) { | |
173 | select.selectedIndex = i; | |
174 | if (fun) fun(def); | |
175 | break; | |
176 | } | |
177 | i++; | |
178 | opt = select.options[i]; | |
179 | } | |
180 | } | |
181 | ||
182 | container.appendChild(select); | |
183 | return select; | |
184 | }; | |
185 | ||
186 | var SliderPlusTextInput = function() { | |
187 | this.init = function(cfg) { | |
188 | this.slider = cfg.slider; | |
189 | this.textInput = cfg.textInput; | |
190 | this.callback = cfg.callback; | |
191 | return this; | |
192 | }; | |
193 | ||
194 | this.set = function(value) { | |
195 | this.slider.value = "" + value; | |
196 | this.textInput.value = "" + value; | |
197 | this.callback(value); | |
198 | }; | |
199 | }; | |
200 | ||
201 | yoob.makeSliderPlusTextInput = function(container, label, min_, max_, size, value, fun) { | |
202 | yoob.makeSpan(container, label); | |
203 | var slider = yoob.makeSlider(container, min_, max_, value); | |
204 | var s = "" + value; | |
205 | var textInput = yoob.makeTextInput(container, size, s); | |
206 | slider.onchange = function(e) { | |
207 | textInput.value = slider.value; | |
208 | fun(parseInt(slider.value, 10)); | |
209 | }; | |
210 | textInput.onchange = function(e) { | |
211 | var v = parseInt(textInput.value, 10); | |
212 | if (v !== NaN) { | |
213 | slider.value = "" + v; | |
214 | fun(v); | |
215 | } | |
216 | }; | |
217 | return new SliderPlusTextInput().init({ | |
218 | 'slider': slider, | |
219 | 'textInput': textInput, | |
220 | 'callback': fun | |
221 | }); | |
222 | }; | |
223 | ||
224 | yoob.makeSVG = function(container) { | |
225 | var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); | |
226 | /* <svg viewBox = "0 0 200 200" version = "1.1"> */ | |
227 | container.appendChild(svg); | |
228 | return svg; | |
229 | }; | |
230 | ||
231 | yoob.makeSVGElem = function(svg, tag, cfg) { | |
232 | var elem = document.createElementNS(svg.namespaceURI, tag); | |
233 | for (var key in cfg) { | |
234 | if (cfg.hasOwnProperty(key)) { | |
235 | elem.setAttribute(key, cfg[key]); | |
236 | } | |
237 | } | |
238 | svg.appendChild(elem); | |
239 | return elem; | |
240 | }; |
18 | 18 | </body> |
19 | 19 | <script src="heronsis-hermnonicii.js"></script> |
20 | 20 | <script> |
21 | launch('../common-yoob.js-0.11/', 'container'); | |
21 | launch('yoob/', 'container'); | |
22 | 22 | </script> |
0 | /* | |
1 | * This file is part of yoob.js version 0.7 | |
2 | * Available from https://github.com/catseye/yoob.js/ | |
3 | * This file is in the public domain. See http://unlicense.org/ for details. | |
4 | */ | |
5 | if (window.yoob === undefined) yoob = {}; | |
6 | ||
7 | /* | |
8 | * Pretty standard shim to get window.{request,cancelRequest}AnimationFrame | |
9 | * functions, synthesized from the theory and the many examples I've seen. | |
10 | */ | |
11 | ||
12 | window.requestAnimationFrame = | |
13 | window.requestAnimationFrame || | |
14 | window.webkitRequestAnimationFrame || | |
15 | window.mozRequestAnimationFrame || | |
16 | window.oRequestAnimationFrame || | |
17 | window.msRequestAnimationFrame || | |
18 | function(f, elem) { | |
19 | return setTimeout(function() { | |
20 | f(Date.now()); | |
21 | }, 1000 / 60); | |
22 | }; | |
23 | ||
24 | // it was called "cancelRequestAnimationFrame" in the editor's draft: | |
25 | // http://webstuff.nfshost.com/anim-timing/Overview.html | |
26 | // but "cancelAnimationFrame" in the Candidate Recommendation: | |
27 | // http://www.w3.org/TR/animation-timing/ | |
28 | window.cancelAnimationFrame = | |
29 | window.cancelAnimationFrame || | |
30 | window.webkitCancelAnimationFrame || | |
31 | window.mozCancelAnimationFrame || | |
32 | window.oCancelAnimationFrame || | |
33 | window.msCancelAnimationFrame || | |
34 | window.cancelRequestAnimationFrame || | |
35 | window.webkitCancelRequestAnimationFrame || | |
36 | window.mozCancelRequestAnimationFrame || | |
37 | window.oCancelRequestAnimationFrame || | |
38 | window.msCancelRequestAnimationFrame || | |
39 | clearTimeout; | |
40 | window.cancelRequestAnimationFrame = window.cancelAnimationFrame; | |
41 | ||
42 | /* | |
43 | * A yoob.Animation object manages an animation. | |
44 | * | |
45 | * How many things get animated by one yoob.Animation object is up to | |
46 | * you. For animated demos, it may be sufficient to have only one | |
47 | * Animation object which updates many independent graphical objects. | |
48 | * However, it may be useful to have multiple Animation objects, if | |
49 | * the program can be in different states (for example, one title screen | |
50 | * Animation, and a different Animation to use during gameplay.) | |
51 | */ | |
52 | yoob.Animation = function() { | |
53 | /* | |
54 | * Initialize a yoob.Animation. Takes a configuration dictionary: | |
55 | * | |
56 | * mode 'quantum' (default) or 'proportional' | |
57 | * object the object to call methods on | |
58 | * tickTime in msec. for quantum only. default = 1/60th sec | |
59 | * lastTime internal (but see below) | |
60 | * accumDelta internal, only used in quantum mode | |
61 | * | |
62 | * There are two modes that a yoob.Animation can be in, | |
63 | * 'quantum' (the default) and 'proportional'. | |
64 | * | |
65 | * Once the Animation has been started (by calling animation.start()): | |
66 | * | |
67 | * In the 'quantum' mode, the object's draw() method is called on | |
68 | * each animation frame, and the object's update() method is called as | |
69 | * many times as necessary to ensure it is called once for every tickTime | |
70 | * milliseconds that have passed. Neither method is passed any | |
71 | * parameters. | |
72 | * | |
73 | * update() (or draw(), in 'proportional' mode only) may return the | |
74 | * exact object 'false' to force the animation to stop immediately. | |
75 | * | |
76 | * In the 'proportional' mode, the object's draw() method is called on | |
77 | * each animation frame, and the amount of time (in milliseconds) that has | |
78 | * elapsed since the last time it was called (or 0 if it was never | |
79 | * previously called) is passed as the first and only parameter. | |
80 | */ | |
81 | this.init = function(cfg) { | |
82 | this.object = cfg.object; | |
83 | this.lastTime = cfg.lastTime || null; | |
84 | this.accumDelta = cfg.accumDelta || 0; | |
85 | this.tickTime = cfg.tickTime || (1000.0 / 60.0); | |
86 | this.mode = cfg.mode || 'quantum'; | |
87 | this.request = null; | |
88 | return this; | |
89 | }; | |
90 | ||
91 | this.start = function() { | |
92 | if (this.request) { | |
93 | return false; | |
94 | } | |
95 | var $this = this; | |
96 | if (this.mode === 'quantum') { | |
97 | var animFrame = function(time) { | |
98 | $this.object.draw(); | |
99 | if ($this.lastTime === null) { | |
100 | $this.lastTime = time; | |
101 | } | |
102 | $this.accumDelta += (time - $this.lastTime); | |
103 | while ($this.accumDelta > $this.tickTime) { | |
104 | $this.accumDelta -= $this.tickTime; | |
105 | var result = $this.object.update(); | |
106 | if (result === false) { | |
107 | $this.accumDelta = $this.tickTime; | |
108 | $this.request = null; | |
109 | } | |
110 | } | |
111 | $this.lastTime = time; | |
112 | if ($this.request) { | |
113 | $this.request = requestAnimationFrame(animFrame); | |
114 | } | |
115 | }; | |
116 | } else if (this.mode === 'proportional') { | |
117 | var animFrame = function(time) { | |
118 | var timeElapsed = ( | |
119 | $this.lastTime == null ? 0 : time - $this.lastTime | |
120 | ); | |
121 | $this.lastTime = time; | |
122 | var result = $this.object.draw(timeElapsed); | |
123 | if (result === false) { | |
124 | $this.request = null; | |
125 | } | |
126 | if ($this.request) { | |
127 | $this.request = requestAnimationFrame(animFrame); | |
128 | } | |
129 | }; | |
130 | } | |
131 | this.request = requestAnimationFrame(animFrame); | |
132 | return true; | |
133 | }; | |
134 | ||
135 | this.stop = function() { | |
136 | if (this.request) { | |
137 | cancelRequestAnimationFrame(this.request); | |
138 | } | |
139 | this.request = null; | |
140 | }; | |
141 | ||
142 | this.reset = function() { | |
143 | this.stop(); | |
144 | this.lastTime = null; | |
145 | }; | |
146 | }; |
0 | /* | |
1 | * This file is part of yoob.js version 0.9 | |
2 | * Available from https://github.com/catseye/yoob.js/ | |
3 | * This file is in the public domain. See http://unlicense.org/ for details. | |
4 | */ | |
5 | if (window.yoob === undefined) yoob = {}; | |
6 | ||
7 | /* | |
8 | * This class provides objects that resize a canvas to fill (or be centered in) | |
9 | * an area in the viewport, with several options. | |
10 | * | |
11 | * See here for the main use cases I wanted to address: | |
12 | * https://gist.github.com/cpressey/0e2d7f8f9a9a28c863ec | |
13 | * | |
14 | * You don't really need this if all you want is a full-viewport canvas; | |
15 | * that's easy enough to do with CSS and a simple onresize handler (see | |
16 | * above article.) But it does accomodate that if you want. | |
17 | */ | |
18 | yoob.CanvasResizer = function() { | |
19 | /* | |
20 | * Initializes this CanvasResizer and returns it. Does not hook into | |
21 | * any DOM events, so generally you want to call .register() afterwards. | |
22 | * | |
23 | * `canvas`: the canvas to resize | |
24 | * | |
25 | * `onResizeStart`: an optional function which, if supplied, will be | |
26 | * called, passing the new width and height as parameters, after the | |
27 | * new canvas size has been computed, but before the canvas is actually | |
28 | * resized. It may return the exact object `false` to cancel the resize. | |
29 | * | |
30 | * `onResizeEnd`: an optional function which, if supplied, will be | |
31 | * called after the canvas has actually been resized. This can be | |
32 | * used to, for example, redraw the canvas contents. | |
33 | * | |
34 | * `onResizeFail`: an optional function which, if supplied, will be | |
35 | * called after the canvas has failed to be resized (because | |
36 | * allowContraction is false and there is no room for it.) | |
37 | * | |
38 | * `desired{Width,Height}`: the desired width and height of the canvas | |
39 | * | |
40 | * `redimensionCanvas`: should we set the canvas's width and height | |
41 | * properties to the clientWidth and clientHeight of the element | |
42 | * after it has been resized? defaults to true. | |
43 | * | |
44 | * `preserveAspectRatio`: should we try to preserve the aspect ratio | |
45 | * of the canvas after resizing? defaults to true. | |
46 | * | |
47 | * `allowExpansion`: should we ever resize the canvas to a size larger | |
48 | * than the desired width & height? defaults to false. | |
49 | * | |
50 | * `allowContraction`: should we ever resize the canvas to a size smaller | |
51 | * than the desired width & height? defaults to true. | |
52 | * | |
53 | * `missingCanvasElement`: if allowContraction is false, this should be | |
54 | * a DOM element whose `display` style will be changed from `none` to | |
55 | * `inline-block` when the viewport is too small to display the canvas. | |
56 | * | |
57 | * `centerVertically`: should we apply a top margin to the canvas | |
58 | * element, to equal half the available space below it, after resizing | |
59 | * it? defaults to defaults to true. | |
60 | */ | |
61 | this.init = function(cfg) { | |
62 | var nop = function() {}; | |
63 | this.canvas = cfg.canvas; | |
64 | this.redraw = cfg.redraw || nop; | |
65 | this.onResizeStart = cfg.onResizeStart || nop; | |
66 | this.onResizeEnd = cfg.onResizeEnd || nop; | |
67 | this.onResizeFail = cfg.onResizeFail || nop; | |
68 | this.desiredWidth = cfg.desiredWidth || null; | |
69 | this.desiredHeight = cfg.desiredHeight || null; | |
70 | this.redimensionCanvas = cfg.redimensionCanvas === false ? false : true; | |
71 | this.preserveAspectRatio = cfg.preserveAspectRatio === false ? false : true; | |
72 | this.allowExpansion = !!cfg.allowExpansion; | |
73 | this.allowContraction = cfg.allowContraction === false ? false : true; | |
74 | this.missingCanvasElement = cfg.missingCanvasElement; | |
75 | this.centerVertically = cfg.centerVertically === false ? false : true; | |
76 | return this; | |
77 | }; | |
78 | ||
79 | this.register = function(w) { | |
80 | var $this = this; | |
81 | var resizeCanvas = function(e) { | |
82 | $this.resizeCanvas(e); | |
83 | }; | |
84 | window.addEventListener("load", resizeCanvas); | |
85 | window.addEventListener("resize", resizeCanvas); | |
86 | // TODO: orientationchange? | |
87 | return this; | |
88 | }; | |
89 | ||
90 | /* | |
91 | * Returns a two-element list, containing the width and height of the | |
92 | * available space in the viewport, measured from the upper-left corner | |
93 | * of the given element. | |
94 | */ | |
95 | this.getAvailableSpace = function(elem) { | |
96 | var rect = elem.getBoundingClientRect(); | |
97 | var absTop = Math.round(rect.top + window.pageYOffset); | |
98 | var absLeft = Math.round(rect.left + window.pageXOffset); | |
99 | var html = document.documentElement; | |
100 | var availWidth = html.clientWidth - absLeft * 2; | |
101 | var availHeight = html.clientHeight - (absTop + absLeft * 2); | |
102 | return [availWidth, availHeight]; | |
103 | }; | |
104 | ||
105 | /* | |
106 | * Given a destination width and height, return the scaling factor | |
107 | * which is needed to scale the desired width and height to that | |
108 | * destination rectangle. | |
109 | */ | |
110 | this.getFitScale = function(destWidth, destHeight) { | |
111 | var widthFactor = this.desiredWidth / destWidth; | |
112 | var heightFactor = this.desiredHeight / destHeight; | |
113 | return 1 / Math.max(widthFactor, heightFactor); | |
114 | }; | |
115 | ||
116 | this.resizeCanvas = function() { | |
117 | var avail = this.getAvailableSpace(this.canvas.parentElement); | |
118 | var availWidth = avail[0]; | |
119 | var availHeight = avail[1]; | |
120 | var newWidth = availWidth; | |
121 | var newHeight = availHeight; | |
122 | ||
123 | if (this.preserveAspectRatio) { | |
124 | var scale = this.getFitScale(availWidth, availHeight); | |
125 | if (!this.allowExpansion) { | |
126 | scale = Math.min(scale, 1); | |
127 | } | |
128 | if (!this.allowContraction) { | |
129 | scale = Math.max(scale, 1); | |
130 | } | |
131 | newWidth = Math.trunc(this.desiredWidth * scale); | |
132 | newHeight = Math.trunc(this.desiredHeight * scale); | |
133 | } else { | |
134 | // if we don't care about preserving the aspect ratio but do | |
135 | // care about preserving the size, clamp each dimension | |
136 | if (!this.allowExpansion) { | |
137 | newWidth = Math.min(newWidth, this.desiredWidth); | |
138 | newHeight = Math.min(newHeight, this.desiredHeight); | |
139 | } | |
140 | if (!this.allowContraction) { | |
141 | newWidth = Math.max(newWidth, this.desiredWidth); | |
142 | newHeight = Math.max(newHeight, this.desiredHeight); | |
143 | } | |
144 | } | |
145 | ||
146 | if (newWidth > availWidth || newHeight > availHeight) { | |
147 | // due to not allowing contraction, our canvas is still | |
148 | // too big to display. hide it and show the other thing | |
149 | this.canvas.style.display = 'none'; | |
150 | if (this.missingCanvasElement) { | |
151 | this.missingCanvasElement.style.display = 'inline-block'; | |
152 | } | |
153 | this.onResizeFail(); | |
154 | return; | |
155 | } | |
156 | ||
157 | this.canvas.style.display = 'inline-block'; | |
158 | if (this.missingCanvasElement) { | |
159 | this.missingCanvasElement.style.display = 'none'; | |
160 | } | |
161 | ||
162 | var result = this.onResizeStart(newWidth, newHeight); | |
163 | if (result === false) { | |
164 | return; | |
165 | } | |
166 | ||
167 | if (true) { | |
168 | // TODO: add an option to skip this part...? | |
169 | // you might want to skip it if you have these as %'s | |
170 | this.canvas.style.width = newWidth + "px"; | |
171 | this.canvas.style.height = newHeight + "px"; | |
172 | } | |
173 | ||
174 | this.canvas.style.marginTop = "0"; | |
175 | if (this.centerVertically) { | |
176 | if (availHeight > newHeight) { | |
177 | this.canvas.style.marginTop = | |
178 | Math.trunc((availHeight - newHeight) / 2) + "px"; | |
179 | } | |
180 | } | |
181 | ||
182 | var changed = false; | |
183 | if (this.redimensionCanvas) { | |
184 | if (this.canvas.width !== newWidth || this.canvas.height !== newHeight) { | |
185 | this.canvas.width = newWidth; | |
186 | this.canvas.height = newHeight; | |
187 | changed = true; | |
188 | } | |
189 | } | |
190 | ||
191 | this.onResizeEnd(newWidth, newHeight, changed); | |
192 | }; | |
193 | }; |
0 | /* | |
1 | * This file is part of yoob.js version 0.10 | |
2 | * Available from https://github.com/catseye/yoob.js/ | |
3 | * This file is in the public domain. See http://unlicense.org/ for details. | |
4 | */ | |
5 | if (window.yoob === undefined) yoob = {}; | |
6 | ||
7 | /* | |
8 | * Functions for creating elements. | |
9 | */ | |
10 | ||
11 | yoob.makeCanvas = function(container, width, height) { | |
12 | var canvas = document.createElement('canvas'); | |
13 | if (width) { | |
14 | canvas.width = width; | |
15 | } | |
16 | if (height) { | |
17 | canvas.height = height; | |
18 | } | |
19 | container.appendChild(canvas); | |
20 | return canvas; | |
21 | }; | |
22 | ||
23 | yoob.makeButton = function(container, labelText, fun) { | |
24 | var button = document.createElement('button'); | |
25 | button.innerHTML = labelText; | |
26 | container.appendChild(button); | |
27 | if (fun) { | |
28 | button.onclick = fun; | |
29 | } | |
30 | return button; | |
31 | }; | |
32 | ||
33 | yoob.checkBoxNumber = 0; | |
34 | yoob.makeCheckbox = function(container, checked, labelText, fun) { | |
35 | var checkbox = document.createElement('input'); | |
36 | checkbox.type = "checkbox"; | |
37 | checkbox.id = 'cfzzzb_' + yoob.checkBoxNumber; | |
38 | checkbox.checked = checked; | |
39 | var label = document.createElement('label'); | |
40 | label.htmlFor = 'cfzzzb_' + yoob.checkBoxNumber; | |
41 | yoob.checkBoxNumber += 1; | |
42 | label.appendChild(document.createTextNode(labelText)); | |
43 | ||
44 | container.appendChild(checkbox); | |
45 | container.appendChild(label); | |
46 | ||
47 | if (fun) { | |
48 | checkbox.onchange = function(e) { | |
49 | fun(checkbox.checked); | |
50 | }; | |
51 | } | |
52 | return checkbox; | |
53 | }; | |
54 | ||
55 | yoob.makeTextInput = function(container, size, value) { | |
56 | var input = document.createElement('input'); | |
57 | input.size = "" + (size || 12); | |
58 | input.value = value || ""; | |
59 | container.appendChild(input); | |
60 | return input; | |
61 | }; | |
62 | ||
63 | yoob.makeSlider = function(container, min, max, value, fun) { | |
64 | var slider = document.createElement('input'); | |
65 | slider.type = "range"; | |
66 | slider.min = min; | |
67 | slider.max = max; | |
68 | slider.value = value || 0; | |
69 | if (fun) { | |
70 | slider.onchange = function(e) { | |
71 | fun(parseInt(slider.value, 10)); | |
72 | }; | |
73 | } | |
74 | container.appendChild(slider); | |
75 | return slider; | |
76 | }; | |
77 | ||
78 | yoob.makeParagraph = function(container, innerHTML) { | |
79 | var p = document.createElement('p'); | |
80 | p.innerHTML = innerHTML || ''; | |
81 | container.appendChild(p); | |
82 | return p; | |
83 | }; | |
84 | ||
85 | yoob.makeSpan = function(container, innerHTML) { | |
86 | var span = document.createElement('span'); | |
87 | span.innerHTML = innerHTML || ''; | |
88 | container.appendChild(span); | |
89 | return span; | |
90 | }; | |
91 | ||
92 | yoob.makeDiv = function(container, innerHTML) { | |
93 | var div = document.createElement('div'); | |
94 | div.innerHTML = innerHTML || ''; | |
95 | container.appendChild(div); | |
96 | return div; | |
97 | }; | |
98 | ||
99 | yoob.makePre = function(container, innerHTML) { | |
100 | var elem = document.createElement('pre'); | |
101 | elem.innerHTML = innerHTML || ''; | |
102 | container.appendChild(elem); | |
103 | return elem; | |
104 | }; | |
105 | ||
106 | yoob.makePanel = function(container, title, isOpen) { | |
107 | isOpen = !!isOpen; | |
108 | var panelContainer = document.createElement('div'); | |
109 | var button = document.createElement('button'); | |
110 | var innerContainer = document.createElement('div'); | |
111 | innerContainer.style.display = isOpen ? "block" : "none"; | |
112 | ||
113 | button.innerHTML = (isOpen ? "∇" : "⊳") + " " + title; | |
114 | button.onclick = function(e) { | |
115 | isOpen = !isOpen; | |
116 | button.innerHTML = (isOpen ? "∇" : "⊳") + " " + title; | |
117 | innerContainer.style.display = isOpen ? "block" : "none"; | |
118 | }; | |
119 | ||
120 | panelContainer.appendChild(button); | |
121 | panelContainer.appendChild(innerContainer); | |
122 | container.appendChild(panelContainer); | |
123 | return innerContainer; | |
124 | }; | |
125 | ||
126 | yoob.makeTextArea = function(container, cols, rows, initial) { | |
127 | var textarea = document.createElement('textarea'); | |
128 | textarea.rows = "" + rows; | |
129 | textarea.cols = "" + cols; | |
130 | if (initial) { | |
131 | textarea.value = initial; | |
132 | } | |
133 | container.appendChild(textarea); | |
134 | return textarea; | |
135 | }; | |
136 | ||
137 | yoob.makeLineBreak = function(container) { | |
138 | var br = document.createElement('br'); | |
139 | container.appendChild(br); | |
140 | return br; | |
141 | }; | |
142 | ||
143 | yoob.makeSelect = function(container, labelText, optionsArray, fun, def) { | |
144 | var label = document.createElement('label'); | |
145 | label.innerHTML = labelText; | |
146 | container.appendChild(label); | |
147 | ||
148 | var select = document.createElement("select"); | |
149 | ||
150 | for (var i = 0; i < optionsArray.length; i++) { | |
151 | var op = document.createElement("option"); | |
152 | op.value = optionsArray[i][0]; | |
153 | op.text = optionsArray[i][1]; | |
154 | if (optionsArray[i].length > 2) { | |
155 | op.selected = optionsArray[i][2]; | |
156 | } else { | |
157 | op.selected = false; | |
158 | } | |
159 | select.options.add(op); | |
160 | } | |
161 | ||
162 | if (fun) { | |
163 | select.onchange = function(e) { | |
164 | fun(optionsArray[select.selectedIndex][0]); | |
165 | }; | |
166 | } | |
167 | ||
168 | if (def) { | |
169 | var i = 0; | |
170 | var opt = select.options[i]; | |
171 | while (opt) { | |
172 | if (opt.value === def) { | |
173 | select.selectedIndex = i; | |
174 | if (fun) fun(def); | |
175 | break; | |
176 | } | |
177 | i++; | |
178 | opt = select.options[i]; | |
179 | } | |
180 | } | |
181 | ||
182 | container.appendChild(select); | |
183 | return select; | |
184 | }; | |
185 | ||
186 | var SliderPlusTextInput = function() { | |
187 | this.init = function(cfg) { | |
188 | this.slider = cfg.slider; | |
189 | this.textInput = cfg.textInput; | |
190 | this.callback = cfg.callback; | |
191 | return this; | |
192 | }; | |
193 | ||
194 | this.set = function(value) { | |
195 | this.slider.value = "" + value; | |
196 | this.textInput.value = "" + value; | |
197 | this.callback(value); | |
198 | }; | |
199 | }; | |
200 | ||
201 | yoob.makeSliderPlusTextInput = function(container, label, min_, max_, size, value, fun) { | |
202 | yoob.makeSpan(container, label); | |
203 | var slider = yoob.makeSlider(container, min_, max_, value); | |
204 | var s = "" + value; | |
205 | var textInput = yoob.makeTextInput(container, size, s); | |
206 | slider.onchange = function(e) { | |
207 | textInput.value = slider.value; | |
208 | fun(parseInt(slider.value, 10)); | |
209 | }; | |
210 | textInput.onchange = function(e) { | |
211 | var v = parseInt(textInput.value, 10); | |
212 | if (v !== NaN) { | |
213 | slider.value = "" + v; | |
214 | fun(v); | |
215 | } | |
216 | }; | |
217 | return new SliderPlusTextInput().init({ | |
218 | 'slider': slider, | |
219 | 'textInput': textInput, | |
220 | 'callback': fun | |
221 | }); | |
222 | }; | |
223 | ||
224 | yoob.makeSVG = function(container) { | |
225 | var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); | |
226 | /* <svg viewBox = "0 0 200 200" version = "1.1"> */ | |
227 | container.appendChild(svg); | |
228 | return svg; | |
229 | }; | |
230 | ||
231 | yoob.makeSVGElem = function(svg, tag, cfg) { | |
232 | var elem = document.createElementNS(svg.namespaceURI, tag); | |
233 | for (var key in cfg) { | |
234 | if (cfg.hasOwnProperty(key)) { | |
235 | elem.setAttribute(key, cfg[key]); | |
236 | } | |
237 | } | |
238 | svg.appendChild(elem); | |
239 | return elem; | |
240 | }; |
0 | /* | |
1 | * This file is part of yoob.js version 0.9 | |
2 | * Available from https://github.com/catseye/yoob.js/ | |
3 | * This file is in the public domain. See http://unlicense.org/ for details. | |
4 | */ | |
5 | if (window.yoob === undefined) yoob = {}; | |
6 | ||
7 | /* | |
8 | * This is really just an interface; duck-typing-wise, you can use any | |
9 | * Javascript object you want as a sprite, so long as it exposes these | |
10 | * methods. | |
11 | */ | |
12 | yoob.Sprite = function() { | |
13 | ||
14 | /* | |
15 | * x and y always represent the CENTRE of the Sprite(). | |
16 | * Chainable. | |
17 | */ | |
18 | this.init = function(cfg) { | |
19 | this.x = cfg.x; | |
20 | this.y = cfg.y; | |
21 | this.width = cfg.width; | |
22 | this.height = cfg.height; | |
23 | this.dx = cfg.dx || 0; | |
24 | this.dy = cfg.dy || 0; | |
25 | this.isDraggable = cfg.isDraggable || false; | |
26 | this.isClickable = cfg.isClickable || false; | |
27 | this.fillStyle = cfg.fillStyle || "green"; | |
28 | this.visible = (cfg.visible === undefined ? true : (!!cfg.visible)); | |
29 | this._isBeingDragged = false; | |
30 | return this; | |
31 | }; | |
32 | ||
33 | this.getX = function() { | |
34 | return this.x; | |
35 | }; | |
36 | ||
37 | this.getLeftX = function() { | |
38 | return this.x - this.width / 2; | |
39 | }; | |
40 | ||
41 | this.getRightX = function() { | |
42 | return this.x + this.width / 2; | |
43 | }; | |
44 | ||
45 | this.getY = function() { | |
46 | return this.y; | |
47 | }; | |
48 | ||
49 | this.getTopY = function() { | |
50 | return this.y - this.height / 2; | |
51 | }; | |
52 | ||
53 | this.getBottomY = function() { | |
54 | return this.y + this.height / 2; | |
55 | }; | |
56 | ||
57 | this.getWidth = function() { | |
58 | return this.width; | |
59 | }; | |
60 | ||
61 | this.getHeight = function() { | |
62 | return this.height; | |
63 | }; | |
64 | ||
65 | this.isBeingDragged = function() { | |
66 | return this._isBeingDragged; | |
67 | }; | |
68 | ||
69 | /* | |
70 | * Chainable. | |
71 | */ | |
72 | this.setPosition = function(x, y) { | |
73 | this.x = x; | |
74 | this.y = y; | |
75 | return this; | |
76 | }; | |
77 | ||
78 | /* | |
79 | * Chainable. | |
80 | */ | |
81 | this.setDimensions = function(width, height) { | |
82 | this.width = width; | |
83 | this.height = height; | |
84 | return this; | |
85 | }; | |
86 | ||
87 | /* | |
88 | * Chainable. | |
89 | */ | |
90 | this.setVelocity = function(dx, dy) { | |
91 | this.dx = dx; | |
92 | this.dy = dy; | |
93 | return this; | |
94 | }; | |
95 | ||
96 | /* | |
97 | * Chainable. | |
98 | */ | |
99 | this.setDestination = function(x, y, ticks) { | |
100 | this.destX = x; | |
101 | this.destY = y; | |
102 | this.setVelocity((this.destX - this.getX()) / ticks, (this.destY - this.getY()) / ticks); | |
103 | this.destCounter = ticks; | |
104 | return this; | |
105 | }; | |
106 | ||
107 | this.move = function(x, y) { | |
108 | this.x += this.dx; | |
109 | this.y += this.dy; | |
110 | this.onmove(); | |
111 | if (this.destCounter !== undefined) { | |
112 | this.destCounter--; | |
113 | if (this.destCounter <= 0) { | |
114 | this.destCounter = undefined; | |
115 | this.setPosition(this.destX, this.destY).setVelocity(0, 0); | |
116 | this.onreachdestination(); | |
117 | } | |
118 | } | |
119 | }; | |
120 | ||
121 | // override this if your shape is not a rectangle | |
122 | this.containsPoint = function(x, y) { | |
123 | return (x >= this.getLeftX() && x <= this.getRightX() && | |
124 | y >= this.getTopY() && y <= this.getBottomY()); | |
125 | }; | |
126 | ||
127 | // NOTE: this assumes that `sprite` is larger or equal in size to `this`. | |
128 | // you may need to override this in a sophisticated way if you | |
129 | // expect it to detect sprites of different shapes intersecting | |
130 | this.intersects = function(sprite) { | |
131 | var x1 = this.getLeftX(); | |
132 | var x2 = this.getRightX(); | |
133 | var y1 = this.getTopY(); | |
134 | var y2 = this.getBottomY(); | |
135 | return (sprite.containsPoint(x1, y1) || sprite.containsPoint(x2, y1) || | |
136 | sprite.containsPoint(x1, y2) || sprite.containsPoint(x2, y2)); | |
137 | }; | |
138 | ||
139 | // you will probably want to override this | |
140 | // if you do, it's up to you to honour this.visible. | |
141 | this.draw = function(ctx) { | |
142 | if (!this.visible) return; | |
143 | ctx.fillStyle = this.fillStyle || "green"; | |
144 | ctx.fillRect(this.getLeftX(), this.getTopY(), this.getWidth(), this.getHeight()); | |
145 | }; | |
146 | ||
147 | // event handlers. override to detect these events. | |
148 | this.ongrab = function() { | |
149 | }; | |
150 | this.ondrag = function() { | |
151 | }; | |
152 | this.ondrop = function() { | |
153 | }; | |
154 | this.onclick = function() { | |
155 | }; | |
156 | this.onmove = function() { | |
157 | }; | |
158 | this.onreachdestination = function() { | |
159 | }; | |
160 | ||
161 | }; | |
162 | ||
163 | /* | |
164 | * This still has a few shortcomings at the moment. | |
165 | */ | |
166 | yoob.SpriteManager = function() { | |
167 | /* | |
168 | * Attach this SpriteManager to a canvas. | |
169 | */ | |
170 | this.init = function(cfg) { | |
171 | this.canvasX = undefined; | |
172 | this.canvasY = undefined; | |
173 | this.offsetX = undefined; | |
174 | this.offsetY = undefined; | |
175 | this.dragging = undefined; | |
176 | this.sprites = []; | |
177 | ||
178 | this.canvas = cfg.canvas; | |
179 | ||
180 | var $this = this; | |
181 | this.canvas.addEventListener('mousedown', function(e) { | |
182 | return $this.onmousedown(e, e); | |
183 | }); | |
184 | this.canvas.addEventListener('touchstart', function(e) { | |
185 | return $this.onmousedown(e, e.touches[0]); | |
186 | }); | |
187 | ||
188 | this.canvas.addEventListener('mousemove', function(e) { | |
189 | return $this.onmousemove(e, e); | |
190 | }); | |
191 | this.canvas.addEventListener('touchmove', function(e) { | |
192 | return $this.onmousemove(e, e.touches[0]); | |
193 | }); | |
194 | ||
195 | this.canvas.addEventListener('mouseup', function(e) { | |
196 | return $this.onmouseup(e, e); | |
197 | }); | |
198 | this.canvas.addEventListener('touchend', function(e) { | |
199 | return $this.onmouseup(e, e.touches[0]); | |
200 | }); | |
201 | ||
202 | return this; | |
203 | }; | |
204 | ||
205 | /* | |
206 | * Common handling of mouse and touch events | |
207 | */ | |
208 | this.onmousedown = function(e, touch) { | |
209 | e.preventDefault(); | |
210 | this.canvasX = touch.pageX - this.canvas.offsetLeft; | |
211 | this.canvasY = touch.pageY - this.canvas.offsetTop; | |
212 | ||
213 | var sprite = this.getSpriteAt(this.canvasX, this.canvasY); | |
214 | if (sprite === undefined) return; | |
215 | if (sprite.isDraggable) { | |
216 | this.dragging = sprite; | |
217 | this.dragging._isBeingDragged = true; | |
218 | this.dragging.ongrab(); | |
219 | this.offsetX = sprite.getX() - this.canvasX; | |
220 | this.offsetY = sprite.getY() - this.canvasY; | |
221 | this.canvas.style.cursor = "move"; | |
222 | } else if (sprite.isClickable) { | |
223 | sprite.onclick(e); | |
224 | } | |
225 | }; | |
226 | ||
227 | this.onmousemove = function(e, touch) { | |
228 | e.preventDefault(); | |
229 | if (!this.dragging) return; | |
230 | ||
231 | this.canvasX = touch.pageX - this.canvas.offsetLeft; | |
232 | this.canvasY = touch.pageY - this.canvas.offsetTop; | |
233 | ||
234 | this.dragging.setPosition(this.canvasX + this.offsetX, | |
235 | this.canvasY + this.offsetY); | |
236 | }; | |
237 | ||
238 | this.onmouseup = function(e, touch) { | |
239 | e.preventDefault(); | |
240 | this.canvas.onmousemove = null; | |
241 | this.canvasX = undefined; | |
242 | this.canvasY = undefined; | |
243 | this.offsetX = undefined; | |
244 | this.offsetY = undefined; | |
245 | if (this.dragging !== undefined) { | |
246 | this.dragging.ondrop(); | |
247 | this.dragging._isBeingDragged = false; | |
248 | } | |
249 | this.dragging = undefined; | |
250 | this.canvas.style.cursor = "auto"; | |
251 | }; | |
252 | ||
253 | this.move = function() { | |
254 | this.foreach(function(sprite) { | |
255 | sprite.move(); | |
256 | }); | |
257 | }; | |
258 | ||
259 | this.draw = function(ctx) { | |
260 | if (ctx === undefined) { | |
261 | ctx = this.canvas.getContext('2d'); | |
262 | } | |
263 | this.foreach(function(sprite) { | |
264 | sprite.draw(ctx); | |
265 | }); | |
266 | }; | |
267 | ||
268 | this.addSprite = function(sprite) { | |
269 | this.sprites.push(sprite); | |
270 | }; | |
271 | ||
272 | this.removeSprite = function(sprite) { | |
273 | var index = undefined; | |
274 | for (var i = 0; i < this.sprites.length; i++) { | |
275 | if (this.sprites[i] === sprite) { | |
276 | index = i; | |
277 | break; | |
278 | } | |
279 | } | |
280 | if (index !== undefined) { | |
281 | this.sprites.splice(index, 1); | |
282 | } | |
283 | }; | |
284 | ||
285 | this.clearSprites = function() { | |
286 | this.sprites = []; | |
287 | }; | |
288 | ||
289 | this.moveToFront = function(sprite) { | |
290 | this.removeSprite(sprite); | |
291 | this.sprites.push(sprite); | |
292 | }; | |
293 | ||
294 | this.moveToBack = function(sprite) { | |
295 | this.removeSprite(sprite); | |
296 | this.sprites.unshift(sprite); | |
297 | }; | |
298 | ||
299 | this.getSpriteAt = function(x, y) { | |
300 | for (var i = this.sprites.length-1; i >= 0; i--) { | |
301 | var sprite = this.sprites[i]; | |
302 | if (sprite.containsPoint(x, y)) { | |
303 | return sprite; | |
304 | } | |
305 | } | |
306 | return undefined; | |
307 | }; | |
308 | ||
309 | this.foreach = function(fun) { | |
310 | for (var i = this.sprites.length-1; i >= 0; i--) { | |
311 | var sprite = this.sprites[i]; | |
312 | var result = fun(sprite); | |
313 | if (result === 'remove') { | |
314 | this.removeSprite(sprite); | |
315 | } | |
316 | if (result === 'return') { | |
317 | return sprite; | |
318 | } | |
319 | } | |
320 | }; | |
321 | ||
322 | }; |
18 | 18 | </body> |
19 | 19 | <script src="hirsute-miasma.js"></script> |
20 | 20 | <script> |
21 | launch('../common-yoob.js-0.11/', 'container'); | |
21 | launch('yoob/', 'container'); | |
22 | 22 | </script> |
0 | /* | |
1 | * This file is part of yoob.js version 0.7 | |
2 | * Available from https://github.com/catseye/yoob.js/ | |
3 | * This file is in the public domain. See http://unlicense.org/ for details. | |
4 | */ | |
5 | if (window.yoob === undefined) yoob = {}; | |
6 | ||
7 | /* | |
8 | * Pretty standard shim to get window.{request,cancelRequest}AnimationFrame | |
9 | * functions, synthesized from the theory and the many examples I've seen. | |
10 | */ | |
11 | ||
12 | window.requestAnimationFrame = | |
13 | window.requestAnimationFrame || | |
14 | window.webkitRequestAnimationFrame || | |
15 | window.mozRequestAnimationFrame || | |
16 | window.oRequestAnimationFrame || | |
17 | window.msRequestAnimationFrame || | |
18 | function(f, elem) { | |
19 | return setTimeout(function() { | |
20 | f(Date.now()); | |
21 | }, 1000 / 60); | |
22 | }; | |
23 | ||
24 | // it was called "cancelRequestAnimationFrame" in the editor's draft: | |
25 | // http://webstuff.nfshost.com/anim-timing/Overview.html | |
26 | // but "cancelAnimationFrame" in the Candidate Recommendation: | |
27 | // http://www.w3.org/TR/animation-timing/ | |
28 | window.cancelAnimationFrame = | |
29 | window.cancelAnimationFrame || | |
30 | window.webkitCancelAnimationFrame || | |
31 | window.mozCancelAnimationFrame || | |
32 | window.oCancelAnimationFrame || | |
33 | window.msCancelAnimationFrame || | |
34 | window.cancelRequestAnimationFrame || | |
35 | window.webkitCancelRequestAnimationFrame || | |
36 | window.mozCancelRequestAnimationFrame || | |
37 | window.oCancelRequestAnimationFrame || | |
38 | window.msCancelRequestAnimationFrame || | |
39 | clearTimeout; | |
40 | window.cancelRequestAnimationFrame = window.cancelAnimationFrame; | |
41 | ||
42 | /* | |
43 | * A yoob.Animation object manages an animation. | |
44 | * | |
45 | * How many things get animated by one yoob.Animation object is up to | |
46 | * you. For animated demos, it may be sufficient to have only one | |
47 | * Animation object which updates many independent graphical objects. | |
48 | * However, it may be useful to have multiple Animation objects, if | |
49 | * the program can be in different states (for example, one title screen | |
50 | * Animation, and a different Animation to use during gameplay.) | |
51 | */ | |
52 | yoob.Animation = function() { | |
53 | /* | |
54 | * Initialize a yoob.Animation. Takes a configuration dictionary: | |
55 | * | |
56 | * mode 'quantum' (default) or 'proportional' | |
57 | * object the object to call methods on | |
58 | * tickTime in msec. for quantum only. default = 1/60th sec | |
59 | * lastTime internal (but see below) | |
60 | * accumDelta internal, only used in quantum mode | |
61 | * | |
62 | * There are two modes that a yoob.Animation can be in, | |
63 | * 'quantum' (the default) and 'proportional'. | |
64 | * | |
65 | * Once the Animation has been started (by calling animation.start()): | |
66 | * | |
67 | * In the 'quantum' mode, the object's draw() method is called on | |
68 | * each animation frame, and the object's update() method is called as | |
69 | * many times as necessary to ensure it is called once for every tickTime | |
70 | * milliseconds that have passed. Neither method is passed any | |
71 | * parameters. | |
72 | * | |
73 | * update() (or draw(), in 'proportional' mode only) may return the | |
74 | * exact object 'false' to force the animation to stop immediately. | |
75 | * | |
76 | * In the 'proportional' mode, the object's draw() method is called on | |
77 | * each animation frame, and the amount of time (in milliseconds) that has | |
78 | * elapsed since the last time it was called (or 0 if it was never | |
79 | * previously called) is passed as the first and only parameter. | |
80 | */ | |
81 | this.init = function(cfg) { | |
82 | this.object = cfg.object; | |
83 | this.lastTime = cfg.lastTime || null; | |
84 | this.accumDelta = cfg.accumDelta || 0; | |
85 | this.tickTime = cfg.tickTime || (1000.0 / 60.0); | |
86 | this.mode = cfg.mode || 'quantum'; | |
87 | this.request = null; | |
88 | return this; | |
89 | }; | |
90 | ||
91 | this.start = function() { | |
92 | if (this.request) { | |
93 | return false; | |
94 | } | |
95 | var $this = this; | |
96 | if (this.mode === 'quantum') { | |
97 | var animFrame = function(time) { | |
98 | $this.object.draw(); | |
99 | if ($this.lastTime === null) { | |
100 | $this.lastTime = time; | |
101 | } | |
102 | $this.accumDelta += (time - $this.lastTime); | |
103 | while ($this.accumDelta > $this.tickTime) { | |
104 | $this.accumDelta -= $this.tickTime; | |
105 | var result = $this.object.update(); | |
106 | if (result === false) { | |
107 | $this.accumDelta = $this.tickTime; | |
108 | $this.request = null; | |
109 | } | |
110 | } | |
111 | $this.lastTime = time; | |
112 | if ($this.request) { | |
113 | $this.request = requestAnimationFrame(animFrame); | |
114 | } | |
115 | }; | |
116 | } else if (this.mode === 'proportional') { | |
117 | var animFrame = function(time) { | |
118 | var timeElapsed = ( | |
119 | $this.lastTime == null ? 0 : time - $this.lastTime | |
120 | ); | |
121 | $this.lastTime = time; | |
122 | var result = $this.object.draw(timeElapsed); | |
123 | if (result === false) { | |
124 | $this.request = null; | |
125 | } | |
126 | if ($this.request) { | |
127 | $this.request = requestAnimationFrame(animFrame); | |
128 | } | |
129 | }; | |
130 | } | |
131 | this.request = requestAnimationFrame(animFrame); | |
132 | return true; | |
133 | }; | |
134 | ||
135 | this.stop = function() { | |
136 | if (this.request) { | |
137 | cancelRequestAnimationFrame(this.request); | |
138 | } | |
139 | this.request = null; | |
140 | }; | |
141 | ||
142 | this.reset = function() { | |
143 | this.stop(); | |
144 | this.lastTime = null; | |
145 | }; | |
146 | }; |
0 | /* | |
1 | * This file is part of yoob.js version 0.10 | |
2 | * Available from https://github.com/catseye/yoob.js/ | |
3 | * This file is in the public domain. See http://unlicense.org/ for details. | |
4 | */ | |
5 | if (window.yoob === undefined) yoob = {}; | |
6 | ||
7 | /* | |
8 | * Functions for creating elements. | |
9 | */ | |
10 | ||
11 | yoob.makeCanvas = function(container, width, height) { | |
12 | var canvas = document.createElement('canvas'); | |
13 | if (width) { | |
14 | canvas.width = width; | |
15 | } | |
16 | if (height) { | |
17 | canvas.height = height; | |
18 | } | |
19 | container.appendChild(canvas); | |
20 | return canvas; | |
21 | }; | |
22 | ||
23 | yoob.makeButton = function(container, labelText, fun) { | |
24 | var button = document.createElement('button'); | |
25 | button.innerHTML = labelText; | |
26 | container.appendChild(button); | |
27 | if (fun) { | |
28 | button.onclick = fun; | |
29 | } | |
30 | return button; | |
31 | }; | |
32 | ||
33 | yoob.checkBoxNumber = 0; | |
34 | yoob.makeCheckbox = function(container, checked, labelText, fun) { | |
35 | var checkbox = document.createElement('input'); | |
36 | checkbox.type = "checkbox"; | |
37 | checkbox.id = 'cfzzzb_' + yoob.checkBoxNumber; | |
38 | checkbox.checked = checked; | |
39 | var label = document.createElement('label'); | |
40 | label.htmlFor = 'cfzzzb_' + yoob.checkBoxNumber; | |
41 | yoob.checkBoxNumber += 1; | |
42 | label.appendChild(document.createTextNode(labelText)); | |
43 | ||
44 | container.appendChild(checkbox); | |
45 | container.appendChild(label); | |
46 | ||
47 | if (fun) { | |
48 | checkbox.onchange = function(e) { | |
49 | fun(checkbox.checked); | |
50 | }; | |
51 | } | |
52 | return checkbox; | |
53 | }; | |
54 | ||
55 | yoob.makeTextInput = function(container, size, value) { | |
56 | var input = document.createElement('input'); | |
57 | input.size = "" + (size || 12); | |
58 | input.value = value || ""; | |
59 | container.appendChild(input); | |
60 | return input; | |
61 | }; | |
62 | ||
63 | yoob.makeSlider = function(container, min, max, value, fun) { | |
64 | var slider = document.createElement('input'); | |
65 | slider.type = "range"; | |
66 | slider.min = min; | |
67 | slider.max = max; | |
68 | slider.value = value || 0; | |
69 | if (fun) { | |
70 | slider.onchange = function(e) { | |
71 | fun(parseInt(slider.value, 10)); | |
72 | }; | |
73 | } | |
74 | container.appendChild(slider); | |
75 | return slider; | |
76 | }; | |
77 | ||
78 | yoob.makeParagraph = function(container, innerHTML) { | |
79 | var p = document.createElement('p'); | |
80 | p.innerHTML = innerHTML || ''; | |
81 | container.appendChild(p); | |
82 | return p; | |
83 | }; | |
84 | ||
85 | yoob.makeSpan = function(container, innerHTML) { | |
86 | var span = document.createElement('span'); | |
87 | span.innerHTML = innerHTML || ''; | |
88 | container.appendChild(span); | |
89 | return span; | |
90 | }; | |
91 | ||
92 | yoob.makeDiv = function(container, innerHTML) { | |
93 | var div = document.createElement('div'); | |
94 | div.innerHTML = innerHTML || ''; | |
95 | container.appendChild(div); | |
96 | return div; | |
97 | }; | |
98 | ||
99 | yoob.makePre = function(container, innerHTML) { | |
100 | var elem = document.createElement('pre'); | |
101 | elem.innerHTML = innerHTML || ''; | |
102 | container.appendChild(elem); | |
103 | return elem; | |
104 | }; | |
105 | ||
106 | yoob.makePanel = function(container, title, isOpen) { | |
107 | isOpen = !!isOpen; | |
108 | var panelContainer = document.createElement('div'); | |
109 | var button = document.createElement('button'); | |
110 | var innerContainer = document.createElement('div'); | |
111 | innerContainer.style.display = isOpen ? "block" : "none"; | |
112 | ||
113 | button.innerHTML = (isOpen ? "∇" : "⊳") + " " + title; | |
114 | button.onclick = function(e) { | |
115 | isOpen = !isOpen; | |
116 | button.innerHTML = (isOpen ? "∇" : "⊳") + " " + title; | |
117 | innerContainer.style.display = isOpen ? "block" : "none"; | |
118 | }; | |
119 | ||
120 | panelContainer.appendChild(button); | |
121 | panelContainer.appendChild(innerContainer); | |
122 | container.appendChild(panelContainer); | |
123 | return innerContainer; | |
124 | }; | |
125 | ||
126 | yoob.makeTextArea = function(container, cols, rows, initial) { | |
127 | var textarea = document.createElement('textarea'); | |
128 | textarea.rows = "" + rows; | |
129 | textarea.cols = "" + cols; | |
130 | if (initial) { | |
131 | textarea.value = initial; | |
132 | } | |
133 | container.appendChild(textarea); | |
134 | return textarea; | |
135 | }; | |
136 | ||
137 | yoob.makeLineBreak = function(container) { | |
138 | var br = document.createElement('br'); | |
139 | container.appendChild(br); | |
140 | return br; | |
141 | }; | |
142 | ||
143 | yoob.makeSelect = function(container, labelText, optionsArray, fun, def) { | |
144 | var label = document.createElement('label'); | |
145 | label.innerHTML = labelText; | |
146 | container.appendChild(label); | |
147 | ||
148 | var select = document.createElement("select"); | |
149 | ||
150 | for (var i = 0; i < optionsArray.length; i++) { | |
151 | var op = document.createElement("option"); | |
152 | op.value = optionsArray[i][0]; | |
153 | op.text = optionsArray[i][1]; | |
154 | if (optionsArray[i].length > 2) { | |
155 | op.selected = optionsArray[i][2]; | |
156 | } else { | |
157 | op.selected = false; | |
158 | } | |
159 | select.options.add(op); | |
160 | } | |
161 | ||
162 | if (fun) { | |
163 | select.onchange = function(e) { | |
164 | fun(optionsArray[select.selectedIndex][0]); | |
165 | }; | |
166 | } | |
167 | ||
168 | if (def) { | |
169 | var i = 0; | |
170 | var opt = select.options[i]; | |
171 | while (opt) { | |
172 | if (opt.value === def) { | |
173 | select.selectedIndex = i; | |
174 | if (fun) fun(def); | |
175 | break; | |
176 | } | |
177 | i++; | |
178 | opt = select.options[i]; | |
179 | } | |
180 | } | |
181 | ||
182 | container.appendChild(select); | |
183 | return select; | |
184 | }; | |
185 | ||
186 | var SliderPlusTextInput = function() { | |
187 | this.init = function(cfg) { | |
188 | this.slider = cfg.slider; | |
189 | this.textInput = cfg.textInput; | |
190 | this.callback = cfg.callback; | |
191 | return this; | |
192 | }; | |
193 | ||
194 | this.set = function(value) { | |
195 | this.slider.value = "" + value; | |
196 | this.textInput.value = "" + value; | |
197 | this.callback(value); | |
198 | }; | |
199 | }; | |
200 | ||
201 | yoob.makeSliderPlusTextInput = function(container, label, min_, max_, size, value, fun) { | |
202 | yoob.makeSpan(container, label); | |
203 | var slider = yoob.makeSlider(container, min_, max_, value); | |
204 | var s = "" + value; | |
205 | var textInput = yoob.makeTextInput(container, size, s); | |
206 | slider.onchange = function(e) { | |
207 | textInput.value = slider.value; | |
208 | fun(parseInt(slider.value, 10)); | |
209 | }; | |
210 | textInput.onchange = function(e) { | |
211 | var v = parseInt(textInput.value, 10); | |
212 | if (v !== NaN) { | |
213 | slider.value = "" + v; | |
214 | fun(v); | |
215 | } | |
216 | }; | |
217 | return new SliderPlusTextInput().init({ | |
218 | 'slider': slider, | |
219 | 'textInput': textInput, | |
220 | 'callback': fun | |
221 | }); | |
222 | }; | |
223 | ||
224 | yoob.makeSVG = function(container) { | |
225 | var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); | |
226 | /* <svg viewBox = "0 0 200 200" version = "1.1"> */ | |
227 | container.appendChild(svg); | |
228 | return svg; | |
229 | }; | |
230 | ||
231 | yoob.makeSVGElem = function(svg, tag, cfg) { | |
232 | var elem = document.createElementNS(svg.namespaceURI, tag); | |
233 | for (var key in cfg) { | |
234 | if (cfg.hasOwnProperty(key)) { | |
235 | elem.setAttribute(key, cfg[key]); | |
236 | } | |
237 | } | |
238 | svg.appendChild(elem); | |
239 | return elem; | |
240 | }; |
20 | 20 | </body> |
21 | 21 | <script src="hypongtrochoid.js"></script> |
22 | 22 | <script> |
23 | launch('../common-yoob.js-0.11/', 'container'); | |
23 | launch('yoob/', 'container'); | |
24 | 24 | </script> |
0 | /* | |
1 | * This file is part of yoob.js version 0.7 | |
2 | * Available from https://github.com/catseye/yoob.js/ | |
3 | * This file is in the public domain. See http://unlicense.org/ for details. | |
4 | */ | |
5 | if (window.yoob === undefined) yoob = {}; | |
6 | ||
7 | /* | |
8 | * Pretty standard shim to get window.{request,cancelRequest}AnimationFrame | |
9 | * functions, synthesized from the theory and the many examples I've seen. | |
10 | */ | |
11 | ||
12 | window.requestAnimationFrame = | |
13 | window.requestAnimationFrame || | |
14 | window.webkitRequestAnimationFrame || | |
15 | window.mozRequestAnimationFrame || | |
16 | window.oRequestAnimationFrame || | |
17 | window.msRequestAnimationFrame || | |
18 | function(f, elem) { | |
19 | return setTimeout(function() { | |
20 | f(Date.now()); | |
21 | }, 1000 / 60); | |
22 | }; | |
23 | ||
24 | // it was called "cancelRequestAnimationFrame" in the editor's draft: | |
25 | // http://webstuff.nfshost.com/anim-timing/Overview.html | |
26 | // but "cancelAnimationFrame" in the Candidate Recommendation: | |
27 | // http://www.w3.org/TR/animation-timing/ | |
28 | window.cancelAnimationFrame = | |
29 | window.cancelAnimationFrame || | |
30 | window.webkitCancelAnimationFrame || | |
31 | window.mozCancelAnimationFrame || | |
32 | window.oCancelAnimationFrame || | |
33 | window.msCancelAnimationFrame || | |
34 | window.cancelRequestAnimationFrame || | |
35 | window.webkitCancelRequestAnimationFrame || | |
36 | window.mozCancelRequestAnimationFrame || | |
37 | window.oCancelRequestAnimationFrame || | |
38 | window.msCancelRequestAnimationFrame || | |
39 | clearTimeout; | |
40 | window.cancelRequestAnimationFrame = window.cancelAnimationFrame; | |
41 | ||
42 | /* | |
43 | * A yoob.Animation object manages an animation. | |
44 | * | |
45 | * How many things get animated by one yoob.Animation object is up to | |
46 | * you. For animated demos, it may be sufficient to have only one | |
47 | * Animation object which updates many independent graphical objects. | |
48 | * However, it may be useful to have multiple Animation objects, if | |
49 | * the program can be in different states (for example, one title screen | |
50 | * Animation, and a different Animation to use during gameplay.) | |
51 | */ | |
52 | yoob.Animation = function() { | |
53 | /* | |
54 | * Initialize a yoob.Animation. Takes a configuration dictionary: | |
55 | * | |
56 | * mode 'quantum' (default) or 'proportional' | |
57 | * object the object to call methods on | |
58 | * tickTime in msec. for quantum only. default = 1/60th sec | |
59 | * lastTime internal (but see below) | |
60 | * accumDelta internal, only used in quantum mode | |
61 | * | |
62 | * There are two modes that a yoob.Animation can be in, | |
63 | * 'quantum' (the default) and 'proportional'. | |
64 | * | |
65 | * Once the Animation has been started (by calling animation.start()): | |
66 | * | |
67 | * In the 'quantum' mode, the object's draw() method is called on | |
68 | * each animation frame, and the object's update() method is called as | |
69 | * many times as necessary to ensure it is called once for every tickTime | |
70 | * milliseconds that have passed. Neither method is passed any | |
71 | * parameters. | |
72 | * | |
73 | * update() (or draw(), in 'proportional' mode only) may return the | |
74 | * exact object 'false' to force the animation to stop immediately. | |
75 | * | |
76 | * In the 'proportional' mode, the object's draw() method is called on | |
77 | * each animation frame, and the amount of time (in milliseconds) that has | |
78 | * elapsed since the last time it was called (or 0 if it was never | |
79 | * previously called) is passed as the first and only parameter. | |
80 | */ | |
81 | this.init = function(cfg) { | |
82 | this.object = cfg.object; | |
83 | this.lastTime = cfg.lastTime || null; | |
84 | this.accumDelta = cfg.accumDelta || 0; | |
85 | this.tickTime = cfg.tickTime || (1000.0 / 60.0); | |
86 | this.mode = cfg.mode || 'quantum'; | |
87 | this.request = null; | |
88 | return this; | |
89 | }; | |
90 | ||
91 | this.start = function() { | |
92 | if (this.request) { | |
93 | return false; | |
94 | } | |
95 | var $this = this; | |
96 | if (this.mode === 'quantum') { | |
97 | var animFrame = function(time) { | |
98 | $this.object.draw(); | |
99 | if ($this.lastTime === null) { | |
100 | $this.lastTime = time; | |
101 | } | |
102 | $this.accumDelta += (time - $this.lastTime); | |
103 | while ($this.accumDelta > $this.tickTime) { | |
104 | $this.accumDelta -= $this.tickTime; | |
105 | var result = $this.object.update(); | |
106 | if (result === false) { | |
107 | $this.accumDelta = $this.tickTime; | |
108 | $this.request = null; | |
109 | } | |
110 | } | |
111 | $this.lastTime = time; | |
112 | if ($this.request) { | |
113 | $this.request = requestAnimationFrame(animFrame); | |
114 | } | |
115 | }; | |
116 | } else if (this.mode === 'proportional') { | |
117 | var animFrame = function(time) { | |
118 | var timeElapsed = ( | |
119 | $this.lastTime == null ? 0 : time - $this.lastTime | |
120 | ); | |
121 | $this.lastTime = time; | |
122 | var result = $this.object.draw(timeElapsed); | |
123 | if (result === false) { | |
124 | $this.request = null; | |
125 | } | |
126 | if ($this.request) { | |
127 | $this.request = requestAnimationFrame(animFrame); | |
128 | } | |
129 | }; | |
130 | } | |
131 | this.request = requestAnimationFrame(animFrame); | |
132 | return true; | |
133 | }; | |
134 | ||
135 | this.stop = function() { | |
136 | if (this.request) { | |
137 | cancelRequestAnimationFrame(this.request); | |
138 | } | |
139 | this.request = null; | |
140 | }; | |
141 | ||
142 | this.reset = function() { | |
143 | this.stop(); | |
144 | this.lastTime = null; | |
145 | }; | |
146 | }; |
0 | /* | |
1 | * This file is part of yoob.js version 0.10 | |
2 | * Available from https://github.com/catseye/yoob.js/ | |
3 | * This file is in the public domain. See http://unlicense.org/ for details. | |
4 | */ | |
5 | if (window.yoob === undefined) yoob = {}; | |
6 | ||
7 | /* | |
8 | * Functions for creating elements. | |
9 | */ | |
10 | ||
11 | yoob.makeCanvas = function(container, width, height) { | |
12 | var canvas = document.createElement('canvas'); | |
13 | if (width) { | |
14 | canvas.width = width; | |
15 | } | |
16 | if (height) { | |
17 | canvas.height = height; | |
18 | } | |
19 | container.appendChild(canvas); | |
20 | return canvas; | |
21 | }; | |
22 | ||
23 | yoob.makeButton = function(container, labelText, fun) { | |
24 | var button = document.createElement('button'); | |
25 | button.innerHTML = labelText; | |
26 | container.appendChild(button); | |
27 | if (fun) { | |
28 | button.onclick = fun; | |
29 | } | |
30 | return button; | |
31 | }; | |
32 | ||
33 | yoob.checkBoxNumber = 0; | |
34 | yoob.makeCheckbox = function(container, checked, labelText, fun) { | |
35 | var checkbox = document.createElement('input'); | |
36 | checkbox.type = "checkbox"; | |
37 | checkbox.id = 'cfzzzb_' + yoob.checkBoxNumber; | |
38 | checkbox.checked = checked; | |
39 | var label = document.createElement('label'); | |
40 | label.htmlFor = 'cfzzzb_' + yoob.checkBoxNumber; | |
41 | yoob.checkBoxNumber += 1; | |
42 | label.appendChild(document.createTextNode(labelText)); | |
43 | ||
44 | container.appendChild(checkbox); | |
45 | container.appendChild(label); | |
46 | ||
47 | if (fun) { | |
48 | checkbox.onchange = function(e) { | |
49 | fun(checkbox.checked); | |
50 | }; | |
51 | } | |
52 | return checkbox; | |
53 | }; | |
54 | ||
55 | yoob.makeTextInput = function(container, size, value) { | |
56 | var input = document.createElement('input'); | |
57 | input.size = "" + (size || 12); | |
58 | input.value = value || ""; | |
59 | container.appendChild(input); | |
60 | return input; | |
61 | }; | |
62 | ||
63 | yoob.makeSlider = function(container, min, max, value, fun) { | |
64 | var slider = document.createElement('input'); | |
65 | slider.type = "range"; | |
66 | slider.min = min; | |
67 | slider.max = max; | |
68 | slider.value = value || 0; | |
69 | if (fun) { | |
70 | slider.onchange = function(e) { | |
71 | fun(parseInt(slider.value, 10)); | |
72 | }; | |
73 | } | |
74 | container.appendChild(slider); | |
75 | return slider; | |
76 | }; | |
77 | ||
78 | yoob.makeParagraph = function(container, innerHTML) { | |
79 | var p = document.createElement('p'); | |
80 | p.innerHTML = innerHTML || ''; | |
81 | container.appendChild(p); | |
82 | return p; | |
83 | }; | |
84 | ||
85 | yoob.makeSpan = function(container, innerHTML) { | |
86 | var span = document.createElement('span'); | |
87 | span.innerHTML = innerHTML || ''; | |
88 | container.appendChild(span); | |
89 | return span; | |
90 | }; | |
91 | ||
92 | yoob.makeDiv = function(container, innerHTML) { | |
93 | var div = document.createElement('div'); | |
94 | div.innerHTML = innerHTML || ''; | |
95 | container.appendChild(div); | |
96 | return div; | |
97 | }; | |
98 | ||
99 | yoob.makePre = function(container, innerHTML) { | |
100 | var elem = document.createElement('pre'); | |
101 | elem.innerHTML = innerHTML || ''; | |
102 | container.appendChild(elem); | |
103 | return elem; | |
104 | }; | |
105 | ||
106 | yoob.makePanel = function(container, title, isOpen) { | |
107 | isOpen = !!isOpen; | |
108 | var panelContainer = document.createElement('div'); | |
109 | var button = document.createElement('button'); | |
110 | var innerContainer = document.createElement('div'); | |
111 | innerContainer.style.display = isOpen ? "block" : "none"; | |
112 | ||
113 | button.innerHTML = (isOpen ? "∇" : "⊳") + " " + title; | |
114 | button.onclick = function(e) { | |
115 | isOpen = !isOpen; | |
116 | button.innerHTML = (isOpen ? "∇" : "⊳") + " " + title; | |
117 | innerContainer.style.display = isOpen ? "block" : "none"; | |
118 | }; | |
119 | ||
120 | panelContainer.appendChild(button); | |
121 | panelContainer.appendChild(innerContainer); | |
122 | container.appendChild(panelContainer); | |
123 | return innerContainer; | |
124 | }; | |
125 | ||
126 | yoob.makeTextArea = function(container, cols, rows, initial) { | |
127 | var textarea = document.createElement('textarea'); | |
128 | textarea.rows = "" + rows; | |
129 | textarea.cols = "" + cols; | |
130 | if (initial) { | |
131 | textarea.value = initial; | |
132 | } | |
133 | container.appendChild(textarea); | |
134 | return textarea; | |
135 | }; | |
136 | ||
137 | yoob.makeLineBreak = function(container) { | |
138 | var br = document.createElement('br'); | |
139 | container.appendChild(br); | |
140 | return br; | |
141 | }; | |
142 | ||
143 | yoob.makeSelect = function(container, labelText, optionsArray, fun, def) { | |
144 | var label = document.createElement('label'); | |
145 | label.innerHTML = labelText; | |
146 | container.appendChild(label); | |
147 | ||
148 | var select = document.createElement("select"); | |
149 | ||
150 | for (var i = 0; i < optionsArray.length; i++) { | |
151 | var op = document.createElement("option"); | |
152 | op.value = optionsArray[i][0]; | |
153 | op.text = optionsArray[i][1]; | |
154 | if (optionsArray[i].length > 2) { | |
155 | op.selected = optionsArray[i][2]; | |
156 | } else { | |
157 | op.selected = false; | |
158 | } | |
159 | select.options.add(op); | |
160 | } | |
161 | ||
162 | if (fun) { | |
163 | select.onchange = function(e) { | |
164 | fun(optionsArray[select.selectedIndex][0]); | |
165 | }; | |
166 | } | |
167 | ||
168 | if (def) { | |
169 | var i = 0; | |
170 | var opt = select.options[i]; | |
171 | while (opt) { | |
172 | if (opt.value === def) { | |
173 | select.selectedIndex = i; | |
174 | if (fun) fun(def); | |
175 | break; | |
176 | } | |
177 | i++; | |
178 | opt = select.options[i]; | |
179 | } | |
180 | } | |
181 | ||
182 | container.appendChild(select); | |
183 | return select; | |
184 | }; | |
185 | ||
186 | var SliderPlusTextInput = function() { | |
187 | this.init = function(cfg) { | |
188 | this.slider = cfg.slider; | |
189 | this.textInput = cfg.textInput; | |
190 | this.callback = cfg.callback; | |
191 | return this; | |
192 | }; | |
193 | ||
194 | this.set = function(value) { | |
195 | this.slider.value = "" + value; | |
196 | this.textInput.value = "" + value; | |
197 | this.callback(value); | |
198 | }; | |
199 | }; | |
200 | ||
201 | yoob.makeSliderPlusTextInput = function(container, label, min_, max_, size, value, fun) { | |
202 | yoob.makeSpan(container, label); | |
203 | var slider = yoob.makeSlider(container, min_, max_, value); | |
204 | var s = "" + value; | |
205 | var textInput = yoob.makeTextInput(container, size, s); | |
206 | slider.onchange = function(e) { | |
207 | textInput.value = slider.value; | |
208 | fun(parseInt(slider.value, 10)); | |
209 | }; | |
210 | textInput.onchange = function(e) { | |
211 | var v = parseInt(textInput.value, 10); | |
212 | if (v !== NaN) { | |
213 | slider.value = "" + v; | |
214 | fun(v); | |
215 | } | |
216 | }; | |
217 | return new SliderPlusTextInput().init({ | |
218 | 'slider': slider, | |
219 | 'textInput': textInput, | |
220 | 'callback': fun | |
221 | }); | |
222 | }; | |
223 | ||
224 | yoob.makeSVG = function(container) { | |
225 | var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); | |
226 | /* <svg viewBox = "0 0 200 200" version = "1.1"> */ | |
227 | container.appendChild(svg); | |
228 | return svg; | |
229 | }; | |
230 | ||
231 | yoob.makeSVGElem = function(svg, tag, cfg) { | |
232 | var elem = document.createElementNS(svg.namespaceURI, tag); | |
233 | for (var key in cfg) { | |
234 | if (cfg.hasOwnProperty(key)) { | |
235 | elem.setAttribute(key, cfg[key]); | |
236 | } | |
237 | } | |
238 | svg.appendChild(elem); | |
239 | return elem; | |
240 | }; |
0 | /* | |
1 | * This file is part of yoob.js version 0.9 | |
2 | * Available from https://github.com/catseye/yoob.js/ | |
3 | * This file is in the public domain. See http://unlicense.org/ for details. | |
4 | */ | |
5 | if (window.yoob === undefined) yoob = {}; | |
6 | ||
7 | /* | |
8 | * This is really just an interface; duck-typing-wise, you can use any | |
9 | * Javascript object you want as a sprite, so long as it exposes these | |
10 | * methods. | |
11 | */ | |
12 | yoob.Sprite = function() { | |
13 | ||
14 | /* | |
15 | * x and y always represent the CENTRE of the Sprite(). | |
16 | * Chainable. | |
17 | */ | |
18 | this.init = function(cfg) { | |
19 | this.x = cfg.x; | |
20 | this.y = cfg.y; | |
21 | this.width = cfg.width; | |
22 | this.height = cfg.height; | |
23 | this.dx = cfg.dx || 0; | |
24 | this.dy = cfg.dy || 0; | |
25 | this.isDraggable = cfg.isDraggable || false; | |
26 | this.isClickable = cfg.isClickable || false; | |
27 | this.fillStyle = cfg.fillStyle || "green"; | |
28 | this.visible = (cfg.visible === undefined ? true : (!!cfg.visible)); | |
29 | this._isBeingDragged = false; | |
30 | return this; | |
31 | }; | |
32 | ||
33 | this.getX = function() { | |
34 | return this.x; | |
35 | }; | |
36 | ||
37 | this.getLeftX = function() { | |
38 | return this.x - this.width / 2; | |
39 | }; | |
40 | ||
41 | this.getRightX = function() { | |
42 | return this.x + this.width / 2; | |
43 | }; | |
44 | ||
45 | this.getY = function() { | |
46 | return this.y; | |
47 | }; | |
48 | ||
49 | this.getTopY = function() { | |
50 | return this.y - this.height / 2; | |
51 | }; | |
52 | ||
53 | this.getBottomY = function() { | |
54 | return this.y + this.height / 2; | |
55 | }; | |
56 | ||
57 | this.getWidth = function() { | |
58 | return this.width; | |
59 | }; | |
60 | ||
61 | this.getHeight = function() { | |
62 | return this.height; | |
63 | }; | |
64 | ||
65 | this.isBeingDragged = function() { | |
66 | return this._isBeingDragged; | |
67 | }; | |
68 | ||
69 | /* | |
70 | * Chainable. | |
71 | */ | |
72 | this.setPosition = function(x, y) { | |
73 | this.x = x; | |
74 | this.y = y; | |
75 | return this; | |
76 | }; | |
77 | ||
78 | /* | |
79 | * Chainable. | |
80 | */ | |
81 | this.setDimensions = function(width, height) { | |
82 | this.width = width; | |
83 | this.height = height; | |
84 | return this; | |
85 | }; | |
86 | ||
87 | /* | |
88 | * Chainable. | |
89 | */ | |
90 | this.setVelocity = function(dx, dy) { | |
91 | this.dx = dx; | |
92 | this.dy = dy; | |
93 | return this; | |
94 | }; | |
95 | ||
96 | /* | |
97 | * Chainable. | |
98 | */ | |
99 | this.setDestination = function(x, y, ticks) { | |
100 | this.destX = x; | |
101 | this.destY = y; | |
102 | this.setVelocity((this.destX - this.getX()) / ticks, (this.destY - this.getY()) / ticks); | |
103 | this.destCounter = ticks; | |
104 | return this; | |
105 | }; | |
106 | ||
107 | this.move = function(x, y) { | |
108 | this.x += this.dx; | |
109 | this.y += this.dy; | |
110 | this.onmove(); | |
111 | if (this.destCounter !== undefined) { | |
112 | this.destCounter--; | |
113 | if (this.destCounter <= 0) { | |
114 | this.destCounter = undefined; | |
115 | this.setPosition(this.destX, this.destY).setVelocity(0, 0); | |
116 | this.onreachdestination(); | |
117 | } | |
118 | } | |
119 | }; | |
120 | ||
121 | // override this if your shape is not a rectangle | |
122 | this.containsPoint = function(x, y) { | |
123 | return (x >= this.getLeftX() && x <= this.getRightX() && | |
124 | y >= this.getTopY() && y <= this.getBottomY()); | |
125 | }; | |
126 | ||
127 | // NOTE: this assumes that `sprite` is larger or equal in size to `this`. | |
128 | // you may need to override this in a sophisticated way if you | |
129 | // expect it to detect sprites of different shapes intersecting | |
130 | this.intersects = function(sprite) { | |
131 | var x1 = this.getLeftX(); | |
132 | var x2 = this.getRightX(); | |
133 | var y1 = this.getTopY(); | |
134 | var y2 = this.getBottomY(); | |
135 | return (sprite.containsPoint(x1, y1) || sprite.containsPoint(x2, y1) || | |
136 | sprite.containsPoint(x1, y2) || sprite.containsPoint(x2, y2)); | |
137 | }; | |
138 | ||
139 | // you will probably want to override this | |
140 | // if you do, it's up to you to honour this.visible. | |
141 | this.draw = function(ctx) { | |
142 | if (!this.visible) return; | |
143 | ctx.fillStyle = this.fillStyle || "green"; | |
144 | ctx.fillRect(this.getLeftX(), this.getTopY(), this.getWidth(), this.getHeight()); | |
145 | }; | |
146 | ||
147 | // event handlers. override to detect these events. | |
148 | this.ongrab = function() { | |
149 | }; | |
150 | this.ondrag = function() { | |
151 | }; | |
152 | this.ondrop = function() { | |
153 | }; | |
154 | this.onclick = function() { | |
155 | }; | |
156 | this.onmove = function() { | |
157 | }; | |
158 | this.onreachdestination = function() { | |
159 | }; | |
160 | ||
161 | }; | |
162 | ||
163 | /* | |
164 | * This still has a few shortcomings at the moment. | |
165 | */ | |
166 | yoob.SpriteManager = function() { | |
167 | /* | |
168 | * Attach this SpriteManager to a canvas. | |
169 | */ | |
170 | this.init = function(cfg) { | |
171 | this.canvasX = undefined; | |
172 | this.canvasY = undefined; | |
173 | this.offsetX = undefined; | |
174 | this.offsetY = undefined; | |
175 | this.dragging = undefined; | |
176 | this.sprites = []; | |
177 | ||
178 | this.canvas = cfg.canvas; | |
179 | ||
180 | var $this = this; | |
181 | this.canvas.addEventListener('mousedown', function(e) { | |
182 | return $this.onmousedown(e, e); | |
183 | }); | |
184 | this.canvas.addEventListener('touchstart', function(e) { | |
185 | return $this.onmousedown(e, e.touches[0]); | |
186 | }); | |
187 | ||
188 | this.canvas.addEventListener('mousemove', function(e) { | |
189 | return $this.onmousemove(e, e); | |
190 | }); | |
191 | this.canvas.addEventListener('touchmove', function(e) { | |
192 | return $this.onmousemove(e, e.touches[0]); | |
193 | }); | |
194 | ||
195 | this.canvas.addEventListener('mouseup', function(e) { | |
196 | return $this.onmouseup(e, e); | |
197 | }); | |
198 | this.canvas.addEventListener('touchend', function(e) { | |
199 | return $this.onmouseup(e, e.touches[0]); | |
200 | }); | |
201 | ||
202 | return this; | |
203 | }; | |
204 | ||
205 | /* | |
206 | * Common handling of mouse and touch events | |
207 | */ | |
208 | this.onmousedown = function(e, touch) { | |
209 | e.preventDefault(); | |
210 | this.canvasX = touch.pageX - this.canvas.offsetLeft; | |
211 | this.canvasY = touch.pageY - this.canvas.offsetTop; | |
212 | ||
213 | var sprite = this.getSpriteAt(this.canvasX, this.canvasY); | |
214 | if (sprite === undefined) return; | |
215 | if (sprite.isDraggable) { | |
216 | this.dragging = sprite; | |
217 | this.dragging._isBeingDragged = true; | |
218 | this.dragging.ongrab(); | |
219 | this.offsetX = sprite.getX() - this.canvasX; | |
220 | this.offsetY = sprite.getY() - this.canvasY; | |
221 | this.canvas.style.cursor = "move"; | |
222 | } else if (sprite.isClickable) { | |
223 | sprite.onclick(e); | |
224 | } | |
225 | }; | |
226 | ||
227 | this.onmousemove = function(e, touch) { | |
228 | e.preventDefault(); | |
229 | if (!this.dragging) return; | |
230 | ||
231 | this.canvasX = touch.pageX - this.canvas.offsetLeft; | |
232 | this.canvasY = touch.pageY - this.canvas.offsetTop; | |
233 | ||
234 | this.dragging.setPosition(this.canvasX + this.offsetX, | |
235 | this.canvasY + this.offsetY); | |
236 | }; | |
237 | ||
238 | this.onmouseup = function(e, touch) { | |
239 | e.preventDefault(); | |
240 | this.canvas.onmousemove = null; | |
241 | this.canvasX = undefined; | |
242 | this.canvasY = undefined; | |
243 | this.offsetX = undefined; | |
244 | this.offsetY = undefined; | |
245 | if (this.dragging !== undefined) { | |
246 | this.dragging.ondrop(); | |
247 | this.dragging._isBeingDragged = false; | |
248 | } | |
249 | this.dragging = undefined; | |
250 | this.canvas.style.cursor = "auto"; | |
251 | }; | |
252 | ||
253 | this.move = function() { | |
254 | this.foreach(function(sprite) { | |
255 | sprite.move(); | |
256 | }); | |
257 | }; | |
258 | ||
259 | this.draw = function(ctx) { | |
260 | if (ctx === undefined) { | |
261 | ctx = this.canvas.getContext('2d'); | |
262 | } | |
263 | this.foreach(function(sprite) { | |
264 | sprite.draw(ctx); | |
265 | }); | |
266 | }; | |
267 | ||
268 | this.addSprite = function(sprite) { | |
269 | this.sprites.push(sprite); | |
270 | }; | |
271 | ||
272 | this.removeSprite = function(sprite) { | |
273 | var index = undefined; | |
274 | for (var i = 0; i < this.sprites.length; i++) { | |
275 | if (this.sprites[i] === sprite) { | |
276 | index = i; | |
277 | break; | |
278 | } | |
279 | } | |
280 | if (index !== undefined) { | |
281 | this.sprites.splice(index, 1); | |
282 | } | |
283 | }; | |
284 | ||
285 | this.clearSprites = function() { | |
286 | this.sprites = []; | |
287 | }; | |
288 | ||
289 | this.moveToFront = function(sprite) { | |
290 | this.removeSprite(sprite); | |
291 | this.sprites.push(sprite); | |
292 | }; | |
293 | ||
294 | this.moveToBack = function(sprite) { | |
295 | this.removeSprite(sprite); | |
296 | this.sprites.unshift(sprite); | |
297 | }; | |
298 | ||
299 | this.getSpriteAt = function(x, y) { | |
300 | for (var i = this.sprites.length-1; i >= 0; i--) { | |
301 | var sprite = this.sprites[i]; | |
302 | if (sprite.containsPoint(x, y)) { | |
303 | return sprite; | |
304 | } | |
305 | } | |
306 | return undefined; | |
307 | }; | |
308 | ||
309 | this.foreach = function(fun) { | |
310 | for (var i = this.sprites.length-1; i >= 0; i--) { | |
311 | var sprite = this.sprites[i]; | |
312 | var result = fun(sprite); | |
313 | if (result === 'remove') { | |
314 | this.removeSprite(sprite); | |
315 | } | |
316 | if (result === 'return') { | |
317 | return sprite; | |
318 | } | |
319 | } | |
320 | }; | |
321 | ||
322 | }; |