123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845 |
- /*
- * emcclib.js: one of the Javascript components of an Emscripten-based
- * web/Javascript front end for Puzzles.
- *
- * The other parts of this system live in emcc.c and emccpre.js. It
- * also depends on being run in the context of a web page containing
- * an appropriate collection of bits and pieces (a canvas, some
- * buttons and links etc), which is generated for each puzzle by the
- * script html/jspage.pl.
- *
- * This file contains a set of Javascript functions which we insert
- * into Emscripten's library object via the --js-library option; this
- * allows us to provide JS code which can be called from the
- * Emscripten-compiled C, mostly dealing with UI interaction of
- * various kinds.
- */
- mergeInto(LibraryManager.library, {
- /*
- * void js_init_puzzle(void);
- *
- * Called at the start of main() to set up event handlers.
- */
- js_init_puzzle: function() {
- initPuzzle();
- },
- /*
- * void js_post_init(void);
- *
- * Called at the end of main() once the initial puzzle has been
- * started.
- */
- js_post_init: function() {
- post_init();
- },
- /*
- * void js_debug(const char *message);
- *
- * A function to write a diagnostic to the Javascript console.
- * Unused in production, but handy in development.
- */
- js_debug: function(ptr) {
- console.log(UTF8ToString(ptr));
- },
- /*
- * void js_error_box(const char *message);
- *
- * A wrapper around Javascript's alert(), so the C code can print
- * simple error message boxes (e.g. when invalid data is entered
- * in a configuration dialog).
- */
- js_error_box: function(ptr) {
- alert(UTF8ToString(ptr));
- },
- /*
- * void js_remove_type_dropdown(void);
- *
- * Get rid of the drop-down list on the web page for selecting
- * game presets. Called at setup time if the game back end
- * provides neither presets nor configurability.
- */
- js_remove_type_dropdown: function() {
- if (gametypelist === null) return;
- var gametypeitem = gametypelist.closest("li");
- if (gametypeitem === null) return;
- gametypeitem.parentNode.removeChild(gametypeitem);
- gametypelist = null;
- },
- /*
- * void js_remove_solve_button(void);
- *
- * Get rid of the Solve button on the web page. Called at setup
- * time if the game doesn't support an in-game solve function.
- */
- js_remove_solve_button: function() {
- if (solve_button === null) return;
- var solve_item = solve_button.closest("li");
- if (solve_item === null) return;
- solve_item.parentNode.removeChild(solve_item);
- solve_button = null;
- },
- /*
- * void js_add_preset(int menuid, const char *name, int value);
- *
- * Add a preset to the drop-down types menu, or to a submenu of
- * it. 'menuid' specifies an index into our array of submenus
- * where the item might be placed; 'value' specifies the number
- * that js_get_selected_preset() will return when this item is
- * clicked.
- */
- js_add_preset: function(menuid, ptr, value) {
- var name = UTF8ToString(ptr);
- var item = document.createElement("li");
- var label = document.createElement("label");
- var tick = document.createElement("input");
- tick.type = "radio";
- tick.className = "tick";
- tick.name = "preset";
- tick.value = value;
- label.appendChild(tick);
- label.appendChild(document.createTextNode(" " + name));
- item.appendChild(label);
- gametypesubmenus[menuid].appendChild(item);
- tick.onclick = function(event) {
- if (dlg_dimmer === null) {
- command(2);
- }
- }
- },
- /*
- * int js_add_preset_submenu(int menuid, const char *name);
- *
- * Add a submenu in the presets menu hierarchy. Returns its index,
- * for passing as the 'menuid' argument in further calls to
- * js_add_preset or this function.
- */
- js_add_preset_submenu: function(menuid, ptr, value) {
- var name = UTF8ToString(ptr);
- var item = document.createElement("li");
- // We still create a transparent tick element, even though it
- // won't ever be selected, to make submenu titles line up
- // nicely with their neighbours.
- var label = document.createElement("div");
- var tick = document.createElement("span");
- tick.className = "tick";
- label.appendChild(tick);
- label.tabIndex = 0;
- label.appendChild(document.createTextNode(" " + name));
- item.appendChild(label);
- var submenu = document.createElement("ul");
- label.appendChild(submenu);
- gametypesubmenus[menuid].appendChild(item);
- var toret = gametypesubmenus.length;
- gametypesubmenus.push(submenu);
- return toret;
- },
- /*
- * int js_get_selected_preset(void);
- *
- * Return the index of the currently selected value in the type
- * dropdown.
- */
- js_get_selected_preset: function() {
- return menuform.elements["preset"].value;
- },
- /*
- * void js_select_preset(int n);
- *
- * Cause a different value to be selected in the type dropdown
- * (for when the user selects values from the Custom configurer
- * which turn out to exactly match a preset).
- */
- js_select_preset: function(n) {
- menuform.elements["preset"].value = n;
- },
- /*
- * void js_default_colour(float *output);
- *
- * Try to extract a default colour from the CSS computed
- * background colour of the canvas element.
- */
- js_default_colour: function(output) {
- var col = window.getComputedStyle(onscreen_canvas).backgroundColor;
- /* We only support opaque sRGB colours. */
- var m = col.match(
- /^rgb\((\d+(?:\.\d+)?), (\d+(?:\.\d+)?), (\d+(?:\.\d+)?)\)$/);
- if (m) {
- setValue(output, +m[1] / 255, "float");
- setValue(output + 4, +m[2] / 255, "float");
- setValue(output + 8, +m[3] / 255, "float");
- }
- },
- /*
- * void js_set_background_colour(const char *bg);
- *
- * Record the puzzle background colour in a CSS variable so
- * the style sheet can use it if it wants.
- */
- js_set_background_colour: function(bgptr) {
- document.documentElement.style.setProperty("--puzzle-background",
- UTF8ToString(bgptr));
- },
- /*
- * void js_get_date_64(unsigned *p);
- *
- * Return the current date, in milliseconds since the epoch
- * (Javascript's native format), as a 64-bit integer. Used to
- * invent an initial random seed for puzzle generation.
- */
- js_get_date_64: function(ptr) {
- var d = (new Date()).valueOf();
- setValue(ptr, d, 'i64');
- },
- /*
- * void js_update_permalinks(const char *desc, const char *seed);
- *
- * Update the permalinks on the web page for a new game
- * description and optional random seed. desc can never be NULL,
- * but seed might be (if the game was generated by entering a
- * descriptive id by hand), in which case we suppress display of
- * the random seed permalink.
- */
- js_update_permalinks: function(desc, seed) {
- desc = encodeURI(UTF8ToString(desc)).replace(/#/g, "%23");
- if (permalink_desc !== null)
- permalink_desc.href = "#" + desc;
- if (permalink_seed !== null) {
- if (seed == 0) {
- permalink_seed.style.display = "none";
- } else {
- seed = encodeURI(UTF8ToString(seed)).replace(/#/g, "%23");;
- permalink_seed.href = "#" + seed;
- permalink_seed.style.display = "";
- }
- }
- },
- /*
- * void js_enable_undo_redo(int undo, int redo);
- *
- * Set the enabled/disabled states of the undo and redo buttons,
- * after a move.
- */
- js_enable_undo_redo: function(undo, redo) {
- disable_menu_item(undo_button, (undo == 0));
- disable_menu_item(redo_button, (redo == 0));
- },
- /*
- * void js_enable_undo_redo(bool undo, bool redo);
- *
- * Update any labels for the SoftLeft and Enter keys.
- */
- js_update_key_labels: function(lsk_ptr, csk_ptr) {
- var elem;
- var lsk_text = UTF8ToString(lsk_ptr);
- var csk_text = UTF8ToString(csk_ptr);
- for (elem of document.querySelectorAll("#puzzle .lsk"))
- elem.textContent = lsk_text == csk_text ? "" : lsk_text;
- for (elem of document.querySelectorAll("#puzzle .csk"))
- elem.textContent = csk_text;
- },
- /*
- * void js_activate_timer();
- *
- * Start calling the C timer_callback() function every frame.
- * The C code ensures that the activate and deactivate functions
- * are called in a sensible order.
- */
- js_activate_timer: function() {
- timer_reference = performance.now();
- var frame = function(now) {
- timer = window.requestAnimationFrame(frame);
- // The callback might call js_deactivate_timer() below.
- timer_callback((now - timer_reference) / 1000.0);
- timer_reference = now;
- };
- timer = window.requestAnimationFrame(frame);
- },
- /*
- * void js_deactivate_timer();
- *
- * Stop calling the C timer_callback() function every frame.
- */
- js_deactivate_timer: function() {
- window.cancelAnimationFrame(timer);
- },
- /*
- * void js_canvas_start_draw(void);
- *
- * Prepare to do some drawing on the canvas.
- */
- js_canvas_start_draw: function() {
- update_xmin = update_xmax = update_ymin = update_ymax = undefined;
- },
- /*
- * void js_canvas_draw_update(int x, int y, int w, int h);
- *
- * Mark a rectangle of the off-screen canvas as needing to be
- * copied to the on-screen one.
- */
- js_canvas_draw_update: function(x, y, w, h) {
- /*
- * Currently we do this in a really simple way, just by taking
- * the smallest rectangle containing all updates so far. We
- * could instead keep the data in a richer form (e.g. retain
- * multiple smaller rectangles needing update, and only redraw
- * the whole thing beyond a certain threshold) but this will
- * do for now.
- */
- if (update_xmin === undefined || update_xmin > x) update_xmin = x;
- if (update_ymin === undefined || update_ymin > y) update_ymin = y;
- if (update_xmax === undefined || update_xmax < x+w) update_xmax = x+w;
- if (update_ymax === undefined || update_ymax < y+h) update_ymax = y+h;
- },
- /*
- * void js_canvas_end_draw(void);
- *
- * Finish the drawing, by actually copying the newly drawn stuff
- * to the on-screen canvas.
- */
- js_canvas_end_draw: function() {
- if (update_xmin !== undefined) {
- var onscreen_ctx =
- onscreen_canvas.getContext('2d', { alpha: false });
- onscreen_ctx.drawImage(offscreen_canvas,
- update_xmin, update_ymin,
- update_xmax - update_xmin,
- update_ymax - update_ymin,
- update_xmin, update_ymin,
- update_xmax - update_xmin,
- update_ymax - update_ymin);
- }
- },
- /*
- * void js_canvas_draw_rect(int x, int y, int w, int h,
- * const char *colour);
- *
- * Draw a rectangle.
- */
- js_canvas_draw_rect: function(x, y, w, h, colptr) {
- ctx.fillStyle = UTF8ToString(colptr);
- ctx.fillRect(x, y, w, h);
- },
- /*
- * void js_canvas_clip_rect(int x, int y, int w, int h);
- *
- * Set a clipping rectangle.
- */
- js_canvas_clip_rect: function(x, y, w, h) {
- ctx.save();
- ctx.beginPath();
- ctx.rect(x, y, w, h);
- ctx.clip();
- },
- /*
- * void js_canvas_unclip(void);
- *
- * Reset to no clipping.
- */
- js_canvas_unclip: function() {
- ctx.restore();
- },
- /*
- * void js_canvas_draw_line(float x1, float y1, float x2, float y2,
- * int width, const char *colour);
- *
- * Draw a line. We must adjust the coordinates by 0.5 because
- * Javascript's canvas coordinates appear to be pixel corners,
- * whereas we want pixel centres. Also, we manually draw the pixel
- * at each end of the line, which our clients will expect but
- * Javascript won't reliably do by default (in common with other
- * Postscriptish drawing frameworks).
- */
- js_canvas_draw_line: function(x1, y1, x2, y2, width, colour) {
- colour = UTF8ToString(colour);
- ctx.beginPath();
- ctx.moveTo(x1 + 0.5, y1 + 0.5);
- ctx.lineTo(x2 + 0.5, y2 + 0.5);
- ctx.lineWidth = width;
- ctx.lineCap = 'round';
- ctx.lineJoin = 'round';
- ctx.strokeStyle = colour;
- ctx.stroke();
- ctx.fillStyle = colour;
- ctx.fillRect(x1, y1, 1, 1);
- ctx.fillRect(x2, y2, 1, 1);
- },
- /*
- * void js_canvas_draw_poly(int *points, int npoints,
- * const char *fillcolour,
- * const char *outlinecolour);
- *
- * Draw a polygon.
- */
- js_canvas_draw_poly: function(pointptr, npoints, fill, outline) {
- ctx.beginPath();
- ctx.moveTo(getValue(pointptr , 'i32') + 0.5,
- getValue(pointptr+4, 'i32') + 0.5);
- for (var i = 1; i < npoints; i++)
- ctx.lineTo(getValue(pointptr+8*i , 'i32') + 0.5,
- getValue(pointptr+8*i+4, 'i32') + 0.5);
- ctx.closePath();
- if (fill != 0) {
- ctx.fillStyle = UTF8ToString(fill);
- ctx.fill();
- }
- ctx.lineWidth = '1';
- ctx.lineCap = 'round';
- ctx.lineJoin = 'round';
- ctx.strokeStyle = UTF8ToString(outline);
- ctx.stroke();
- },
- /*
- * void js_canvas_draw_circle(int x, int y, int r,
- * const char *fillcolour,
- * const char *outlinecolour);
- *
- * Draw a circle.
- */
- js_canvas_draw_circle: function(x, y, r, fill, outline) {
- ctx.beginPath();
- ctx.arc(x + 0.5, y + 0.5, r, 0, 2*Math.PI);
- if (fill != 0) {
- ctx.fillStyle = UTF8ToString(fill);
- ctx.fill();
- }
- ctx.lineWidth = '1';
- ctx.lineCap = 'round';
- ctx.lineJoin = 'round';
- ctx.strokeStyle = UTF8ToString(outline);
- ctx.stroke();
- },
- /*
- * int js_canvas_find_font_midpoint(int height, bool monospaced);
- *
- * Return the adjustment required for text displayed using
- * ALIGN_VCENTRE. We want to place the midpoint between the
- * baseline and the cap-height at the specified position; so this
- * function returns the adjustment which, when added to the
- * desired centre point, returns the y-coordinate at which you
- * should put the baseline.
- *
- * There is no sensible method of querying this kind of font
- * metric in Javascript, so instead we render a piece of test text
- * to a throwaway offscreen canvas and then read the pixel data
- * back out to find the highest and lowest pixels. That's good
- * _enough_ (in that we only needed the answer to the nearest
- * pixel anyway), but rather disgusting!
- *
- * Since this is a very expensive operation, we cache the results
- * per (font,height) pair.
- */
- js_canvas_find_font_midpoint: function(height, monospaced) {
- if (height == 0) {
- // Handle this degenerate case by hand. Otherwise we end
- // up passing height=0 to the getImageData call below,
- // causing browsers to report errors.
- return 0;
- }
- // Resolve the font into a string.
- var ctx1 = onscreen_canvas.getContext('2d', { alpha: false });
- canvas_set_font(ctx1, height, monospaced);
- // Reuse cached value if possible
- if (midpoint_cache[ctx1.font] !== undefined)
- return midpoint_cache[ctx1.font];
- // Find the width of the string
- var width = (ctx1.measureText(midpoint_test_str).width + 1) | 0;
- // Construct a test canvas of appropriate size, initialise it to
- // black, and draw the string on it in white
- var measure_canvas = document.createElement('canvas');
- var ctx2 = measure_canvas.getContext('2d', { alpha: false });
- ctx2.canvas.width = width;
- ctx2.canvas.height = 2*height;
- ctx2.fillStyle = "#000000";
- ctx2.fillRect(0, 0, width, 2*height);
- var baseline = (1.5*height) | 0;
- ctx2.fillStyle = "#ffffff";
- canvas_set_font(ctx2, height, monospaced);
- ctx2.fillText(midpoint_test_str, 0, baseline);
- // Scan the contents of the test canvas to find the top and bottom
- // set pixels.
- var pixels = ctx2.getImageData(0, 0, width, 2*height).data;
- var ymin = 2*height, ymax = -1;
- for (var y = 0; y < 2*height; y++) {
- for (var x = 0; x < width; x++) {
- if (pixels[4*(y*width+x)] != 0) {
- if (ymin > y) ymin = y;
- if (ymax < y) ymax = y;
- break;
- }
- }
- }
- var ret = (baseline - (ymin + ymax) / 2) | 0;
- midpoint_cache[ctx1.font] = ret;
- return ret;
- },
- /*
- * void js_canvas_draw_text(int x, int y, int halign,
- * const char *colptr, int height,
- * bool monospaced, const char *text);
- *
- * Draw text. Vertical alignment has been taken care of on the C
- * side, by optionally calling the above function. Horizontal
- * alignment is handled here, since we can get the canvas draw
- * function to do it for us with almost no extra effort.
- */
- js_canvas_draw_text: function(x, y, halign, colptr, fontsize, monospaced,
- text) {
- canvas_set_font(ctx, fontsize, monospaced);
- ctx.fillStyle = UTF8ToString(colptr);
- ctx.textAlign = (halign == 0 ? 'left' :
- halign == 1 ? 'center' : 'right');
- ctx.textBaseline = 'alphabetic';
- ctx.fillText(UTF8ToString(text), x, y);
- },
- /*
- * int js_canvas_new_blitter(int w, int h);
- *
- * Create a new blitter object, which is just an offscreen canvas
- * of the specified size.
- */
- js_canvas_new_blitter: function(w, h) {
- var id = blittercount++;
- blitters[id] = document.createElement("canvas");
- blitters[id].width = w;
- blitters[id].height = h;
- return id;
- },
- /*
- * void js_canvas_free_blitter(int id);
- *
- * Free a blitter (or rather, destroy our reference to it so JS
- * can garbage-collect it, and also enforce that we don't
- * accidentally use it again afterwards).
- */
- js_canvas_free_blitter: function(id) {
- blitters[id] = null;
- },
- /*
- * void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h);
- *
- * Copy from the puzzle image to a blitter. The size is passed to
- * us, partly so we don't have to remember the size of each
- * blitter, but mostly so that the C side can adjust the copy
- * rectangle in the case where it partially overlaps the edge of
- * the screen.
- */
- js_canvas_copy_to_blitter: function(id, x, y, w, h) {
- var blitter_ctx = blitters[id].getContext('2d', { alpha: false });
- blitter_ctx.drawImage(offscreen_canvas,
- x, y, w, h,
- 0, 0, w, h);
- },
- /*
- * void js_canvas_copy_from_blitter(int id, int x, int y, int w, int h);
- *
- * Copy from a blitter back to the puzzle image. As above, the
- * size of the copied rectangle is passed to us from the C side
- * and may already have been modified.
- */
- js_canvas_copy_from_blitter: function(id, x, y, w, h) {
- ctx.drawImage(blitters[id],
- 0, 0, w, h,
- x, y, w, h);
- },
- /*
- * void js_canvas_remove_statusbar(void);
- *
- * Cause a status bar not to exist. Called at setup time if the
- * puzzle back end turns out not to want one.
- */
- js_canvas_remove_statusbar: function() {
- if (statusbar !== null)
- statusbar.parentNode.removeChild(statusbar);
- statusbar = null;
- },
- /*
- * void js_canvas_set_statusbar(const char *text);
- *
- * Set the text in the status bar.
- */
- js_canvas_set_statusbar: function(ptr) {
- statusbar.textContent = UTF8ToString(ptr);
- },
- /*
- * bool js_canvas_get_preferred_size(int *wp, int *hp);
- *
- * This is called before calling midend_size() to set a puzzle to
- * the default size. If the JavaScript layer has an opinion about
- * how big the puzzle should be, it can overwrite *wp and *hp with
- * its preferred size, and return true if the "user" parameter to
- * midend_size() should be true. Otherwise it should leave them
- * alone and return false.
- */
- js_canvas_get_preferred_size: function(wp, hp) {
- if (document.readyState == "complete" && containing_div !== null) {
- var dpr = window.devicePixelRatio || 1;
- setValue(wp, containing_div.clientWidth * dpr, "i32");
- setValue(hp, containing_div.clientHeight * dpr, "i32");
- return true;
- }
- return false;
- },
- /*
- * void js_canvas_set_size(int w, int h);
- *
- * Set the size of the puzzle canvas. Called whenever the size of
- * the canvas needs to change. That might be because of a change
- * of configuration, because the user has resized the puzzle, or
- * because the device pixel ratio has changed.
- */
- js_canvas_set_size: function(w, h) {
- onscreen_canvas.width = w;
- offscreen_canvas.width = w;
- if (resizable_div !== null)
- resizable_div.style.width =
- w / (window.devicePixelRatio || 1) + "px";
- else {
- onscreen_canvas.style.width =
- w / (window.devicePixelRatio || 1) + "px";
- onscreen_canvas.style.height =
- h / (window.devicePixelRatio || 1) + "px";
- }
- onscreen_canvas.height = h;
- offscreen_canvas.height = h;
- },
- /*
- * double js_get_device_pixel_ratio();
- *
- * Return the current device pixel ratio.
- */
- js_get_device_pixel_ratio: function() {
- return window.devicePixelRatio || 1;
- },
- /*
- * void js_dialog_init(const char *title);
- *
- * Begin constructing a 'dialog box' which will be popped up in an
- * overlay on top of the rest of the puzzle web page.
- */
- js_dialog_init: function(titletext) {
- dialog_init(UTF8ToString(titletext));
- },
- /*
- * void js_dialog_string(int i, const char *title, const char *initvalue);
- *
- * Add a string control (that is, an edit box) to the dialog under
- * construction.
- */
- js_dialog_string: function(index, title, initialtext) {
- var label = document.createElement("label");
- label.textContent = UTF8ToString(title);
- dlg_form.appendChild(label);
- var editbox = document.createElement("input");
- editbox.type = "text";
- editbox.value = UTF8ToString(initialtext);
- label.appendChild(editbox);
- dlg_form.appendChild(document.createElement("br"));
- dlg_return_funcs.push(function() {
- dlg_return_sval(index, editbox.value);
- });
- },
- /*
- * void js_dialog_choices(int i, const char *title, const char *choicelist,
- * int initvalue);
- *
- * Add a choices control (i.e. a drop-down list) to the dialog
- * under construction. The 'choicelist' parameter is unchanged
- * from the way the puzzle back end will have supplied it: i.e.
- * it's still encoded as a single string whose first character
- * gives the separator.
- */
- js_dialog_choices: function(index, title, choicelist, initvalue) {
- var label = document.createElement("label");
- label.textContent = UTF8ToString(title);
- dlg_form.appendChild(label);
- var dropdown = document.createElement("select");
- var choicestr = UTF8ToString(choicelist);
- var items = choicestr.slice(1).split(choicestr[0]);
- var options = [];
- for (var i in items) {
- var option = document.createElement("option");
- option.value = i;
- option.appendChild(document.createTextNode(items[i]));
- if (i == initvalue) option.selected = true;
- dropdown.appendChild(option);
- options.push(option);
- }
- label.appendChild(dropdown);
- dlg_form.appendChild(document.createElement("br"));
- dlg_return_funcs.push(function() {
- var val = 0;
- for (var i in options) {
- if (options[i].selected) {
- val = options[i].value;
- break;
- }
- }
- dlg_return_ival(index, val);
- });
- },
- /*
- * void js_dialog_boolean(int i, const char *title, int initvalue);
- *
- * Add a boolean control (a checkbox) to the dialog under
- * construction. Checkboxes are generally expected to be sensitive
- * on their label text as well as the box itself, so for this
- * control we create an actual label rather than merely a text
- * node (and hence we must allocate an id to the checkbox so that
- * the label can refer to it).
- */
- js_dialog_boolean: function(index, title, initvalue) {
- var checkbox = document.createElement("input");
- checkbox.type = "checkbox";
- checkbox.checked = (initvalue != 0);
- var checkboxlabel = document.createElement("label");
- checkboxlabel.appendChild(checkbox);
- checkboxlabel.appendChild(document.createTextNode(UTF8ToString(title)));
- dlg_form.appendChild(checkboxlabel);
- dlg_form.appendChild(document.createElement("br"));
- dlg_return_funcs.push(function() {
- dlg_return_ival(index, checkbox.checked ? 1 : 0);
- });
- },
- /*
- * void js_dialog_launch(void);
- *
- * Finish constructing a dialog, and actually display it, dimming
- * everything else on the page.
- */
- js_dialog_launch: function() {
- dialog_launch(function(event) {
- for (var i in dlg_return_funcs)
- dlg_return_funcs[i]();
- command(3); // OK
- }, function(event) {
- command(4); // Cancel
- });
- },
- /*
- * void js_dialog_cleanup(void);
- *
- * Stop displaying a dialog, and clean up the internal state
- * associated with it.
- */
- js_dialog_cleanup: function() {
- dialog_cleanup();
- },
- /*
- * void js_focus_canvas(void);
- *
- * Return keyboard focus to the puzzle canvas. Called after a
- * puzzle-control button is pressed, which tends to have the side
- * effect of taking focus away from the canvas.
- */
- js_focus_canvas: function() {
- onscreen_canvas.focus();
- },
- /*
- * bool js_savefile_read(void *buf, int len);
- *
- * Read len bytes from the save file that we're currently loading.
- */
- js_savefile_read: function(buf, len) {
- return savefile_read_callback(buf, len);
- },
- /*
- * void js_save_prefs(const char *);
- *
- * Write a buffer of serialised preferences data into localStorage.
- */
- js_save_prefs: function(buf) {
- var prefsdata = UTF8ToString(buf);
- try {
- localStorage.setItem(location.pathname + " preferences", prefsdata);
- } catch (error) {
- // Tell the user their preferences have not been saved.
- console.error(error);
- alert("Saving of preferences failed: " + error.message);
- }
- },
- /*
- * void js_load_prefs(midend *);
- *
- * Retrieve preferences data from localStorage. If there is any,
- * pass it back in as a string, via prefs_load_callback.
- */
- js_load_prefs: function(me) {
- try {
- var prefsdata =
- localStorage.getItem(location.pathname + " preferences");
- if (prefsdata !== undefined && prefsdata !== null) {
- var lenbytes = lengthBytesUTF8(prefsdata) + 1;
- var dest = _malloc(lenbytes);
- if (dest != 0) {
- stringToUTF8(prefsdata, dest, lenbytes);
- prefs_load_callback(me, dest);
- _free(dest);
- }
- }
- } catch (error) {
- // Log the error but otherwise pretend the settings were
- // absent.
- console.warn(error);
- }
- }
- });
|