chiark / gitweb /
Forbid undo of new-game if it would change the params.
[sgt-puzzles.git] / emcclib.js
index 8fb9dd8efa4a7d62c5ab0853909cfff5c90c07cb..907dc19995ea326001b9ca307d346bfe3d421a8c 100644 (file)
@@ -2,7 +2,11 @@
  * 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
@@ -41,7 +45,7 @@ mergeInto(LibraryManager.library, {
      * provides neither presets nor configurability.
      */
     js_remove_type_dropdown: function() {
-        document.getElementById("gametype").style.display = "none";
+        gametypelist.style.display = "none";
     },
 
     /*
@@ -55,56 +59,62 @@ mergeInto(LibraryManager.library, {
     },
 
     /*
-     * 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);
      *
@@ -112,12 +122,7 @@ mergeInto(LibraryManager.library, {
      * dropdown.
      */
     js_get_selected_preset: function() {
-        for (var i in gametypeoptions) {
-            if (gametypeoptions[i].selected) {
-                return gametypeoptions[i].value;
-            }
-        }
-        return 0;
+        return gametypeselectedindex;
     },
 
     /*
@@ -128,13 +133,15 @@ mergeInto(LibraryManager.library, {
      * 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";
+            }
         }
     },
 
@@ -179,8 +186,8 @@ mergeInto(LibraryManager.library, {
      * 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));
     },
 
     /*
@@ -313,8 +320,8 @@ mergeInto(LibraryManager.library, {
         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;
@@ -342,8 +349,8 @@ mergeInto(LibraryManager.library, {
             ctx.fill();
         }
         ctx.lineWidth = '1';
-        ctx.lineCap = '1';
-        ctx.lineJoin = '1';
+        ctx.lineCap = 'round';
+        ctx.lineJoin = 'round';
         ctx.strokeStyle = Pointer_stringify(outline);
         ctx.stroke();
     },
@@ -363,8 +370,8 @@ mergeInto(LibraryManager.library, {
             ctx.fill();
         }
         ctx.lineWidth = '1';
-        ctx.lineCap = '1';
-        ctx.lineJoin = '1';
+        ctx.lineCap = 'round';
+        ctx.lineJoin = 'round';
         ctx.strokeStyle = Pointer_stringify(outline);
         ctx.stroke();
     },
@@ -399,7 +406,7 @@ mergeInto(LibraryManager.library, {
         // 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
@@ -463,6 +470,7 @@ mergeInto(LibraryManager.library, {
         blitters[id] = document.createElement("canvas");
         blitters[id].width = w;
         blitters[id].height = h;
+        return id;
     },
 
     /*
@@ -512,18 +520,20 @@ mergeInto(LibraryManager.library, {
      * 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);
     },
 
     /*
@@ -547,8 +557,11 @@ mergeInto(LibraryManager.library, {
     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;
@@ -561,38 +574,7 @@ mergeInto(LibraryManager.library, {
      * 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));
     },
 
     /*
@@ -687,29 +669,13 @@ mergeInto(LibraryManager.library, {
      * 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
+        });
     },
 
     /*
@@ -719,10 +685,7 @@ mergeInto(LibraryManager.library, {
      * 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();
     },
 
     /*
@@ -734,5 +697,5 @@ mergeInto(LibraryManager.library, {
      */
     js_focus_canvas: function() {
         onscreen_canvas.focus();
-    },
+    }
 });