git @ Cat's Eye Technologies Circute / 4ffa82e
Update to yoob.js 0.7; use yoob.PresetManager; add Reset button. Chris Pressey 7 years ago
6 changed file(s) with 538 addition(s) and 152 deletion(s). Raw diff Collapse all Expand all
66 display: none;
77 }
88 #display_container {
9 border: 1px solid red;
109 padding: 4px;
1110 }
1211 #display_text {
1716 border: 1px solid green;
1817 }
1918 #load {
19 display: none;
20 }
21 .example_configuration {
2022 display: none;
2123 }
2224 </style>
3032 <button id="start">Start</button>
3133 <button id="stop">Stop</button>
3234 <button id="step">Step</button>
33 Speed: <input id="speed" type="range" min="0" max="200" value="0" /><br />
34
35 Example:
36 <span id="select_source_container">
37 <select id="select_source">
38 <option>(select one...)</option>
39 <option>and</option>
40 <option>gated</option>
41 <option>or</option>
42 <option>osc</option>
43 <option>smallosc</option>
44 <option>sr</option>
45 <option>store_hmm</option>
46 <option>xor</option>
47 <option>xor2</option>
48 </select>
49 </span>
35 <button id="reset">Reset</button>
36 Speed: <input id="speed" type="range" min="0" max="200" value="0" />
37
38 <div>
39 Example: <select id="select_source"></select>
40 </div>
5041
5142 <textarea id="input" rows="20" cols="80">
5243 =========
7162 </canvas>
7263 </div>
7364
74 <div id="and" style="display: none;"
65 <div id="and" class="example_configuration"
7566 > =
7667 =
7768 #######== = =========
9182 =========</div>
9283
9384
94 <div id="gated" style="display: none;"
85 <div id="gated" class="example_configuration"
9586 > ####
9687 # #
9788 =N###
116107 </div>
117108
118109
119 <div id="or" style="display: none;"
110 <div id="or" class="example_configuration"
120111 > =
121112 =
122113 #######== ===N=== =========
129120 </div>
130121
131122
132 <div id="osc" style="display: none;"
123 <div id="osc" class="example_configuration"
133124 > =========
134125 = =
135126 ==N== =
139130 =========</div>
140131
141132
142 <div id="smallosc" style="display: none;"
133 <div id="smallosc" class="example_configuration"
143134 > ============================================================================
144135 =
145136 =N#
147138 </div>
148139
149140
150 <div id="sr" style="display: none;"
141 <div id="sr" class="example_configuration"
151142 >
152143 # #
153144 # #
173164 </div>
174165
175166
176 <div id="store_hmm" style="display: none;"
167 <div id="store_hmm" class="example_configuration"
177168 >
178169
179170 ==========
192183 ====</div>
193184
194185
195 <div id="xor" style="display: none;"
186 <div id="xor" class="example_configuration"
196187 >
197188
198189 ==========================
216207 ####################</div>
217208
218209
219 <div id="xor2" style="display: none;"
210 <div id="xor2" class="example_configuration"
220211 >
221212
222213 ==========================
245236 <script src="yoob/playfield.js"></script>
246237 <script src="yoob/playfield-html-view.js"></script>
247238 <script src="yoob/playfield-canvas-view.js"></script>
239 <script src="yoob/preset-manager.js"></script>
248240 <script src="../script/circute.js"></script>
249241 <script>
250242 var pf;
321313 'step': 'step',
322314 'load': 'load',
323315 'edit': 'edit',
316 'reset': 'reset',
324317 'speed': 'speed',
325 'select': 'select_source',
326318 'source': 'input',
327319 'display': 'display_container'
328320 });
329
330321 c.click_load();
322
323 e = (new yoob.PresetManager()).init({
324 selectElem: document.getElementById('select_source'),
325 controller: c
326 }).populateFromClass('example_configuration');
331327 </script>
00 /*
1 * This file is part of yoob.js version 0.3
1 * This file is part of yoob.js version 0.7
22 * Available from https://github.com/catseye/yoob.js/
33 * This file is in the public domain. See http://unlicense.org/ for details.
44 */
55 if (window.yoob === undefined) yoob = {};
66
77 /*
8 * A controller for executing(/animating/evolving) states
9 * (such as esolang program states or cellular automaton
10 * configurations.)
11 *
12 * Can be connected to a UI in the DOM.
13 *
14 * Subclass this and override the following methods:
8 * A controller for executing(/animating/evolving) states such as esolang
9 * program states or cellular automaton configurations. For the sake of
10 * convenience, we will refer to this as the _program state_, even though
11 * it is of course highly adaptable and might not represent a "program".
12 *
13 * The controller can be connected to a UI in the DOM, consisting of:
14 *
15 * - a set of buttons which control the evolution of the state:
16 * - start
17 * - stop
18 * - step
19 * - load
20 * - edit
21 * - reset
22 *
23 * - a slider control which adjusts the speed of program state evolution.
24 *
25 * - a `source` element from which an program state can be loaded,
26 * and which is generally assumed to support user-editing of the source.
27 * The `edit` button will cause the `source` to be shown and the `display`
28 * to be hidden, while the `load` button will load the program state from
29 * the `source`, hide the `source`, and show the `display`.
30 *
31 * - a `display` element on which the current program state will be
32 * depicted. Note that the controller is not directly responsible for
33 * rendering the program state; use something like yoob.PlayfieldCanvasView
34 * for that instead. The controller only knows about the `display` in order
35 * to hide it while the `source` is being edited and to show it after the
36 * `source` has been loaded.
37 *
38 * - an `input` element, which provides input to the running program.
39 *
40 * Each of these is optional, and if not configured, will not be used.
41 *
42 * To use a Controller, create a subclass of yoob.Controller and override
43 * the following methods:
1544 * - make it evolve the state by one tick in the step() method
1645 * - make it load the state from a multiline string in the load() method
46 *
47 * In these methods, you will need to store the state (in whatever
48 * representation you find convenient for processing and for depicting on
49 * the `display` in some fashion) somehow. You may store it in a closed-over
50 * private variable, or in an attribute on your controller object.
51 *
52 * If you store in in an attribute on your controller object, you should use
53 * the `.programState` attribute; it is reserved for this purpose.
54 *
55 * You should *not* store it in the `.state` attribute, as a yoob.Controller
56 * uses this to track its own state (yes, it has its own state independent of
57 * the program state. at least potentially.)
1758 */
1859 yoob.Controller = function() {
60 var STOPPED = 0; // the program has terminated (itself)
61 var PAUSED = 1; // the program is ready to step/run (stopped by user)
62 var RUNNING = 2; // the program is running
63 var BLOCKED = 3; // the program is waiting for more input
64
1965 this.intervalId = undefined;
2066 this.delay = 100;
67 this.state = STOPPED;
68
2169 this.source = undefined;
70 this.input = undefined;
71 this.display = undefined;
72
2273 this.speed = undefined;
2374 this.controls = {};
2475
25 this.makeEventHandler = function(control, key) {
76 /*
77 * This is not a public method.
78 */
79 this._makeEventHandler = function(control, key) {
2680 if (this['click_' + key] !== undefined) {
2781 key = 'click_' + key;
2882 }
29 var self = this;
83 var $this = this;
3084 return function(e) {
31 self[key](control);
85 $this[key](control);
3286 };
3387 };
3488
3791 * are the actions a controller can undertake, and the values
3892 * are either DOM elements or strings; if strings, DOM elements
3993 * with those ids will be obtained from the document and used.
94 *
95 * When the button associated with e.g. 'start' is clicked,
96 * the corresponding method (in this case, 'click_start()')
97 * on this Controller will be called. These functions are
98 * responsible for changing the state of the Controller (both
99 * the internal state, and the enabled status, etc. of the
100 * controls), and for calling other methods on the Controller
101 * to implement the particulars of the action.
102 *
103 * For example, 'click_step()' calls 'performStep()' which
104 * calls 'step()' (which a subclass or instantiator must
105 * provide an implementation for.)
106 *
107 * To simulate one of the buttons being clicked, you may
108 * call 'click_foo()' yourself in code. However, that will
109 * be subject to the current restrictions of the interface.
110 * You may be better off calling one of the "internal" methods
111 * like 'performStep()'.
40112 */
41113 this.connect = function(dict) {
42 var self = this;
43 var keys = ["start", "stop", "step", "load", "edit", "select"];
114 var $this = this;
115
116 var keys = ["start", "stop", "step", "load", "edit", "reset"];
44117 for (var i in keys) {
45118 var key = keys[i];
46119 var value = dict[key];
47120 if (typeof value === 'string') {
48121 value = document.getElementById(value);
49122 }
50 if (value !== undefined) {
51 if (key === 'select') {
52 value.onchange = this.makeEventHandler(value, key);
53 } else {
54 value.onclick = this.makeEventHandler(value, key);
55 }
123 if (value) {
124 value.onclick = this._makeEventHandler(value, key);
56125 this.controls[key] = value;
57126 }
58127 }
59128
60 var keys = ["source", "display"];
129 var keys = ["speed", "source", "input", "display"];
61130 for (var i in keys) {
62131 var key = keys[i];
63132 var value = dict[key];
64133 if (typeof value === 'string') {
65134 value = document.getElementById(value);
66135 }
67 if (value !== undefined) {
136 if (value) {
68137 this[key] = value;
69 }
70 }
71
72 var speed = dict.speed;
73 if (typeof speed === 'string') {
74 speed = document.getElementById(speed);
75 }
76 if (speed !== undefined) {
77 this.speed = speed;
78 speed.value = self.delay;
79 speed.onchange = function(e) {
80 self.delay = speed.value;
81 if (self.intervalId !== undefined) {
82 self.stop();
83 self.start();
138 // special cases
139 if (key === 'speed') {
140 this.speed.value = this.delay;
141 this.speed.onchange = function(e) {
142 $this.setDelayFrom($this.speed);
143 if ($this.intervalId !== undefined) {
144 $this.stop();
145 $this.start();
146 }
147 }
148 } else if (key === 'input') {
149 this.input.onchange = function(e) {
150 if (this.value.length > 0) {
151 $this.unblock();
152 }
153 }
84154 }
85155 }
86 }
156 }
157
158 this.click_stop();
87159 };
88160
89161 this.click_step = function(e) {
90 this.stop();
91 this.step();
92 };
93
162 if (this.state === STOPPED) return;
163 this.click_stop();
164 this.state = PAUSED;
165 this.performStep();
166 };
167
168 /*
169 * Override this and make it evolve the program state by one tick.
170 * The method may also return a control code string:
171 *
172 * - `stop` to indicate that the program has terminated.
173 * - `block` to indicate that the program is waiting for more input.
174 */
94175 this.step = function() {
95176 alert("step() NotImplementedError");
96177 };
97178
179 this.performStep = function() {
180 var code = this.step();
181 if (code === 'stop') {
182 this.terminate();
183 } else if (code === 'block') {
184 this.state = BLOCKED;
185 }
186 };
187
98188 this.click_load = function(e) {
99 this.stop();
189 this.click_stop();
100190 this.load(this.source.value);
191 this.state = PAUSED;
101192 if (this.controls.edit) this.controls.edit.style.display = "inline";
102193 if (this.controls.load) this.controls.load.style.display = "none";
103194 if (this.controls.start) this.controls.start.disabled = false;
104195 if (this.controls.step) this.controls.step.disabled = false;
105 if (this.controls.stop) this.controls.stop.disabled = false;
196 if (this.controls.stop) this.controls.stop.disabled = true;
106197 if (this.display) this.display.style.display = "block";
107198 if (this.source) this.source.style.display = "none";
108199 };
111202 alert("load() NotImplementedError");
112203 };
113204
205 /*
206 * Loads a source text into the source element.
207 */
208 this.loadSource = function(text) {
209 if (this.source) this.source.value = text;
210 this.load(text);
211 this.state = PAUSED;
212 };
213
214 /*
215 * Loads a source text into the source element.
216 * Assumes it comes from an element in the document, so it translates
217 * the basic HTML escapes (but no others) to plain text.
218 */
219 this.loadSourceFromHTML = function(html) {
220 var text = html;
221 text = text.replace(/\&lt;/g, '<');
222 text = text.replace(/\&gt;/g, '>');
223 text = text.replace(/\&amp;/g, '&');
224 this.loadSource(text);
225 };
226
227 /*
228 * This is the basic idea, but not fleshed out yet.
229 * - Should we cache the source somewhere?
230 * - While we're waiting, should we disable the UI / show a spinny?
231 */
232 this.loadSourceFromURL = function(url, errorCallback) {
233 var http = new XMLHttpRequest();
234 var $this = this;
235 if (!errorCallback) {
236 errorCallback = function(http) {
237 $this.loadSource(
238 "Error: could not load " + url + ": " + http.statusText
239 );
240 }
241 }
242 http.open("get", url, true);
243 http.onload = function(e) {
244 if (http.readyState === 4 && http.responseText) {
245 if (http.status === 200) {
246 $this.loadSource(http.responseText);
247 } else {
248 errorCallback(http);
249 }
250 }
251 };
252 http.send(null);
253 };
254
114255 this.click_edit = function(e) {
115 this.stop();
256 this.click_stop();
116257 if (this.controls.edit) this.controls.edit.style.display = "none";
117258 if (this.controls.load) this.controls.load.style.display = "inline";
118259 if (this.controls.start) this.controls.start.disabled = true;
122263 if (this.source) this.source.style.display = "block";
123264 };
124265
125 this.click_select = function(control) {
126 this.stop();
127 var source = document.getElementById(
128 control.options[control.selectedIndex].value
129 );
130 var text = source.innerHTML;
131 text = text.replace(/\&lt;/g, '<');
132 text = text.replace(/\&gt;/g, '>');
133 text = text.replace(/\&amp;/g, '&');
134 if (this.source) this.source.value = text;
135 this.load(text);
266 this.click_start = function(e) {
267 this.start();
268 if (this.controls.start) this.controls.start.disabled = true;
269 if (this.controls.step) this.controls.step.disabled = false;
270 if (this.controls.stop) this.controls.stop.disabled = false;
136271 };
137272
138273 this.start = function() {
139274 if (this.intervalId !== undefined)
140275 return;
141276 this.step();
142 var self = this;
143 this.intervalId = setInterval(function() { self.step(); }, this.delay);
277 var $this = this;
278 this.intervalId = setInterval(function() {
279 $this.performStep();
280 }, this.delay);
281 this.state = RUNNING;
282 };
283
284 this.click_stop = function(e) {
285 this.stop();
286 this.state = PAUSED;
287 /* why is this check here? ... */
288 if (this.controls.stop && this.controls.stop.disabled) {
289 return;
290 }
291 if (this.controls.start) this.controls.start.disabled = false;
292 if (this.controls.step) this.controls.step.disabled = false;
293 if (this.controls.stop) this.controls.stop.disabled = true;
294 };
295
296 this.terminate = function(e) {
297 this.stop();
298 this.state = STOPPED;
299 if (this.controls.start) this.controls.start.disabled = true;
300 if (this.controls.step) this.controls.step.disabled = true;
301 if (this.controls.stop) this.controls.stop.disabled = true;
144302 };
145303
146304 this.stop = function() {
149307 clearInterval(this.intervalId);
150308 this.intervalId = undefined;
151309 };
310
311 this.click_reset = function(e) {
312 this.click_stop();
313 this.load(this.source.value);
314 if (this.controls.start) this.controls.start.disabled = false;
315 if (this.controls.step) this.controls.step.disabled = false;
316 if (this.controls.stop) this.controls.stop.disabled = true;
317 };
318
319 /*
320 * Override this to change how the delay is acquired from the 'speed'
321 * element.
322 */
323 this.setDelayFrom = function(elem) {
324 this.delay = elem.max - elem.value;
325 };
152326 };
00 /*
1 * This file is part of yoob.js version 0.5
1 * This file is part of yoob.js version 0.6
22 * Available from https://github.com/catseye/yoob.js/
33 * This file is in the public domain. See http://unlicense.org/ for details.
44 */
1212 * TODO: option to stretch content rendering to fill a fixed-size canvas
1313 */
1414 yoob.PlayfieldCanvasView = function() {
15 this.pf = undefined;
16 this.canvas = undefined;
17
1815 this.init = function(pf, canvas) {
1916 this.pf = pf;
2017 this.canvas = canvas;
18 this.ctx = canvas.getContext('2d');
2119 this.cursors = [];
22 this.cellWidth = 8;
23 this.cellHeight = 8;
20 this.fixedPosition = false;
21 this.fixedSizeCanvas = false;
22 this.drawCursorsFirst = true;
23 this.setCellDimensions(8, 8);
2424 return this;
2525 };
26
27 /* Chain setters */
26
27 /*** Chainable setters ***/
28
29 /*
30 * Set the list of cursors to the given list of yoob.Cursor (or compatible)
31 * objects.
32 */
2833 this.setCursors = function(cursors) {
2934 this.cursors = cursors;
3035 return this;
3136 };
37
38 /*
39 * Set the displayed dimensions of every cell.
40 * cellWidth and cellHeight are canvas units of measure for each cell.
41 * If cellWidth is undefined, the width of a character in the monospace
42 * font of cellHeight pixels is used.
43 */
3244 this.setCellDimensions = function(cellWidth, cellHeight) {
45 this.ctx.textBaseline = "top";
46 this.ctx.font = cellHeight + "px monospace";
47
48 if (cellWidth === undefined) {
49 cellWidth = this.ctx.measureText("@").width;
50 }
51
3352 this.cellWidth = cellWidth;
3453 this.cellHeight = cellHeight;
3554 return this;
3655 };
3756
3857 /*
58 * Return the requested bounds of the occupied portion of the playfield.
59 * "Occupation" in this sense includes all cursors.
60 *
61 * These may return 'undefined' if there is nothing in the playfield.
62 *
3963 * Override these if you want to draw some portion of the
4064 * playfield which is not the whole playfield.
41 * (Not yet implemented)
4265 */
4366 this.getLowerX = function() {
44 return this.pf.getMinX();
67 var minX = this.pf.getMinX();
68 for (var i = 0; i < this.cursors.length; i++) {
69 if (minX === undefined || this.cursors[i].x < minX) {
70 minX = this.cursors[i].x;
71 }
72 }
73 return minX;
4574 };
4675 this.getUpperX = function() {
47 return this.pf.getMaxX();
76 var maxX = this.pf.getMaxX();
77 for (var i = 0; i < this.cursors.length; i++) {
78 if (maxX === undefined || this.cursors[i].x > maxX) {
79 maxX = this.cursors[i].x;
80 }
81 }
82 return maxX;
4883 };
4984 this.getLowerY = function() {
50 return this.pf.getMinY();
85 var minY = this.pf.getMinY();
86 for (var i = 0; i < this.cursors.length; i++) {
87 if (minY === undefined || this.cursors[i].y < minY) {
88 minY = this.cursors[i].y;
89 }
90 }
91 return minY;
5192 };
5293 this.getUpperY = function() {
53 return this.pf.getMaxY();
94 var maxY = this.pf.getMaxY();
95 for (var i = 0; i < this.cursors.length; i++) {
96 if (maxY === undefined || this.cursors[i].y > maxY) {
97 maxY = this.cursors[i].y;
98 }
99 }
100 return maxY;
54101 };
55102
56103 /*
57104 * Returns the number of occupied cells in the x direction.
105 * "Occupation" in this sense includes all cursors.
58106 */
59107 this.getExtentX = function() {
60108 if (this.getLowerX() === undefined || this.getUpperX() === undefined) {
66114
67115 /*
68116 * Returns the number of occupied cells in the y direction.
117 * "Occupation" in this sense includes all cursors.
69118 */
70119 this.getExtentY = function() {
71120 if (this.getLowerY() === undefined || this.getUpperY() === undefined) {
110159 });
111160 };
112161
113 /*
114 * Draws the Playfield, and a set of Cursors, on a canvas element.
115 * Resizes the canvas to the needed dimensions.
116 * cellWidth and cellHeight are canvas units of measure for each cell.
117 * Note that this is a holdover from when this method was on Playfield
118 * itself; typically you'd just call draw() instead.
119 */
120 this.drawCanvas = function(canvas, cellWidth, cellHeight, cursors) {
121 var ctx = canvas.getContext('2d');
122
123 var width = this.getExtentX();
124 var height = this.getExtentY();
125
126 if (cellWidth === undefined) {
127 ctx.textBaseline = "top";
128 ctx.font = cellHeight + "px monospace";
129 cellWidth = ctx.measureText("@").width;
130 }
131
132 canvas.width = width * cellWidth;
133 canvas.height = height * cellHeight;
134
135 ctx.clearRect(0, 0, canvas.width, canvas.height);
136
137 ctx.textBaseline = "top";
138 ctx.font = cellHeight + "px monospace";
139
140 var offsetX = this.pf.getMinX() * cellWidth * -1;
141 var offsetY = this.pf.getMinY() * cellHeight * -1;
142
143 if (this.fixedPosition) {
144 offsetX = 0;
145 offsetY = 0;
146 }
147
162 this.drawCursors = function(ctx, offsetX, offsetY, cellWidth, cellHeight) {
163 var cursors = this.cursors;
148164 for (var i = 0; i < cursors.length; i++) {
149165 cursors[i].drawContext(
150166 ctx,
153169 cellWidth, cellHeight
154170 );
155171 }
172 };
173
174 /*
175 * Draw the Playfield, and its set of Cursors, on the canvas element.
176 * Resizes the canvas to the needed dimensions first.
177 */
178 this.draw = function() {
179 var canvas = this.canvas;
180 var ctx = canvas.getContext('2d');
181 var cellWidth = this.cellWidth;
182 var cellHeight = this.cellHeight;
183 var cursors = this.cursors;
184
185 var width = this.getExtentX();
186 var height = this.getExtentY();
187
188 canvas.width = width * cellWidth;
189 canvas.height = height * cellHeight;
190
191 this.ctx.textBaseline = "top";
192 this.ctx.font = cellHeight + "px monospace";
193
194 var offsetX = 0;
195 var offsetY = 0;
196
197 if (!this.fixedPosition) {
198 offsetX = (this.getLowerX() || 0) * cellWidth * -1;
199 offsetY = (this.getLowerY() || 0) * cellHeight * -1;
200 }
201
202 if (this.drawCursorsFirst) {
203 this.drawCursors(ctx, offsetX, offsetY, cellWidth, cellHeight);
204 }
156205
157206 this.drawContext(ctx, offsetX, offsetY, cellWidth, cellHeight);
158 };
159
160 /*
161 * Render the playfield on the canvas.
162 */
163 this.draw = function() {
164 this.drawCanvas(
165 this.canvas, this.cellWidth, this.cellHeight, this.cursors
166 );
207
208 if (!this.drawCursorsFirst) {
209 this.drawCursors(ctx, offsetX, offsetY, cellWidth, cellHeight);
210 }
167211 };
168212
169213 };
00 /*
1 * This file is part of yoob.js version 0.5
1 * This file is part of yoob.js version 0.6
22 * Available from https://github.com/catseye/yoob.js/
33 * This file is in the public domain. See http://unlicense.org/ for details.
44 */
1818 this.init = function(pf, element) {
1919 this.pf = pf;
2020 this.element = element;
21 this.cursors = [];
22 return this;
23 };
24
25 /*** Chainable setters ***/
26
27 /*
28 * Set the list of cursors to the given list of yoob.Cursor (or compatible)
29 * objects.
30 */
31 this.setCursors = function(cursors) {
32 this.cursors = cursors;
2133 return this;
2234 };
2335
2436 /*
37 * Return the requested bounds of the occupied portion of the playfield.
38 * "Occupation" in this sense includes all cursors.
39 *
40 * These may return 'undefined' if there is nothing in the playfield.
41 *
2542 * Override these if you want to draw some portion of the
2643 * playfield which is not the whole playfield.
2744 */
2845 this.getLowerX = function() {
29 return this.pf.getMinX();
46 var minX = this.pf.getMinX();
47 for (var i = 0; i < this.cursors.length; i++) {
48 if (minX === undefined || this.cursors[i].x < minX) {
49 minX = this.cursors[i].x;
50 }
51 }
52 return minX;
3053 };
3154 this.getUpperX = function() {
32 return this.pf.getMaxX();
55 var maxX = this.pf.getMaxX();
56 for (var i = 0; i < this.cursors.length; i++) {
57 if (maxX === undefined || this.cursors[i].x > maxX) {
58 maxX = this.cursors[i].x;
59 }
60 }
61 return maxX;
3362 };
3463 this.getLowerY = function() {
35 return this.pf.getMinY();
64 var minY = this.pf.getMinY();
65 for (var i = 0; i < this.cursors.length; i++) {
66 if (minY === undefined || this.cursors[i].y < minY) {
67 minY = this.cursors[i].y;
68 }
69 }
70 return minY;
3671 };
3772 this.getUpperY = function() {
38 return this.pf.getMaxY();
73 var maxY = this.pf.getMaxY();
74 for (var i = 0; i < this.cursors.length; i++) {
75 if (maxY === undefined || this.cursors[i].y > maxY) {
76 maxY = this.cursors[i].y;
77 }
78 }
79 return maxY;
3980 };
4081
4182 /*
4283 * Returns the number of occupied cells in the x direction.
84 * "Occupation" in this sense includes all cursors.
4385 */
4486 this.getExtentX = function() {
4587 if (this.getLowerX() === undefined || this.getUpperX() === undefined) {
5193
5294 /*
5395 * Returns the number of occupied cells in the y direction.
96 * "Occupation" in this sense includes all cursors.
5497 */
5598 this.getExtentY = function() {
5699 if (this.getLowerY() === undefined || this.getUpperY() === undefined) {
75118 for (var y = this.getLowerY(); y <= this.getUpperY(); y++) {
76119 var row = "";
77120 for (var x = this.getLowerX(); x <= this.getUpperX(); x++) {
78 row += this.render(this.pf.get(x, y));
121 var rendered = this.render(this.pf.get(x, y));
122 for (var i = 0; i < this.cursors.length; i++) {
123 if (this.cursors[i].x === x && this.cursors[i].y === y) {
124 rendered = this.cursors[i].wrapText(rendered);
125 }
126 }
127 row += rendered;
79128 }
80129 text += row + "\n";
81130 }
00 /*
1 * This file is part of yoob.js version 0.4
1 * This file is part of yoob.js version 0.6
22 * Available from https://github.com/catseye/yoob.js/
33 * This file is in the public domain. See http://unlicense.org/ for details.
44 */
7979 this.minX = undefined;
8080 this.minY = undefined;
8181 this.maxX = undefined;
82 this.maxX = undefined;
82 this.maxY = undefined;
8383
8484 for (var cell in this._store) {
8585 var pos = cell.split(',');
100100 this.minX = undefined;
101101 this.minY = undefined;
102102 this.maxX = undefined;
103 this.maxX = undefined;
103 this.maxY = undefined;
104104 };
105105
106106 /*
0 /*
1 * This file is part of yoob.js version 0.6
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 * An object for managing a set of "presets" -- which, for an esolang,
9 * might be example programs; for an emulator, might be ROM images;
10 * for a control panel, may be pre-selected combinations of settings;
11 * and so forth.
12 *
13 * Mostly intended to be connected to a yoob.Controller -- but need not be.
14 */
15 yoob.PresetManager = function() {
16 /*
17 * The single argument is a dictionary (object) where the keys are:
18 *
19 * selectElem: (required) the <select> DOM element that will be
20 * populated with the available example programs. Selecting one
21 * will cause the .select() method of this manager to be called.
22 * it will also call .onselect if that method is present.
23 *
24 * controller: a yoob.Controller (or compatible object) that will
25 * be informed of the selection, if no callback was supplied
26 * when the item was added.
27 */
28 this.init = function(cfg) {
29 this.selectElem = cfg.selectElem;
30 this.controller = cfg.controller || null;
31 this.clear();
32 var $this = this;
33 this.selectElem.onchange = function() {
34 $this._select(this.options[this.selectedIndex].value);
35 }
36 return this;
37 };
38
39 /*
40 * Removes all options from the selectElem, and their associated data.
41 */
42 this.clear = function() {
43 this.reactTo = {};
44 while (this.selectElem.firstChild) {
45 this.selectElem.removeChild(this.selectElem.firstChild);
46 }
47 this.add('(select one...)', function() {});
48 return this;
49 };
50
51 /*
52 * Adds a preset to this PresetManager. When it is selected,
53 * the given callback will be called, being passed the id as the
54 * first argument. If no callback is provided, a default callback,
55 * which loads the contents of the element with the specified id
56 * into the configured yoob.Controller, will be used.
57 */
58 this.add = function(id, callback) {
59 var opt = document.createElement("option");
60 opt.text = id;
61 opt.value = id;
62 this.selectElem.options.add(opt);
63 var $this = this;
64 this.reactTo[id] = callback || function(id) {
65 $this.controller.click_stop(); // in case it is currently running
66 $this.controller.loadSourceFromHTML(
67 document.getElementById(id).innerHTML
68 );
69 };
70 return this;
71 };
72
73 /*
74 * Called by the selectElem's onchange event. For sanity, you should
75 * probably not call this yourself.
76 */
77 this._select = function(id) {
78 this.reactTo[id](id);
79 if (this.onselect) {
80 this.onselect(id);
81 }
82 };
83
84 /*
85 * Call this to programmatically select an item. This will change
86 * the selected option in the selectElem and trigger the appropriate
87 * callback in this PresetManager.
88 */
89 this.select = function(id) {
90 var i = 0;
91 var opt = this.selectElem.options[i];
92 while (opt) {
93 if (opt.value === id) {
94 this.selectElem.selectedIndex = i;
95 this._select(id);
96 return this;
97 }
98 i++;
99 opt = this.selectElem.options[i];
100 }
101 // if not found, select the "(select one...)" option
102 this.selectElem.selectedIndex = 0;
103 return this;
104 };
105
106 /*
107 * When called, every DOM element in the document with the given
108 * class will be considered a preset, and the manager
109 * will be populated with these. Generally the CSS for the class
110 * will have `display: none` and the elements will be <div>s.
111 *
112 * callback is as described for the .add() method.
113 */
114 this.populateFromClass = function(className, callback) {
115 var elements = document.getElementsByClassName(className);
116 for (var i = 0; i < elements.length; i++) {
117 var e = elements[i];
118 this.add(e.id, callback);
119 }
120 return this;
121 };
122 };