diff options
| -rw-r--r-- | emcc.c | 2 | ||||
| -rw-r--r-- | emcclib.js | 90 | ||||
| -rw-r--r-- | emccpre.js | 33 | ||||
| -rwxr-xr-x | html/jspage.pl | 142 |
4 files changed, 180 insertions, 87 deletions
@@ -696,7 +696,7 @@ void command(int n) midend_redraw(me); update_undo_redo(); js_focus_canvas(); - select_appropriate_preset(); /* sort out Custom/Customise */ + select_appropriate_preset(); } } break; @@ -45,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"; }, /* @@ -67,34 +67,35 @@ mergeInto(LibraryManager.library, { * 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. + * this function. */ js_add_preset: function(ptr) { - var name = (ptr == 0 ? "Customise..." : 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); + var name = (ptr == 0 ? "Custom" : Pointer_stringify(ptr)); + var value = gametypeitems.length; + var item = document.createElement("li"); if (ptr == 0) { // The option we've just created is the one for inventing // a new custom setup. - gametypenewcustom = option; - option.value = -1; - - // Now create another element called 'Custom', which will - // be auto-selected by us to indicate the custom settings - // you've previously selected. However, we don't add it to - // the game type selector; it will only appear when the - // user actually has custom settings selected. - option = document.createElement("option"); - option.value = -2; - option.appendChild(document.createTextNode("Custom")); - gametypethiscustom = option; + gametypecustom = item; + value = -1; + } + + 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)); + gametypelist.appendChild(item); + gametypeitems.push(item); + + item.onclick = function(event) { + if (dlg_dimmer === null) { + gametypeselectedindex = value; + command(2); + } } }, @@ -105,12 +106,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; }, /* @@ -121,33 +117,15 @@ mergeInto(LibraryManager.library, { * which turn out to exactly match a preset). */ js_select_preset: function(n) { - if (gametypethiscustom !== null) { - // Fiddle with the Custom/Customise options. If we're - // about to select the Custom option, then it should be in - // the menu, and the other one should read "Re-customise"; - // if we're about to select another one, then the static - // Custom option should disappear and the other one should - // read "Customise". - - if (gametypethiscustom.parentNode == gametypeselector) - gametypeselector.removeChild(gametypethiscustom); - if (gametypenewcustom.parentNode == gametypeselector) - gametypeselector.removeChild(gametypenewcustom); - - if (n < 0) { - gametypeselector.appendChild(gametypethiscustom); - gametypenewcustom.lastChild.data = "Re-customise..."; + 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 { - gametypenewcustom.lastChild.data = "Customise..."; + tick.style.color = "transparent"; } - gametypeselector.appendChild(gametypenewcustom); - gametypenewcustom.selected = false; - } - - if (n < 0) { - gametypethiscustom.selected = true; - } else { - gametypeoptions[n].selected = true; } }, @@ -192,8 +170,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)); }, /* @@ -79,22 +79,11 @@ var dlg_return_funcs = null; // pass back the final value in each dialog control. var dlg_return_sval, dlg_return_ival; -// The <select> object implementing the game-type drop-down, and a -// list of the <option> objects inside it. Used by js_add_preset(), +// The <ul> object implementing the game-type drop-down, and a list of +// the <li> objects inside it. Used by js_add_preset(), // js_get_selected_preset() and js_select_preset(). -// -// gametypethiscustom is an option which indicates some custom game -// params you've already set up, and which will be auto-selected on -// return from the customisation dialog; gametypenewcustom is an -// option which you select to indicate that you want to bring up the -// customisation dialog and select a new configuration. Ideally I'd do -// this with just one option serving both purposes, but instead we -// have to do this a bit oddly because browsers don't send 'onchange' -// events for a select element if you reselect the same one - so if -// you've picked a custom setup and now want to change it, you need a -// way to specify that. -var gametypeselector = null, gametypeoptions = []; -var gametypethiscustom = null, gametypehiddencustom = null; +var gametypelist = null, gametypeitems = [], gametypecustom = null; +var gametypeselectedindex = null; // The two anchors used to give permalinks to the current puzzle. Used // by js_update_permalinks(). @@ -131,6 +120,14 @@ function relative_mouse_coords(event, element) { y: event.pageY - ecoords.y}; } +// Enable and disable items in the CSS menus. +function disable_menu_item(item, disabledFlag) { + if (disabledFlag) + item.className = "disabled"; + else + item.className = ""; +} + // Init function called from body.onload. function initPuzzle() { // Construct the off-screen canvas used for double buffering. @@ -232,11 +229,7 @@ function initPuzzle() { command(9); }; - gametypeselector = document.getElementById("gametype"); - gametypeselector.onchange = function(event) { - if (dlg_dimmer === null) - command(2); - }; + gametypelist = document.getElementById("gametype"); // In IE, the canvas doesn't automatically gain focus on a mouse // click, so make sure it does diff --git a/html/jspage.pl b/html/jspage.pl index 19868bd..a21f977 100755 --- a/html/jspage.pl +++ b/html/jspage.pl @@ -63,6 +63,129 @@ EOF <meta http-equiv="Content-Type" content="text/html; charset=ASCII" /> <title>${puzzlename}, ${unfinishedtitlefragment}from Simon Tatham's Portable Puzzle Collection</title> <script type="text/javascript" src="${filename}.js"></script> +<style class="text/css"> +/* Margins and centring on the top-level div for the game menu */ +#gamemenu { margin-top: 0; margin-bottom: 0.5em; text-align: center } + +/* Inside that div, the main menu bar and every submenu inside it is a <ul> */ +#gamemenu ul { + list-style: none; /* get rid of the normal unordered-list bullets */ + display: inline; /* make top-level menu bar items appear side by side */ + position: relative; /* allow submenus to position themselves near parent */ + margin: 0; + margin-bottom: 0.5em; + padding: 0; +} + +/* Individual menu items are <li> elements within such a <ul> */ +#gamemenu ul li { + /* Add a little mild text formatting */ + font-weight: bold; font-size: 0.8em; + /* Line height and padding appropriate to top-level menu items */ + padding-left: 0.75em; padding-right: 0.75em; + padding-top: 0.2em; padding-bottom: 0.2em; + margin: 0; + /* Make top-level menu items appear side by side, not vertically stacked */ + display: inline; + /* Suppress the text-selection I-beam pointer */ + cursor: default; + /* Surround each menu item with a border. The left border is removed + * because it will abut the right border of the previous item. (A rule + * below will reinstate the left border for the leftmost menu item.) */ + border-left: 0; + border-right: 1px solid rgba(0,0,0,0.3); + border-top: 1px solid rgba(0,0,0,0.3); + border-bottom: 1px solid rgba(0,0,0,0.3); +} + +#gamemenu ul li.disabled { + /* Grey out menu items with the "disabled" class */ + color: rgba(0,0,0,0.5); +} + +#gamemenu ul li:first-of-type { + /* Reinstate the left border for the leftmost top-level menu item */ + border-left: 1px solid rgba(0,0,0,0.3); +} + +#gamemenu ul li:hover { + /* When the mouse is over a menu item, highlight it */ + background: rgba(0,0,0,0.3); + /* Set position:relative, so that if this item has a submenu it can + * position itself relative to the parent item. */ + position: relative; +} + +#gamemenu ul li.disabled:hover { + /* Disabled menu items don't get a highlight on mouse hover */ + background: inherit; +} + +#gamemenu ul ul { + /* Second-level menus and below are not displayed by default */ + display: none; + /* When they are displayed, they are positioned immediately below + * their parent <li>, and with the left edge aligning */ + position: absolute; + top: 100%; + left: 0; + /* We must specify an explicit background colour for submenus, because + * they must be opaque (don't want other page contents showing through + * them). */ + background: white; + /* And make sure they appear in front. */ + z-index: 1; +} + +#gamemenu ul ul.left { + /* A second-level menu with class "left" aligns its right edge with + * its parent, rather than its left edge */ + left: inherit; right: 0; +} + +/* Menu items in second-level menus and below */ +#gamemenu ul ul li { + /* Go back to vertical stacking, for drop-down submenus */ + display: block; + /* Inhibit wrapping, so the submenu will expand its width as needed. */ + white-space: nowrap; + /* Override the text-align:center from above */ + text-align: left; + /* Don't make the text any smaller than the previous level of menu */ + font-size: 100%; + /* This time it's the top border that we omit on all but the first + * element in the submenu, since now they're vertically stacked */ + border-left: 1px solid rgba(0,0,0,0.3); + border-right: 1px solid rgba(0,0,0,0.3); + border-top: 0; + border-bottom: 1px solid rgba(0,0,0,0.3); +} + +#gamemenu ul ul li:first-of-type { + /* Reinstate top border for first item in a submenu */ + border-top: 1px solid rgba(0,0,0,0.3); +} + +#gamemenu ul ul ul { + /* Third-level submenus are drawn to the side of their parent menu + * item, not below it */ + top: 0; left: 100%; +} + +#gamemenu ul ul ul.left { + /* A submenu with class "left" goes to the left of its parent, + * not the right */ + left: inherit; right: 100%; +} + +#gamemenu ul li:hover > ul { + /* Last but by no means least, the all-important line that makes + * submenus be displayed! Any <ul> whose parent <li> is being + * hovered over gets display:block overriding the display:none + * from above. */ + display: block; +} +</style> </head> <body onLoad="initPuzzle();"> <h1 align=center>${puzzlename}</h1> @@ -73,16 +196,15 @@ ${unfinishedpara} <hr> <div id="puzzle" style="display: none"> -<p align=center> - <input type="button" id="new" value="New game"> - <input type="button" id="restart" value="Restart game"> - <input type="button" id="undo" value="Undo move"> - <input type="button" id="redo" value="Redo move"> - <input type="button" id="solve" value="Solve game"> - <input type="button" id="specific" value="Enter game ID"> - <input type="button" id="random" value="Enter random seed"> - <select id="gametype"></select> -</p> +<div id="gamemenu"><ul><li id="new">New game</li +><li id="restart">Restart game</li +><li id="undo">Undo move</li +><li id="redo">Redo move</li +><li id="solve">Solve game</li +><li id="specific">Enter game ID</li +><li id="random">Enter random seed</li +><li>Select game type<ul id="gametype" class="left"></ul></li +></ul></div> <div align=center> <div id="resizable" style="position:relative; left:0; top:0"> <canvas style="display: block" id="puzzlecanvas" width="1px" height="1px" tabindex="1"> |