summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/emcclib.js
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/emcclib.js')
-rw-r--r--apps/plugins/puzzles/emcclib.js757
1 files changed, 757 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/emcclib.js b/apps/plugins/puzzles/emcclib.js
new file mode 100644
index 0000000000..385281ad0b
--- /dev/null
+++ b/apps/plugins/puzzles/emcclib.js
@@ -0,0 +1,757 @@
1/*
2 * emcclib.js: one of the Javascript components of an Emscripten-based
3 * web/Javascript front end for Puzzles.
4 *
5 * The other parts of this system live in emcc.c and emccpre.js. It
6 * also depends on being run in the context of a web page containing
7 * an appropriate collection of bits and pieces (a canvas, some
8 * buttons and links etc), which is generated for each puzzle by the
9 * script html/jspage.pl.
10 *
11 * This file contains a set of Javascript functions which we insert
12 * into Emscripten's library object via the --js-library option; this
13 * allows us to provide JS code which can be called from the
14 * Emscripten-compiled C, mostly dealing with UI interaction of
15 * various kinds.
16 */
17
18mergeInto(LibraryManager.library, {
19 /*
20 * void js_debug(const char *message);
21 *
22 * A function to write a diagnostic to the Javascript console.
23 * Unused in production, but handy in development.
24 */
25 js_debug: function(ptr) {
26 console.log(Pointer_stringify(ptr));
27 },
28
29 /*
30 * void js_error_box(const char *message);
31 *
32 * A wrapper around Javascript's alert(), so the C code can print
33 * simple error message boxes (e.g. when invalid data is entered
34 * in a configuration dialog).
35 */
36 js_error_box: function(ptr) {
37 alert(Pointer_stringify(ptr));
38 },
39
40 /*
41 * void js_remove_type_dropdown(void);
42 *
43 * Get rid of the drop-down list on the web page for selecting
44 * game presets. Called at setup time if the game back end
45 * provides neither presets nor configurability.
46 */
47 js_remove_type_dropdown: function() {
48 document.getElementById("gametype").style.display = "none";
49 },
50
51 /*
52 * void js_remove_solve_button(void);
53 *
54 * Get rid of the Solve button on the web page. Called at setup
55 * time if the game doesn't support an in-game solve function.
56 */
57 js_remove_solve_button: function() {
58 document.getElementById("solve").style.display = "none";
59 },
60
61 /*
62 * void js_add_preset(const char *name);
63 *
64 * Add a preset to the drop-down types menu. The provided text is
65 * the name of the preset. (The corresponding game_params stays on
66 * the C side and never comes out this far; we just pass a numeric
67 * index back to the C code when a selection is made.)
68 *
69 * The special 'Custom' preset is requested by passing NULL to
70 * this function, rather than the string "Custom", since in that
71 * case we need to do something special - see below.
72 */
73 js_add_preset: function(ptr) {
74 var name = (ptr == 0 ? "Customise..." : Pointer_stringify(ptr));
75 var value = gametypeoptions.length;
76
77 var option = document.createElement("option");
78 option.value = value;
79 option.appendChild(document.createTextNode(name));
80 gametypeselector.appendChild(option);
81 gametypeoptions.push(option);
82
83 if (ptr == 0) {
84 // The option we've just created is the one for inventing
85 // a new custom setup.
86 gametypenewcustom = option;
87 option.value = -1;
88
89 // Now create another element called 'Custom', which will
90 // be auto-selected by us to indicate the custom settings
91 // you've previously selected. However, we don't add it to
92 // the game type selector; it will only appear when the
93 // user actually has custom settings selected.
94 option = document.createElement("option");
95 option.value = -2;
96 option.appendChild(document.createTextNode("Custom"));
97 gametypethiscustom = option;
98 }
99 },
100
101 /*
102 * int js_get_selected_preset(void);
103 *
104 * Return the index of the currently selected value in the type
105 * dropdown.
106 */
107 js_get_selected_preset: function() {
108 for (var i in gametypeoptions) {
109 if (gametypeoptions[i].selected) {
110 return gametypeoptions[i].value;
111 }
112 }
113 return 0;
114 },
115
116 /*
117 * void js_select_preset(int n);
118 *
119 * Cause a different value to be selected in the type dropdown
120 * (for when the user selects values from the Custom configurer
121 * which turn out to exactly match a preset).
122 */
123 js_select_preset: function(n) {
124 if (gametypethiscustom !== null) {
125 // Fiddle with the Custom/Customise options. If we're
126 // about to select the Custom option, then it should be in
127 // the menu, and the other one should read "Re-customise";
128 // if we're about to select another one, then the static
129 // Custom option should disappear and the other one should
130 // read "Customise".
131
132 if (gametypethiscustom.parentNode == gametypeselector)
133 gametypeselector.removeChild(gametypethiscustom);
134 if (gametypenewcustom.parentNode == gametypeselector)
135 gametypeselector.removeChild(gametypenewcustom);
136
137 if (n < 0) {
138 gametypeselector.appendChild(gametypethiscustom);
139 gametypenewcustom.lastChild.data = "Re-customise...";
140 } else {
141 gametypenewcustom.lastChild.data = "Customise...";
142 }
143 gametypeselector.appendChild(gametypenewcustom);
144 gametypenewcustom.selected = false;
145 }
146
147 if (n < 0) {
148 gametypethiscustom.selected = true;
149 } else {
150 gametypeoptions[n].selected = true;
151 }
152 },
153
154 /*
155 * void js_get_date_64(unsigned *p);
156 *
157 * Return the current date, in milliseconds since the epoch
158 * (Javascript's native format), as a 64-bit integer. Used to
159 * invent an initial random seed for puzzle generation.
160 */
161 js_get_date_64: function(ptr) {
162 var d = (new Date()).valueOf();
163 setValue(ptr, d, 'i64');
164 },
165
166 /*
167 * void js_update_permalinks(const char *desc, const char *seed);
168 *
169 * Update the permalinks on the web page for a new game
170 * description and optional random seed. desc can never be NULL,
171 * but seed might be (if the game was generated by entering a
172 * descriptive id by hand), in which case we suppress display of
173 * the random seed permalink.
174 */
175 js_update_permalinks: function(desc, seed) {
176 desc = Pointer_stringify(desc);
177 permalink_desc.href = "#" + desc;
178
179 if (seed == 0) {
180 permalink_seed.style.display = "none";
181 } else {
182 seed = Pointer_stringify(seed);
183 permalink_seed.href = "#" + seed;
184 permalink_seed.style.display = "inline";
185 }
186 },
187
188 /*
189 * void js_enable_undo_redo(int undo, int redo);
190 *
191 * Set the enabled/disabled states of the undo and redo buttons,
192 * after a move.
193 */
194 js_enable_undo_redo: function(undo, redo) {
195 undo_button.disabled = (undo == 0);
196 redo_button.disabled = (redo == 0);
197 },
198
199 /*
200 * void js_activate_timer();
201 *
202 * Start calling the C timer_callback() function every 20ms.
203 */
204 js_activate_timer: function() {
205 if (timer === null) {
206 timer_reference_date = (new Date()).valueOf();
207 timer = setInterval(function() {
208 var now = (new Date()).valueOf();
209 timer_callback((now - timer_reference_date) / 1000.0);
210 timer_reference_date = now;
211 return true;
212 }, 20);
213 }
214 },
215
216 /*
217 * void js_deactivate_timer();
218 *
219 * Stop calling the C timer_callback() function every 20ms.
220 */
221 js_deactivate_timer: function() {
222 if (timer !== null) {
223 clearInterval(timer);
224 timer = null;
225 }
226 },
227
228 /*
229 * void js_canvas_start_draw(void);
230 *
231 * Prepare to do some drawing on the canvas.
232 */
233 js_canvas_start_draw: function() {
234 ctx = offscreen_canvas.getContext('2d');
235 update_xmin = update_xmax = update_ymin = update_ymax = undefined;
236 },
237
238 /*
239 * void js_canvas_draw_update(int x, int y, int w, int h);
240 *
241 * Mark a rectangle of the off-screen canvas as needing to be
242 * copied to the on-screen one.
243 */
244 js_canvas_draw_update: function(x, y, w, h) {
245 /*
246 * Currently we do this in a really simple way, just by taking
247 * the smallest rectangle containing all updates so far. We
248 * could instead keep the data in a richer form (e.g. retain
249 * multiple smaller rectangles needing update, and only redraw
250 * the whole thing beyond a certain threshold) but this will
251 * do for now.
252 */
253 if (update_xmin === undefined || update_xmin > x) update_xmin = x;
254 if (update_ymin === undefined || update_ymin > y) update_ymin = y;
255 if (update_xmax === undefined || update_xmax < x+w) update_xmax = x+w;
256 if (update_ymax === undefined || update_ymax < y+h) update_ymax = y+h;
257 },
258
259 /*
260 * void js_canvas_end_draw(void);
261 *
262 * Finish the drawing, by actually copying the newly drawn stuff
263 * to the on-screen canvas.
264 */
265 js_canvas_end_draw: function() {
266 if (update_xmin !== undefined) {
267 var onscreen_ctx = onscreen_canvas.getContext('2d');
268 onscreen_ctx.drawImage(offscreen_canvas,
269 update_xmin, update_ymin,
270 update_xmax - update_xmin,
271 update_ymax - update_ymin,
272 update_xmin, update_ymin,
273 update_xmax - update_xmin,
274 update_ymax - update_ymin);
275 }
276 ctx = null;
277 },
278
279 /*
280 * void js_canvas_draw_rect(int x, int y, int w, int h,
281 * const char *colour);
282 *
283 * Draw a rectangle.
284 */
285 js_canvas_draw_rect: function(x, y, w, h, colptr) {
286 ctx.fillStyle = Pointer_stringify(colptr);
287 ctx.fillRect(x, y, w, h);
288 },
289
290 /*
291 * void js_canvas_clip_rect(int x, int y, int w, int h);
292 *
293 * Set a clipping rectangle.
294 */
295 js_canvas_clip_rect: function(x, y, w, h) {
296 ctx.save();
297 ctx.beginPath();
298 ctx.rect(x, y, w, h);
299 ctx.clip();
300 },
301
302 /*
303 * void js_canvas_unclip(void);
304 *
305 * Reset to no clipping.
306 */
307 js_canvas_unclip: function() {
308 ctx.restore();
309 },
310
311 /*
312 * void js_canvas_draw_line(float x1, float y1, float x2, float y2,
313 * int width, const char *colour);
314 *
315 * Draw a line. We must adjust the coordinates by 0.5 because
316 * Javascript's canvas coordinates appear to be pixel corners,
317 * whereas we want pixel centres. Also, we manually draw the pixel
318 * at each end of the line, which our clients will expect but
319 * Javascript won't reliably do by default (in common with other
320 * Postscriptish drawing frameworks).
321 */
322 js_canvas_draw_line: function(x1, y1, x2, y2, width, colour) {
323 colour = Pointer_stringify(colour);
324
325 ctx.beginPath();
326 ctx.moveTo(x1 + 0.5, y1 + 0.5);
327 ctx.lineTo(x2 + 0.5, y2 + 0.5);
328 ctx.lineWidth = width;
329 ctx.lineCap = 'round';
330 ctx.lineJoin = 'round';
331 ctx.strokeStyle = colour;
332 ctx.stroke();
333 ctx.fillStyle = colour;
334 ctx.fillRect(x1, y1, 1, 1);
335 ctx.fillRect(x2, y2, 1, 1);
336 },
337
338 /*
339 * void js_canvas_draw_poly(int *points, int npoints,
340 * const char *fillcolour,
341 * const char *outlinecolour);
342 *
343 * Draw a polygon.
344 */
345 js_canvas_draw_poly: function(pointptr, npoints, fill, outline) {
346 ctx.beginPath();
347 ctx.moveTo(getValue(pointptr , 'i32') + 0.5,
348 getValue(pointptr+4, 'i32') + 0.5);
349 for (var i = 1; i < npoints; i++)
350 ctx.lineTo(getValue(pointptr+8*i , 'i32') + 0.5,
351 getValue(pointptr+8*i+4, 'i32') + 0.5);
352 ctx.closePath();
353 if (fill != 0) {
354 ctx.fillStyle = Pointer_stringify(fill);
355 ctx.fill();
356 }
357 ctx.lineWidth = '1';
358 ctx.lineCap = 'round';
359 ctx.lineJoin = 'round';
360 ctx.strokeStyle = Pointer_stringify(outline);
361 ctx.stroke();
362 },
363
364 /*
365 * void js_canvas_draw_circle(int x, int y, int r,
366 * const char *fillcolour,
367 * const char *outlinecolour);
368 *
369 * Draw a circle.
370 */
371 js_canvas_draw_circle: function(x, y, r, fill, outline) {
372 ctx.beginPath();
373 ctx.arc(x + 0.5, y + 0.5, r, 0, 2*Math.PI);
374 if (fill != 0) {
375 ctx.fillStyle = Pointer_stringify(fill);
376 ctx.fill();
377 }
378 ctx.lineWidth = '1';
379 ctx.lineCap = 'round';
380 ctx.lineJoin = 'round';
381 ctx.strokeStyle = Pointer_stringify(outline);
382 ctx.stroke();
383 },
384
385 /*
386 * int js_canvas_find_font_midpoint(int height, const char *fontptr);
387 *
388 * Return the adjustment required for text displayed using
389 * ALIGN_VCENTRE. We want to place the midpoint between the
390 * baseline and the cap-height at the specified position; so this
391 * function returns the adjustment which, when added to the
392 * desired centre point, returns the y-coordinate at which you
393 * should put the baseline.
394 *
395 * There is no sensible method of querying this kind of font
396 * metric in Javascript, so instead we render a piece of test text
397 * to a throwaway offscreen canvas and then read the pixel data
398 * back out to find the highest and lowest pixels. That's good
399 * _enough_ (in that we only needed the answer to the nearest
400 * pixel anyway), but rather disgusting!
401 *
402 * Since this is a very expensive operation, we cache the results
403 * per (font,height) pair.
404 */
405 js_canvas_find_font_midpoint: function(height, font) {
406 font = Pointer_stringify(font);
407
408 // Reuse cached value if possible
409 if (midpoint_cache[font] !== undefined)
410 return midpoint_cache[font];
411
412 // Find the width of the string
413 var ctx1 = onscreen_canvas.getContext('2d');
414 ctx1.font = font;
415 var width = (ctx1.measureText(midpoint_test_str).width + 1) | 0;
416
417 // Construct a test canvas of appropriate size, initialise it to
418 // black, and draw the string on it in white
419 var measure_canvas = document.createElement('canvas');
420 var ctx2 = measure_canvas.getContext('2d');
421 ctx2.canvas.width = width;
422 ctx2.canvas.height = 2*height;
423 ctx2.fillStyle = "#000000";
424 ctx2.fillRect(0, 0, width, 2*height);
425 var baseline = (1.5*height) | 0;
426 ctx2.fillStyle = "#ffffff";
427 ctx2.font = font;
428 ctx2.fillText(midpoint_test_str, 0, baseline);
429
430 // Scan the contents of the test canvas to find the top and bottom
431 // set pixels.
432 var pixels = ctx2.getImageData(0, 0, width, 2*height).data;
433 var ymin = 2*height, ymax = -1;
434 for (var y = 0; y < 2*height; y++) {
435 for (var x = 0; x < width; x++) {
436 if (pixels[4*(y*width+x)] != 0) {
437 if (ymin > y) ymin = y;
438 if (ymax < y) ymax = y;
439 break;
440 }
441 }
442 }
443
444 var ret = (baseline - (ymin + ymax) / 2) | 0;
445 midpoint_cache[font] = ret;
446 return ret;
447 },
448
449 /*
450 * void js_canvas_draw_text(int x, int y, int halign,
451 * const char *colptr, const char *fontptr,
452 * const char *text);
453 *
454 * Draw text. Vertical alignment has been taken care of on the C
455 * side, by optionally calling the above function. Horizontal
456 * alignment is handled here, since we can get the canvas draw
457 * function to do it for us with almost no extra effort.
458 */
459 js_canvas_draw_text: function(x, y, halign, colptr, fontptr, text) {
460 ctx.font = Pointer_stringify(fontptr);
461 ctx.fillStyle = Pointer_stringify(colptr);
462 ctx.textAlign = (halign == 0 ? 'left' :
463 halign == 1 ? 'center' : 'right');
464 ctx.textBaseline = 'alphabetic';
465 ctx.fillText(Pointer_stringify(text), x, y);
466 },
467
468 /*
469 * int js_canvas_new_blitter(int w, int h);
470 *
471 * Create a new blitter object, which is just an offscreen canvas
472 * of the specified size.
473 */
474 js_canvas_new_blitter: function(w, h) {
475 var id = blittercount++;
476 blitters[id] = document.createElement("canvas");
477 blitters[id].width = w;
478 blitters[id].height = h;
479 return id;
480 },
481
482 /*
483 * void js_canvas_free_blitter(int id);
484 *
485 * Free a blitter (or rather, destroy our reference to it so JS
486 * can garbage-collect it, and also enforce that we don't
487 * accidentally use it again afterwards).
488 */
489 js_canvas_free_blitter: function(id) {
490 blitters[id] = null;
491 },
492
493 /*
494 * void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h);
495 *
496 * Copy from the puzzle image to a blitter. The size is passed to
497 * us, partly so we don't have to remember the size of each
498 * blitter, but mostly so that the C side can adjust the copy
499 * rectangle in the case where it partially overlaps the edge of
500 * the screen.
501 */
502 js_canvas_copy_to_blitter: function(id, x, y, w, h) {
503 var blitter_ctx = blitters[id].getContext('2d');
504 blitter_ctx.drawImage(offscreen_canvas,
505 x, y, w, h,
506 0, 0, w, h);
507 },
508
509 /*
510 * void js_canvas_copy_from_blitter(int id, int x, int y, int w, int h);
511 *
512 * Copy from a blitter back to the puzzle image. As above, the
513 * size of the copied rectangle is passed to us from the C side
514 * and may already have been modified.
515 */
516 js_canvas_copy_from_blitter: function(id, x, y, w, h) {
517 ctx.drawImage(blitters[id],
518 0, 0, w, h,
519 x, y, w, h);
520 },
521
522 /*
523 * void js_canvas_make_statusbar(void);
524 *
525 * Cause a status bar to exist. Called at setup time if the puzzle
526 * back end turns out to want one.
527 */
528 js_canvas_make_statusbar: function() {
529 var statusholder = document.getElementById("statusbarholder");
530 statusbar = document.createElement("div");
531 statusbar.style.overflow = "hidden";
532 statusbar.style.width = (onscreen_canvas.width - 4) + "px";
533 statusholder.style.width = onscreen_canvas.width + "px";
534 statusbar.style.height = "1.2em";
535 statusbar.style.textAlign = "left";
536 statusbar.style.background = "#d8d8d8";
537 statusbar.style.borderLeft = '2px solid #c8c8c8';
538 statusbar.style.borderTop = '2px solid #c8c8c8';
539 statusbar.style.borderRight = '2px solid #e8e8e8';
540 statusbar.style.borderBottom = '2px solid #e8e8e8';
541 statusbar.appendChild(document.createTextNode(" "));
542 statusholder.appendChild(statusbar);
543 },
544
545 /*
546 * void js_canvas_set_statusbar(const char *text);
547 *
548 * Set the text in the status bar.
549 */
550 js_canvas_set_statusbar: function(ptr) {
551 var text = Pointer_stringify(ptr);
552 statusbar.replaceChild(document.createTextNode(text),
553 statusbar.lastChild);
554 },
555
556 /*
557 * void js_canvas_set_size(int w, int h);
558 *
559 * Set the size of the puzzle canvas. Called at setup, and every
560 * time the user picks new puzzle settings requiring a different
561 * size.
562 */
563 js_canvas_set_size: function(w, h) {
564 onscreen_canvas.width = w;
565 offscreen_canvas.width = w;
566 if (statusbar !== null) {
567 statusbar.style.width = (w - 4) + "px";
568 document.getElementById("statusbarholder").style.width = w + "px";
569 }
570 resizable_div.style.width = w + "px";
571
572 onscreen_canvas.height = h;
573 offscreen_canvas.height = h;
574 },
575
576 /*
577 * void js_dialog_init(const char *title);
578 *
579 * Begin constructing a 'dialog box' which will be popped up in an
580 * overlay on top of the rest of the puzzle web page.
581 */
582 js_dialog_init: function(titletext) {
583 // Create an overlay on the page which darkens everything
584 // beneath it.
585 dlg_dimmer = document.createElement("div");
586 dlg_dimmer.style.width = "100%";
587 dlg_dimmer.style.height = "100%";
588 dlg_dimmer.style.background = '#000000';
589 dlg_dimmer.style.position = 'fixed';
590 dlg_dimmer.style.opacity = 0.3;
591 dlg_dimmer.style.top = dlg_dimmer.style.left = 0;
592 dlg_dimmer.style["z-index"] = 99;
593
594 // Now create a form which sits on top of that in turn.
595 dlg_form = document.createElement("form");
596 dlg_form.style.width = (window.innerWidth * 2 / 3) + "px";
597 dlg_form.style.opacity = 1;
598 dlg_form.style.background = '#ffffff';
599 dlg_form.style.color = '#000000';
600 dlg_form.style.position = 'absolute';
601 dlg_form.style.border = "2px solid black";
602 dlg_form.style.padding = "20px";
603 dlg_form.style.top = (window.innerHeight / 10) + "px";
604 dlg_form.style.left = (window.innerWidth / 6) + "px";
605 dlg_form.style["z-index"] = 100;
606
607 var title = document.createElement("p");
608 title.style.marginTop = "0px";
609 title.appendChild(document.createTextNode
610 (Pointer_stringify(titletext)));
611 dlg_form.appendChild(title);
612
613 dlg_return_funcs = [];
614 dlg_next_id = 0;
615 },
616
617 /*
618 * void js_dialog_string(int i, const char *title, const char *initvalue);
619 *
620 * Add a string control (that is, an edit box) to the dialog under
621 * construction.
622 */
623 js_dialog_string: function(index, title, initialtext) {
624 dlg_form.appendChild(document.createTextNode(Pointer_stringify(title)));
625 var editbox = document.createElement("input");
626 editbox.type = "text";
627 editbox.value = Pointer_stringify(initialtext);
628 dlg_form.appendChild(editbox);
629 dlg_form.appendChild(document.createElement("br"));
630
631 dlg_return_funcs.push(function() {
632 dlg_return_sval(index, editbox.value);
633 });
634 },
635
636 /*
637 * void js_dialog_choices(int i, const char *title, const char *choicelist,
638 * int initvalue);
639 *
640 * Add a choices control (i.e. a drop-down list) to the dialog
641 * under construction. The 'choicelist' parameter is unchanged
642 * from the way the puzzle back end will have supplied it: i.e.
643 * it's still encoded as a single string whose first character
644 * gives the separator.
645 */
646 js_dialog_choices: function(index, title, choicelist, initvalue) {
647 dlg_form.appendChild(document.createTextNode(Pointer_stringify(title)));
648 var dropdown = document.createElement("select");
649 var choicestr = Pointer_stringify(choicelist);
650 var items = choicestr.slice(1).split(choicestr[0]);
651 var options = [];
652 for (var i in items) {
653 var option = document.createElement("option");
654 option.value = i;
655 option.appendChild(document.createTextNode(items[i]));
656 if (i == initvalue) option.selected = true;
657 dropdown.appendChild(option);
658 options.push(option);
659 }
660 dlg_form.appendChild(dropdown);
661 dlg_form.appendChild(document.createElement("br"));
662
663 dlg_return_funcs.push(function() {
664 var val = 0;
665 for (var i in options) {
666 if (options[i].selected) {
667 val = options[i].value;
668 break;
669 }
670 }
671 dlg_return_ival(index, val);
672 });
673 },
674
675 /*
676 * void js_dialog_boolean(int i, const char *title, int initvalue);
677 *
678 * Add a boolean control (a checkbox) to the dialog under
679 * construction. Checkboxes are generally expected to be sensitive
680 * on their label text as well as the box itself, so for this
681 * control we create an actual label rather than merely a text
682 * node (and hence we must allocate an id to the checkbox so that
683 * the label can refer to it).
684 */
685 js_dialog_boolean: function(index, title, initvalue) {
686 var checkbox = document.createElement("input");
687 checkbox.type = "checkbox";
688 checkbox.id = "cb" + String(dlg_next_id++);
689 checkbox.checked = (initvalue != 0);
690 dlg_form.appendChild(checkbox);
691 var checkboxlabel = document.createElement("label");
692 checkboxlabel.setAttribute("for", checkbox.id);
693 checkboxlabel.textContent = Pointer_stringify(title);
694 dlg_form.appendChild(checkboxlabel);
695 dlg_form.appendChild(document.createElement("br"));
696
697 dlg_return_funcs.push(function() {
698 dlg_return_ival(index, checkbox.checked ? 1 : 0);
699 });
700 },
701
702 /*
703 * void js_dialog_launch(void);
704 *
705 * Finish constructing a dialog, and actually display it, dimming
706 * everything else on the page.
707 */
708 js_dialog_launch: function() {
709 // Put in the OK and Cancel buttons at the bottom.
710 var button;
711
712 button = document.createElement("input");
713 button.type = "button";
714 button.value = "OK";
715 button.onclick = function(event) {
716 for (var i in dlg_return_funcs)
717 dlg_return_funcs[i]();
718 command(3);
719 }
720 dlg_form.appendChild(button);
721
722 button = document.createElement("input");
723 button.type = "button";
724 button.value = "Cancel";
725 button.onclick = function(event) {
726 command(4);
727 }
728 dlg_form.appendChild(button);
729
730 document.body.appendChild(dlg_dimmer);
731 document.body.appendChild(dlg_form);
732 },
733
734 /*
735 * void js_dialog_cleanup(void);
736 *
737 * Stop displaying a dialog, and clean up the internal state
738 * associated with it.
739 */
740 js_dialog_cleanup: function() {
741 document.body.removeChild(dlg_dimmer);
742 document.body.removeChild(dlg_form);
743 dlg_dimmer = dlg_form = null;
744 onscreen_canvas.focus();
745 },
746
747 /*
748 * void js_focus_canvas(void);
749 *
750 * Return keyboard focus to the puzzle canvas. Called after a
751 * puzzle-control button is pressed, which tends to have the side
752 * effect of taking focus away from the canvas.
753 */
754 js_focus_canvas: function() {
755 onscreen_canvas.focus();
756 }
757});