* 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.
+ * 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
* provides neither presets nor configurability.
*/
js_remove_type_dropdown: function() {
- document.getElementById("gametype").style.display = "none";
+ gametypelist.style.display = "none";
},
/*
},
/*
- * void js_add_preset(const char *name);
+ * void js_add_preset(int menuid, const char *name, int value);
*
- * Add a preset to the drop-down types menu. The provided text is
- * the name of the preset. (The corresponding game_params stays on
- * the C side and never comes out this far; we just pass a numeric
- * index back to the C code when a selection is made.)
- *
- * The special 'Custom' preset is requested by passing NULL to
- * this function, rather than the string "Custom", since in that
- * case we need to do something special - see below.
- */
- js_add_preset: function(ptr) {
- var name = (ptr == 0 ? "Custom" : Pointer_stringify(ptr));
- var value = gametypeoptions.length;
-
- var option = document.createElement("option");
- option.value = value;
- option.appendChild(document.createTextNode(name));
- gametypeselector.appendChild(option);
- gametypeoptions.push(option);
-
- if (ptr == 0) {
- // Create a _second_ element called 'Custom', which is
- // hidden.
- //
- // Hiding this element (that is, setting it display:none)
- // has the effect of making it not show up when the
- // drop-down list is actually opened, but still show up
- // when the item is selected.
- //
- // So what happens is that there's one element marked
- // 'Custom' that the _user_ selects, but a second one to
- // which we reset the dropdown after the config box
- // returns (if we don't then turn out to select a
- // different preset anyway). The point is that if the user
- // has 'Custom' selected, but then wants to customise
- // their settings a second time, we still get an onchange
- // event when they select the Custom option again, which
- // we wouldn't get if the browser thought it was already
- // the selected one. But here, it's _not_ the selected
- // option already; its invisible evil twin is selected.
- option = document.createElement("option");
- option.value = value;
- option.appendChild(document.createTextNode(name));
- option.style.display = "none";
- gametypeselector.appendChild(option);
- gametypehiddencustom = option;
+ * 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 = Pointer_stringify(ptr);
+ var item = document.createElement("li");
+ item.setAttribute("data-index", value);
+ var tick = document.createElement("span");
+ tick.appendChild(document.createTextNode("\u2713"));
+ tick.style.color = "transparent";
+ tick.style.paddingRight = "0.5em";
+ item.appendChild(tick);
+ item.appendChild(document.createTextNode(name));
+ gametypesubmenus[menuid].appendChild(item);
+ gametypeitems.push(item);
+
+ item.onclick = function(event) {
+ if (dlg_dimmer === null) {
+ gametypeselectedindex = value;
+ 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 = Pointer_stringify(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 tick = document.createElement("span");
+ tick.appendChild(document.createTextNode("\u2713"));
+ tick.style.color = "transparent";
+ tick.style.paddingRight = "0.5em";
+ item.appendChild(tick);
+ item.appendChild(document.createTextNode(name));
+ var submenu = document.createElement("ul");
+ item.appendChild(submenu);
+ gametypesubmenus[menuid].appendChild(item);
+ var toret = gametypesubmenus.length;
+ gametypesubmenus.push(submenu);
+ return toret;
+ },
+
/*
* int js_get_selected_preset(void);
*
* dropdown.
*/
js_get_selected_preset: function() {
- for (var i in gametypeoptions) {
- if (gametypeoptions[i].selected) {
- return gametypeoptions[i].value;
- }
- }
- return 0;
+ return gametypeselectedindex;
},
/*
* which turn out to exactly match a preset).
*/
js_select_preset: function(n) {
- if (gametypeoptions[n].value == gametypehiddencustom.value) {
- // If we're asked to select the visible Custom option,
- // select the invisible one instead. See comment above in
- // js_add_preset.
- gametypehiddencustom.selected = true;
- } else {
- gametypeoptions[n].selected = true;
+ gametypeselectedindex = n;
+ for (var i in gametypeitems) {
+ var item = gametypeitems[i];
+ var tick = item.firstChild;
+ if (item.getAttribute("data-index") == n) {
+ tick.style.color = "inherit";
+ } else {
+ tick.style.color = "transparent";
+ }
}
},
* after a move.
*/
js_enable_undo_redo: function(undo, redo) {
- undo_button.disabled = (undo == 0);
- redo_button.disabled = (redo == 0);
+ disable_menu_item(undo_button, (undo == 0));
+ disable_menu_item(redo_button, (redo == 0));
},
/*
ctx.moveTo(x1 + 0.5, y1 + 0.5);
ctx.lineTo(x2 + 0.5, y2 + 0.5);
ctx.lineWidth = width;
- ctx.lineCap = '1';
- ctx.lineJoin = '1';
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
ctx.strokeStyle = colour;
ctx.stroke();
ctx.fillStyle = colour;
ctx.fill();
}
ctx.lineWidth = '1';
- ctx.lineCap = '1';
- ctx.lineJoin = '1';
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
ctx.strokeStyle = Pointer_stringify(outline);
ctx.stroke();
},
ctx.fill();
}
ctx.lineWidth = '1';
- ctx.lineCap = '1';
- ctx.lineJoin = '1';
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
ctx.strokeStyle = Pointer_stringify(outline);
ctx.stroke();
},
// Find the width of the string
var ctx1 = onscreen_canvas.getContext('2d');
ctx1.font = font;
- var width = ctx1.measureText(midpoint_test_str).width;
+ 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
* back end turns out to want one.
*/
js_canvas_make_statusbar: function() {
- var statustd = document.getElementById("statusbarholder");
+ var statusholder = document.getElementById("statusbarholder");
statusbar = document.createElement("div");
statusbar.style.overflow = "hidden";
- statusbar.style.width = onscreen_canvas.width - 4;
+ statusbar.style.width = (onscreen_canvas.width - 4) + "px";
+ statusholder.style.width = onscreen_canvas.width + "px";
statusbar.style.height = "1.2em";
+ statusbar.style.textAlign = "left";
statusbar.style.background = "#d8d8d8";
statusbar.style.borderLeft = '2px solid #c8c8c8';
statusbar.style.borderTop = '2px solid #c8c8c8';
statusbar.style.borderRight = '2px solid #e8e8e8';
statusbar.style.borderBottom = '2px solid #e8e8e8';
statusbar.appendChild(document.createTextNode(" "));
- statustd.appendChild(statusbar);
+ statusholder.appendChild(statusbar);
},
/*
js_canvas_set_size: function(w, h) {
onscreen_canvas.width = w;
offscreen_canvas.width = w;
- if (statusbar !== null)
- statusbar.style.width = w - 4;
+ if (statusbar !== null) {
+ statusbar.style.width = (w - 4) + "px";
+ document.getElementById("statusbarholder").style.width = w + "px";
+ }
+ resizable_div.style.width = w + "px";
onscreen_canvas.height = h;
offscreen_canvas.height = h;
* overlay on top of the rest of the puzzle web page.
*/
js_dialog_init: function(titletext) {
- // Create an overlay on the page which darkens everything
- // beneath it.
- dlg_dimmer = document.createElement("div");
- dlg_dimmer.style.width = "100%";
- dlg_dimmer.style.height = "100%";
- dlg_dimmer.style.background = '#000000';
- dlg_dimmer.style.position = 'fixed';
- dlg_dimmer.style.opacity = 0.3;
- dlg_dimmer.style.top = dlg_dimmer.style.left = 0;
- dlg_dimmer.style["z-index"] = 99;
-
- // Now create a form which sits on top of that in turn.
- dlg_form = document.createElement("form");
- dlg_form.style.width = window.innerWidth * 2 / 3;
- dlg_form.style.opacity = 1;
- dlg_form.style.background = '#ffffff';
- dlg_form.style.color = '#000000';
- dlg_form.style.position = 'absolute';
- dlg_form.style.border = "2px solid black";
- dlg_form.style.padding = 20;
- dlg_form.style.top = window.innerHeight / 10;
- dlg_form.style.left = window.innerWidth / 6;
- dlg_form.style["z-index"] = 100;
-
- var title = document.createElement("p");
- title.style.marginTop = "0px";
- title.appendChild(document.createTextNode
- (Pointer_stringify(titletext)));
- dlg_form.appendChild(title);
-
- dlg_return_funcs = [];
- dlg_next_id = 0;
+ dialog_init(Pointer_stringify(titletext));
},
/*
* everything else on the page.
*/
js_dialog_launch: function() {
- // Put in the OK and Cancel buttons at the bottom.
- var button;
-
- button = document.createElement("input");
- button.type = "button";
- button.value = "OK";
- button.onclick = function(event) {
+ dialog_launch(function(event) {
for (var i in dlg_return_funcs)
dlg_return_funcs[i]();
- command(3);
- }
- dlg_form.appendChild(button);
-
- button = document.createElement("input");
- button.type = "button";
- button.value = "Cancel";
- button.onclick = function(event) {
- command(4);
- }
- dlg_form.appendChild(button);
-
- document.body.appendChild(dlg_dimmer);
- document.body.appendChild(dlg_form);
+ command(3); // OK
+ }, function(event) {
+ command(4); // Cancel
+ });
},
/*
* associated with it.
*/
js_dialog_cleanup: function() {
- document.body.removeChild(dlg_dimmer);
- document.body.removeChild(dlg_form);
- dlg_dimmer = dlg_form = null;
- onscreen_canvas.focus();
+ dialog_cleanup();
},
/*
*/
js_focus_canvas: function() {
onscreen_canvas.focus();
- },
+ }
});