git @ Cat's Eye Technologies HTML5-Gewgaws / 1b04bfd
Continue to move towards individual yoob subdirectories. Chris Pressey 7 years ago
19 changed file(s) with 2979 addition(s) and 5 deletion(s). Raw diff Collapse all Expand all
1818 </body>
1919 <script src="fibonacci-spiral.js"></script>
2020 <script>
21 launch('../common-yoob.js-0.11/', 'container');
21 launch('yoob/', 'container');
2222 </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 };
1212 </body>
1313 <script src="fingerspelling.js"></script>
1414 <script>
15 launch('../common-yoob.js-0.11/', 'container');
15 launch('yoob/', 'container');
1616 </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 };
1818 </body>
1919 <script src="heronsis-hermnonicii.js"></script>
2020 <script>
21 launch('../common-yoob.js-0.11/', 'container');
21 launch('yoob/', 'container');
2222 </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 };
1818 </body>
1919 <script src="hirsute-miasma.js"></script>
2020 <script>
21 launch('../common-yoob.js-0.11/', 'container');
21 launch('yoob/', 'container');
2222 </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 };
2020 </body>
2121 <script src="hypongtrochoid.js"></script>
2222 <script>
23 launch('../common-yoob.js-0.11/', 'container');
23 launch('yoob/', 'container');
2424 </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 };