aboutsummaryrefslogtreecommitdiff
path: root/emcclib.js (follow)
Commit message (Collapse)AuthorAge
* js: load preferences from HTML elementsBen Harris2023-08-21
| | | | | | | | | | | | | | | It will be useful on KaiOS to be able to specify default user preferences that aren't the standard ones, in the same way that we specify some environment variables. As with environment variables, we can now do this be embedding a <script> element in the HTML like this: <script class="preferences" type="text/plain"> show-labels=true </script> These are loaded before the preferences from localStorage, so they just set defaults and can be overridden by the user (but not on KaiOS yet, because we still don't have dialogue boxes there).
* js: prefer some puzzle size even if loading isn't completeBen Harris2023-08-21
| | | | | | | | | | | | | | | | | The js_canvas_get_preferred_size() function was declining to suggest a size for the puzzle if document.readyState wasn't "complete". I think my idea here was that if the document wasn't fully loaded then I couldn't trust the size of the containing <div>. While this was true, declining to provide a size didn't help much since the puzzle still needed a size, and the size of the containing <div> was the best guess we had. Now that function always returns the size of the containing <div> if it exists. This appears to mean that puzzles don't show a brief flash of being the wrong size on KaiOS. That was particularly visible with Flood, where the wrong-size version had borders around the tiles that the right-size version lacked. The containing <div> isn't used on the standard Web versions, so there's no change to behaviour there.
* js: keep colour strings in JavaScript rather than in CBen Harris2023-07-30
| | | | | | | | | | | | | | | The drawing routines in JavaScript used to take pointers to a C string containing a CSS colour name. That meant that JavaScript had to create a new JavaScript string on ever call to a drawing function, which seemed ugly. So now we instead pass colour numbers all the way down into JavaScript and keep an array of JavaScript strings there that can be re-used. The conversion from RGB triples to strings is still done in C, though. This doesn't seem to have fixed either of the bugs I hoped it would, but it does measurably improve drawing performance so I think it's worth doing.
* js: pass preferences file from JS to C on the heap, not the stackBen Harris2023-05-30
| | | | | | | | | | | | | | | | The C stack used by Emscripten is quite small, so passing more than a few klilobytes of data on it tends to cause an overflow. Current versions of puzzles will only generate tiny preferences files, but this might change in future and in any case Puzzles shouldn't crash just because the preferences in local storage have got corrupted. To fix this, we now have JavaScript allocate a suitable amount of C heap memory using malloc() and stick the preferences file in there. This could plausibly fail if the preferences file were really big, but that's unlikely since browsers generally limit an origin to about 5 MB of local storage. In any case, if malloc() does fail, we'll just ignore the preferences file, which is probably the right thing to do.
* js: handle exceptions when accessing localStorageBen Harris2023-05-30
| | | | | | | | | | | | | | | | | Trying to access window.localStorage will generate an exception if the local storage is for some reason inaccessible. This can be demonstrated in Firefox by configuring it to block a site from using site data. Writing to local storage might also cause an exception if, for instance, the quota of data for a site is exceeded. If an exception is raised while loading preferences we log the fact but don't make any report to the user, behaving as if no preferences were found. This avoids annoying the user on every visit to a puzzle page if they're not trying to use preferences. If something goes wrong when saving, we do currently report that to the user in an alert box. This seems reasonable since it's in response to an explicit user action.
* Emscripten: fix edge case of js_canvas_find_font_midpoint.Simon Tatham2023-05-26
| | | | | | | | | | | | | | | | | | | | If the puzzle canvas is at a ludicrously small size, so that you attempt to use a zero-height font, then obviously nothing sensible will appear in the way of text, but you'd at least like to avoid a crash. But currently, js_canvas_find_font_midpoint will make a canvas, print some height-0 text into it, and try to retrieve the image pixels to see what the actual font height was - and this will involve asking getImageData for a zero-sized rectangle of pixels, which is an error. Of course, there's only one possible return value from this function if the font height is 0, so we can just return it without going via getImageData at all. (This crash can be provoked by trying to resize the puzzle canvas to Far Too Small, or by interleaving canvas resizes with browser-tab zooming. I've had one report that it also occurs in less silly situations, which I haven't been able to reproduce. However, this seems like a general improvement anyway.)
* Emscripten: change the localStorage key used for preferences.Simon Tatham2023-04-24
| | | | | | | | | | | | I picked pathname + "#preferences" because Ben suggested either that or pathname + "/preferences", and I thought the former sounded neater. But Ben now suggests that it might be better to avoid using # in the key, just in case anyone should later want to use the whole URL _including_ the fragment ID as the key for some other localStorage usage - for example, autosaved progress per puzzle _instance_, keying off the puzzle URL with a fragment ID in the game description. So, per Ben's replacement suggestion, change the "#" to a space.
* Support user preferences in the Emscripten frontend.Simon Tatham2023-04-24
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Here, user preferences are stored in localStorage, so that they can persist when you come back to the same puzzle page later. localStorage is global across a whole web server, which means we need to take care to put our uses of it in a namespace reasonably unlikely to collide with unrelated web pages on the same server. Ben suggested that a good way to do this would be to store things in localStorage under keys derived from location.pathname. In this case I've appended a fragment id "#preferences" to that, so that space alongside it remains for storing other things we might want in future (such as serialised saved-game files used as quick-save slots). When loading preferences, I've chosen to pass the whole serialised preferences buffer from Javascript to C as a single C string argument to a callback, rather than reusing the existing system for C to read the save file a chunk at a time. Partly that's because preferences data is bounded in size whereas saved games can keep growing; also it's because the way I'm storing preferences data means it will be a UTF-8 string, and I didn't fancy trying to figure out byte offsets in the data on the JS side. I think at this point I should stop keeping a list in the docs of which frontends support preferences. Most of the in-tree ones do now, and that means the remaining interesting frontends are ones I don't have a full list of. At this moment I guess no out-of-tree frontends support preferences (unless someone is _very_ quick off the mark), but as and when that changes, I won't necessarily know, and don't want to have to keep updating the docs when I find out.
* js: Load save files into the C side incrementallyBen Harris2023-04-03
| | | | | | | | | | | | | | | | | | | | | | | | | Before this commit, JavaScript Puzzles loaded a save file by pushing the entire file onto the Emscripten stack and then reading it from there. This worked tolerably for typical save files, but Emscripten's stack defaults to only having 64 kiB of space. That meant that trying to load something that wasn't a real save file tended to cause a stack overflow. I expect that at least some real save files would suffer from the same problem. The stack overflow would generally cause a JavaScript exception and then leave the stack pointer outside the stack, so that any future attempt to call into C would fail as well. To fix this, arrange that the C function for reading data from the save file calls out to JavaScript. The JavaScript can then copy just the requested data into the caller's buffer. We can't pass a JavaScript function pointer to C, but since only one file can be loaded at a time, we can just have a global variable that's the current loading callback. There might still be a problem if you try to load a stupendously large file, since I think FileReader.readAsArrayBuffer() reads the whole file into the browser's RAM. It works on my laptop with files up to a few hundred megabytes, though.
* js: Tolerate the absence of various UI elements from the HTMLBen Harris2023-01-19
| | | | | To make things more consistent, the static buttons now all have their own variables.
* js: Simpler and more robust startup procedureBen Harris2023-01-19
| | | | | | | | | | | | | | | Previously, we initialised all of the JavaScript event handlers as soon at the DOM was loaded, and then called main() ourselves once the Emscripten runtime was ready. This was slightly dangerous since it depended on none of those event handlers' being called before main(). In practice this was difficult because most of the elements the event handlers were attached to were invisible, but it did limit what event handlers could safely be used. Now, the event handlers are initialised from main(). This makes things work in a sufficiently conventional way that we can just let the Emscripten run-time call main() in its usual way, rather than involving ourselves in the minutiae of Emscripten's startup.
* js: Use current_key_label() to label feature phone softkeysBen Harris2023-01-19
|
* js: Add a mode where the puzzle tries to fill the viewportBen Harris2022-12-10
| | | | | | | | | | | | | | | | This is activated by putting the puzzle in an element with id "puzzlecanvascontain". In that case, the puzzle's default size is as close to filling that element as is achievable. Unlike in the normal mode, this sets the CSS size of the canvas directly. Because it might take a little while for the page to settle down after loading, and because the size of the viewport might change, this listens for "resize" and "load" events, and only bothers changing anything when the page is fully loaded. Waiting for the document to be complete might be a problem if we had images and so forth that we could plausibly be waiting for, but we don't.
* js: Add a new function whereby C can ask JS for a preferred board sizeBen Harris2022-12-10
| | | | Currently JS has no opinion.
* js: Set the default colour from the CSS background of the canvasBen Harris2022-12-10
| | | | | | | This allows the HTML/CSS to decide that it would like a different background colour without the need to recompile. The default if the CSS specifies no colour (and hence the canvas is transparent) is the same grey as before.
* js: Allow CSS to set the font used by the puzzleBen Harris2022-12-10
| | | | | | | | | | | | | | This means that the calculated font properties of the HTML canvas now control what font is used. The size is overridden, and for monospaced text so is the family. I'd like to be able to also specify the monospaced font, maybe using a CSS variable, but that looks like being quite a lot of extra complexity. My experience when testing this was that constructing a valid "font" string for a canvas context is prone to breakage, but broke in a way that left the font unchanged, so we always set a simple specification first before trying to construct one from CSS.
* js: Put the puzzle background colour in a CSS variableBen Harris2022-12-05
| | | | | | | | | It's sometimes useful to be able to have an HTML element that visually forms an extension of the puzzle's border. By putting the puzzle's colour 0 (which we assume to be its background) into a CSS variable, such elements can do something like "background-color: var(--puzzle-background)" to get that effect even if the puzzle uses a non-default background colour.
* js: Simplify drawing context managementBen Harris2022-12-03
| | | | | | | | | | | | | There's not much point in re-requesting the drawing context from the offscreen canvas at the start of each drawing operation. The canvas keeps the context around and returns it on every call to getContext(), so we may as well just keep our reference to it too. This does mean that the front-end won't detect puzzles drawing outside of a redraw operation, but I think it's the mid-end's job to assert things like that. Rumours that I'm doing this because I had a mysterious bug whereby ctx was unexpectedly null are entirely true.
* js: Remove support for creating the status bar in JavaScriptBen Harris2022-11-28
| | | | Now we depend on its being in the HTML.
* js: Remove alpha channel from almost all our canvasesBen Harris2022-11-25
| | | | | | | | | | Specifying the { alpha: false } option when creating a rendering context tells the browser that we won't use transparency, and the standard puzzle canvases, along with blitters, are all opaque. No obvious effect, but the MDN suggests that this should reduce CPU usage. The one exception here is the resize handle, which actually is transparent.
* js: Add keyboard navigation for menusBen Harris2022-11-23
| | | | | | | Once the input focus is in the menu system (for instance by Shift+Tab from the puzzle), you can move left and right through the menu bar and up and down within each menu. Enter selects a menu item. The current menu item is tracked by giving it the input focus.
* js: When removing the status bar, null out its variableBen Harris2022-11-21
| | | | It seems polite to allow it to be garbage-collected.
* js: Allow status bar to be present in the HTMLBen Harris2022-11-20
| | | | | | | I'm generally in favour of putting HTML in HTML rather the constructing it in JavaScript, and this will allow for simplifying the code eventually. This only changes the JavaScript to make sure that's in people's caches before I change the HTML itself.
* js: Insert a space in game-type submenu headings as wellBen Harris2022-11-15
|
* js: Convert space after tick in menus to a space characterBen Harris2022-11-15
| | | | | | | | | | | Older Firefox versions don't support "-moz-appearance: none" on radio buttons, which seems to mean that the specifies padding for them doesn't appear. Using a space character instead works fine, so do that everywhere. This seems to move the text slightly closer to the tick on browsers that do support "appearance: none", but the result is quite acceptable. This also makes the focus outline on the ticks slightly less weird.
* js: Better handling of games without presets and/or solveBen Harris2022-11-13
| | | | | | | | | | | | | | | | | | Games with neither presets nor configuration (which may only be the Null Game) have been slightly broken since the introduction of hierarchical preset menus, in that the code to remove the "Type..." menu stopped being called then. My switch to using radio buttons in menus then broke them utterly because it's not possible to set the value of an empty radio group, causing a crash at startup. Fix this by detected when there's no preset menu, removing the item from the menu bar, and setting the variable that's meant to indicate this has been done. The solve button problem was more subtle, in that only the <button> was being hidden and not the <li> containing it, which led to the right border of the menu bar being two pixels thick. Switch to fully removing the <li> from the DOM, like we now do with the presets menu, since that also makes my keyboard handler (in another branch) simpler.
* js: Substantially simplify timer codeBen Harris2022-11-13
| | | | | | | | | | The C code in the Emscripten front-end already keeps a timer_active variable to ensure that the timer can be activated only when it's inactive, and deactivated only when it's active. Adjusting the JavaScript side to rely on this makes the code much simpler. The only oddity is that it now requests a new animation frame before calling the callback so that it's ready to be cancelled if the callback decides to deactivate the timer.
* js: Label all form controls and put controls inside labelsBen Harris2022-11-12
| | | | | This should help with accessibility and means we don't need to give IDs to tick-boxes.
* js: Convert menus to use semantically appropriate HTML elementsBen Harris2022-11-12
| | | | | | | | | | | | | | | | | Presets are now radio buttons with labels, and menu items that take actions are now buttons. The <li> representing each menu item is now a thin wrapper around another element: a <label> for radio buttons, a <button> for other buttons, and a <div> for submenu headings. All of the things that previously applied to the <li> now apply to that inner element instead. This means that presets can now use the standard "checked" attribute to indicate which one is selected, and buttons can be disabled using the standard "disabled" attribute. It also means that we can query and set the state of all the presets at once through their RadioNodeList. I think this should also make the menus more accessible, and make it easier to make them keyboard-controllable.
* js: Reinstate a missing variable declarationBen Harris2022-11-09
| | | | | | ... and then decide there was no excuse for renaming the variable, so now it has the same name it had before I started using Window.requestAnimationFrame().
* js: Switch to window.requestAnimationFrame() for timingBen Harris2022-11-09
| | | | | | | | | | This is an API specifically designed for the purposes of timing animations. Unlike setInterval, it tries to synchronise with the screen refresh rate. It naturally passes us timing information, saving the need to construct a Date object every frame. It has the nice feature that browsers (at least Firefox 91) will call it less frequently when the puzzle page isn't visible, which saves CPU time in puzzles that run a timer continuously.
* js: Move much of the handling of device pixel ratios to the mid-endBen Harris2022-11-08
| | | | | Now that the mid-end knows how to do it, we can remove some complexity from the front end.
* js: Tolerate the non-existence of some HTML elementsBen Harris2022-10-29
| | | | Specifically, the permalinks, the apology, and the resizable div.
* js: Take device pixel ratio into account when setting default sizeBen Harris2022-10-27
| | | | | | | | | | | | | | This is a bit of a hack. When setting the puzzle to its default size, either at startup or from a right-click on the resize handle, we now scale the default size from midend_size() by the device pixel ratio, and then pass that back to midend_size(). This does more or less the right thing, in that the puzzle now starts up at a size that scales with the font size. There are still some slight inconsistencies, where sequences of DPR changes and puzzle parameter changes can have order-dependent effects on the size of the puzzle. Happily these effects are small and fairly hard to observe.
* js: Split setting nominal and actual canvas sizeBen Harris2022-10-27
| | | | | Now zooming in and out repeatedly doesn't cause the canvas to wither away, but user resizes don't stick any more. Still more to do.
* js: Very bad attempt at making puzzles change size when zoomingBen Harris2022-10-27
| | | | | | This has the entertaining consequence that repeatedly zooming in and out causes puzzles to gradually shrink, thus demonstrating that recording the nominal size correctly will be necessary.
* js: Use String.replace() in place of .replaceAll()Ben Harris2022-10-26
| | | | | The latter turns out to be a little too new for KaiOS 2.5, but .replace() will do global replacements if fed a RegExp.
* js: Pay attention to the device pixel ratioBen Harris2022-10-22
| | | | | | | | | | | | | | | | | | | | The CSS "px" unit isn't always a device pixel. On devices with high-DPI displays, there can often be multiple device pixels to a CSS px, while in particularly low-resolution displays (like feature phones), the user might zoom out to get several CSS px to a device pixel. And even on desktop browsers, text zooming controls can change the ratio. To make Puzzles' rendering look good on an arbitrary device pixel ratio, we really want the pixels of the canvas to be device pixels, not CSS px, so that the canvas doesn't have to be scaled by the browser for display. To correct this, we now control the CSS size of the puzzle canvas, via its containing <div>, to be the canvas size divided by the device pixel ratio. There is a significant gap, which is that this doesn't yet track changes to the device pixel ratio. This is slightly complicated, so I'll put it off to the next commit.
* js: Percent-encode game IDs in URLs and decode them again on inputBen Harris2022-10-21
| | | | | | | | | | | | This is necessary to allow all random seeds to round-trip properly. It's probably not currently necessary for descriptive game IDs, but it won't hurt. I've deliberately gone for encoding only those characters that are not valid in fragment identifiers to minimise the ugliness of the generated URLs. For slightly interesting historical reasons, '#' is not valid in a fragment identifier, so all random seed links end up a little bit ugly.
* js: When making a hidden element visible, just remove "display: none"Ben Harris2022-10-17
| | | | | | | | | | | This removes any assumption in the JavaScript code about precisely what "display" setting the element should have. This means that now the only places where the JavaScript manipulates elements' styles are to set the width of the puzzle container and to mark and unmark elements with "display: none". These both seem like reasonable kinds of semantic markup that just happen to be expressed as styles.
* js: Remove unnecessary setting of status bar sizeBen Harris2022-10-17
| | | | | | An element with display: block will naturally adjust to fit the width of its container, so if that's what you want there's no need to set its width explicitly.
* js: Move most style settings from JavaScript to CSSBen Harris2022-10-17
| | | | | | | | | Some elements (generally those created by JavaScript) had their style parameters set directly by JavaScript. Putting styles in CSS generally makes them easier to understand (and fiddle with), so I've done that. The only styles left in JavaScript are those that are calculated by JavaScript (like the status-bar size) and the random-seed permalink visibility because I wasn't quite sure how to handle it.
* Update web puzzles to current WASM-based Emscripten.Simon Tatham2021-04-03
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | I presume this will improve performance. Also, if I've understood correctly, WASM-based compiled web code is capable of automatically growing its memory, which the previous asm.js build of the puzzles could not do, and occasionally caused people to complain that if they tried to play a _really big_ game in their browser, the JS would eventually freeze because the emulated memory ran out. I've been putting off doing this for ages because my previous Emscripten build setup was so finicky that I didn't like to meddle with it. But now that the new cmake system in this source tree makes things generally easier, and particularly since I've just found out that the up-to-date Emscripten is available as a Docker image (namely "emscripten/emsdk"), this seemed like a good moment to give it a try. The source and build changes required for this update weren't too onerous. I was half expecting a huge API upheaval, and indeed there was _some_ change, but very little: - in the JS initPuzzle function, move the call to Module.callMain() into Module.onRuntimeInitialized instead of doing it at the top level, because New Emscripten's .js output likes to load the accompanying .wasm file asynchronously, so you can't call the WASM main() until it actually exists. - in the JS-side library code, replace all uses of Emscripten's Pointer_stringify() function with the new name UTF8ToString(). (The new version also has an ASCIIToString(), so I guess the reason for the name change is that now you get to choose which character set you meant. I need to use UTF-8, so that the × and ÷ signs in Keen will work.) - set EXTRA_EXPORTED_RUNTIME_METHODS=[cwrap,callMain] on the emcc link command line, otherwise they aren't available for my JS setup code to call. - (removed -s ASM_JS=1 from the link options, though I'm not actually sure it made any difference one way or the other in the new WASM world) - be prepared for a set of .wasm files to show up as build products alongside the .js ones. - stop building with -DCMAKE_BUILD_TYPE=Release! I'm not sure why that was needed, but if I leave that flag on my cmake command line, the output .js file fails to embed my emccpre.js, so the initial call to initPuzzle() fails from the HTML wrapper page, meaning nothing at all happens.
* Factor some HTML dialog functions out of emcclib.Simon Tatham2017-09-05
| | | | | | | | | | | | | | I'm about to want to call these from Javascript as well as from Emscripten-compiled C, so I need versions of them that aren't wrapped up in the Emscripten library object and also don't expect half their parameters to be Emscripten-verse char pointers requiring calls to Pointer_stringify. The easiest way to achieve all of that is to turn the Emscripten- ready versions of those functions in emcclib.js into tiny wrappers around the JS versions, which do the pointer stringification and a couple of other details like directing callbacks to the right C functions.
* Organise the JS menus/buttons bar more like a menu.Simon Tatham2017-09-05
| | | | | | | | | | | | | | | | | | | | | | | | I'm about to introduce a few more options, and the button bar is already a bit wide, so I think I should shrink it horizontally before putting more stuff on it. So I've organised the options into something more like the Game and Type submenus that the desktop versions use. However, I haven't gone quite all the way, on the basis that the web versions will be at least slightly playable on devices without much keyboard, which makes it worth keeping the in-play actions (Undo, Redo, and to a lesser extent Restart and Solve) accessible as top-level buttons in a single click each. As part of this change, I've also separated the menu bar into a drop-down menus section and a top-level buttons section with a gap between them, and put a signalling "..." on the end of the titles in the former section. This change also removes the class="left" on the game-type menu and its submenus, which were previously there to deal with that menu being all the way over on the far right of the menu bar. But the CSS for those classes is still there in jspage.pl, and should still work if I need it again in future.
* Rework the preset menu system to permit submenus.Simon Tatham2017-04-26
| | | | | | | | | | | | | | | | | | | | To do this, I've completely replaced the API between mid-end and front end, so any downstream front end maintainers will have to do some rewriting of their own (sorry). I've done the necessary work in all five of the front ends I keep in-tree here - Windows, GTK, OS X, Javascript/Emscripten, and Java/NestedVM - and I've done it in various different styles (as each front end found most convenient), so that should provide a variety of sample code to show downstreams how, if they should need it. I've left in the old puzzle back-end API function to return a flat list of presets, so for the moment, all the puzzle backends are unchanged apart from an extra null pointer appearing in their top-level game structure. In a future commit I'll actually use the new feature in a puzzle; perhaps in the further future it might make sense to migrate all the puzzles to the new API and stop providing back ends with two alternative ways of doing things, but this seemed like enough upheaval for one day.
* Javascript puzzles: switch to a CSS-based drop-down system.Simon Tatham2017-04-26
| | | | | | | | | | | | | | | The previous control buttons and dropdowns based on form elements were always a bit ugly: partly in a purely visual sense, and partly because of the nasty bodge I had to do with splitting the usual 'Custom' game type menu item into two (to get round the fact that if an element of a <select> is already selected, browsers won't send an event when it's re-selected). Also, I'm about to want to introduce hierarchical submenus in the Type menu, and <select> doesn't support that at all. So here's a replacement system which does everything by CSS properties, including the popping-up of menus when the mouse moves over their parent menu item. (Thanks to the Internet in general for showing me how that trick is done.)
* Fix vertically misaligned text in Javascript puzzles.Simon Tatham2014-06-21
| | | | | | | | | | When I constructed my test canvas to measure the vertical extent of a text string for ALIGN_VCENTRE, I forgot to ensure that the 'width' variable was an integer, as a result of which the loop over canvas pixels kept trying to use fractional array indices and returning undefined :-) [originally from svn r10196]
* Add a draggable resize handle to the JS puzzles.Simon Tatham2013-04-07
| | | | | | | | | | | | | | | | | | | | | | | Rather than design an ersatz 'window frame' surrounding the puzzle canvas, I've simply overlaid the resize handle on the corner of the puzzle itself (canvas or status bar, depending on whether the latter exists), trusting that all games in my collection provide a reasonable border within their drawing area. (OS X already does this with its resize handle, so it's not as if there's no precedent.) Unlike the desktop versions, I control the resize behaviour completely in this environment, so I can constrain the canvas to only ever be sensible sizes with no dead space round the edges (and, in particular, preserve the aspect ratio). Right-clicking the resize handle will restore the puzzle's default tile size. I had intended to implement a maximise-to-browser-window button too, but was annoyingly foiled by scrollbars - if you maximise to the current window width, and as a result the text below the puzzle scrolls off the bottom, then a vertical scrollbar appears and eats into the width you just maximised to. Gah. [originally from svn r9822]
* Regretfully remove my trickery with a hidden <option> element insideSimon Tatham2013-04-05
| | | | | | | | | | | | | | | | | the game-type <select>, since IE turns out to ignore display:none on options. Oh well. Instead I now do a more transparent thing: when custom game params are in use, there's a "Custom" option selected in the dropdown, and a separate 'Re-customise' option which brings the config box back up. When an ordinary preset is selected, the Custom option is missing, and there's just a 'Customise'. In the process I've tinkered a bit to arrange that the custom 'preset' is always represented by a negative number rather than one past the last real preset; that seems more consistent overall. [originally from svn r9811]