aboutsummaryrefslogtreecommitdiff
path: root/emccpre.js (follow)
Commit message (Collapse)AuthorAge
* js: allow for multiple environment blocks in HTMLBen Harris2023-08-21
| | | | | | | Not that I actually need it, but it's just as easy to load multiple environment <script>s from the DOM as it is to load one, so we may as well do that. Since only one element can have id="environment", we do this by matching class="environment" as well.
* 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: Copy-to-clipboard supportBen Harris2023-07-05
| | | | | | | | | | | | | | Now using the browser's "copy" operation while the focus is in the puzzle will copy the puzzle state to the clipboard. Browsers seem to have odd ideas about whate element to target with the "copy" event: Firefox targets the parent of the <canvas> while Chromium targets the <body>. To cope with these and possible future weirdness I attach the event handler to the document and then look to see if it's plausibly related to the canvas. Arguably we might want to handle a wider range of "copy" events, maybe any where the selection isn't empty. I'm not sure, though, so we'll start with the minimal change.
* 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.
* 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: use the "load" event for loading save filesBen Harris2023-04-03
| | | | | | | | | | | | | | | This is in place of the "loadend" event. In Chromium, (and in the specification), "loadend" is triggered not only when the file is loaded but also when loading fails. Obviously when loading fails we don't want to try to parse the (nonexistent) resulting file. Using the "load" event works better, since it's only fired on success, and we can also have an "error" handler to report problems with loading files, albeit with no detail at all. This doesn't seem to make any difference in Firefox, which in my testing fires "load" and "loadend" on success and nothing at all on failure.
* 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: load games using FileReader.readAsArrayBuffer()Ben Harris2023-04-02
| | | | | | | | | | | | | | | | Using .readAsText() meant that trying to load a non-text file (for instance something that's not a save file at all) would generate an "RuntimeError: index out of bounds". This would then leave the Emscripten runtime in a broken state. It might even be possible for a real save file not to be valid UTF-8, for instance if it came from a platform that used a different character encoding for random seeds. There's still a problem with opening very large files, apparently because Emscripten tries to stuff the entire file onto the C stack. That will probably have to be fixed by properly exposing the incremental file-loading API to JavaScript.
* js: Use the Pointer Events API, but only to capture the pointerBen Harris2023-03-01
| | | | | | | | | | | | Element.setPointerCapture() captures both pointer and mouse events, so we can use our existing mouse handlers and just have a minimal pointerdown handler that calls setPointerCapture(). This saves (or at least postpones) all the tedious rewriting referred to in ecd868ac. This means that now drags beyond the puzzle area work in WebKit-based browsers, and we don't get deprecation warnings in current Gecko-based ones. Older Gecko-based browsers continue to use Element.setCapture() and hence still work correctly.
* Revert "JS puzzles: use the PointerEvent API if available."Simon Tatham2023-02-23
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | This reverts commit 9d7c2b8c83506c1f239c840e372058fac603b255. I thought that switching from the JS 'mousedown', 'mousemove' and 'mouseup' events to the corresponding 'pointer*' events would make essentially no difference except that the pointer events would come with more information. But in fact it turns out that there's a fundamental change of semantics. If you press one mouse button down and then, without releasing it, press a second one, then the mouse API will send you this information in the form of two 'mousedown' events, one for each button. But the pointer API will only send you a 'pointerdown' for the first event, when the state of the pointer changes from 'no buttons down' to 'at least one button down'. The second button press will be delivered as a 'pointermove', in which the 'buttons' field is different from its previous value. I'm backing out the migration to PointerEvent for the moment, because that's too complicated for a trivial fix. In simple cases we could easily detect the changed buttons field in the pointermove handler and generate a call to the C side of this front end's mousedown() function, effectively converting the changed JS representation to the one the C was already expecting. But this also has to interact with our one-button support (converting Ctrl and Shift clicks into a different logical button) _and_ with the ad-hoc mechanism we use to avoid delivering buttonless mouse movements to the C side. So getting it right in all cases at once isn't trivial, and I'd rather revert the attempt now and think about it later than commit to getting it all perfect on short notice.
* JS puzzles: use the PointerEvent API if available.Simon Tatham2023-02-22
| | | | | | | | | | | | | | | | | If the browser knows what 'PointerEvent' means, then we switch our 'onmousefoo' event handlers to the 'onpointerfoo' events, for both the puzzle canvas and the resize handle. The immediate effect of this is that we get to use the setPointerCapture method on the puzzle canvas, in preference to the deprecated Firefox-only setCapture. A pointer event also contains extra fields compared to a mouse event: as well as telling you which pointing device the event comes from, it can also provide extra information, such as pressure, or the angle of a stylus if the hardware can detect it. I don't have any immediate ideas about what those could be used for, but it can't hurt to have them available just in case we think of something in future.
* Fix error about setCapture not existing.Simon Tatham2023-02-20
| | | | | | | | | | | | | | | | | | | element.setCapture only seems to exist in Firefox. On most other browsers, our attempt to call it must have been generating a whinge in the console log all along. But Ben's commit bb16b5a70ddf77d turned that into a prominent alert box, triggered on every mouse click in the puzzle canvas. Worked around by wrapping both calls to setCapture in a local subroutine which checks if it's there before calling it. Also, setCapture turns out to be deprecated in any case, according to https://developer.mozilla.org/en-US/docs/Web/API/Element/setCapture . It looks as if the non-deprecated version is element.setPointerCapture: https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture But it also looks as if that needs the 'pointerId' field that's only found in 'onpointerdown' events and not 'onmousedown' ones. So including that as an alternative will be a bigger job.
* js: Add a trivial error handler that alert()sBen Harris2023-02-19
| | | | | | | I'm not quite sure how useful it will be, but it does at least catch an assertion failure in main() and present an opaque message in a box, which is better than stopping and putting a message in the console where no-one will see it.
* js: Hide type menu if there's only one preset and no configurationBen Harris2023-02-16
| | | | It seems a bit silly to display it when there's only one option.
* js: Quicker keyboard access to menu itemsBen Harris2023-01-19
| | | | | | Now pressing "1" to "9" or "0" activate the first ten items of the current menu, to save so much pressing of the arrow keys. There isn't any visible indication of this, so it's a bit of an Easter egg.
* kaios: Make F10 open and close the menuBen Harris2023-01-19
| | | | | | This is no use on KaiOS itself, but provides a way to open the menu from the keyboard on desktop browsers for testing (and for keyboard-accessibility in general).
* 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: Look up elements in the DOM as early as possibleBen Harris2023-01-19
| | | | | | | | | | | | Now that our script is loaded using <script defer>, we can rely on the DOM's being complete as soon as it starts running. So when we declare a variable to point to a DOM element, we can initialise it with that element. This saves having these odd initialisations scattered around the code, usually but not always at the site of first use. I'd like to be able to do the same thing with the variables that point to C functions, but the Module.cwrap() call isn't entirely safe before Emscripten has finished loading the C code.
* 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: Make soft-key labels generate key events when clickedBen Harris2023-01-19
| | | | | This makes the app page a little easier to test on desktop browsers that don't have SoftLeft and SoftRight keys.
* kaios: Major parts of a build for KaiOSBen Harris2023-01-19
| | | | | | | | | | | | | | | | | | | | | | | | | | KaiOS (which is based on Firefox OS, formerly Boot to Gecko) runs its "native" apps in a Web browser, so this is essentially a rather specialised version of the JavaScript front-end. Indeed, the JavaScript and C parts are the same as the Web version. There are three major parts that are specific to the KaiOS build. First, there's manifest.pl, which generates a KaiOS-specific JSON manifest describing each puzzle. Second, there's a new HTML page generator, apppage.pl, that generates an HTML page that is much less like a Web page, and much more like an application, than the one generated by jspage.pl. It expects to build a single HTML page at a time and gets all its limited knowledge of the environment from its command line. This makes it gratuitously different from jspage.pl and javapage.pl, but makes it easier to run from the build system. And finally, there's the CMake glue that assembles the necessary parts for each application in a directory. This includes the manifest, the HTML, the JavaScript, the KaiOS-specific icons (generated as part of the GTK build) and a copy of the HTML documentation. The directory is assembled using CMake's install() function, and can be installed on a KaiOS device using the developer tools.
* js: Have the "SoftRight" key open the menu by focussing itBen Harris2023-01-19
|
* js: When opening a dialogue box, try to focus itBen Harris2022-12-10
| | | | This will make using menus from the keyboard more convenient.
* 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: 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: Bypass our own dialogue box when loadingBen Harris2022-12-05
| | | | | | | | By constructing the <input type=file> off screen and activating it from JavaScript, we can jump straight to the browser's upload dialogue box without interposing our own one. This gives a smoother experience, and also avoids the difficult-to-handle <input type=file> ever being visible.
* js: Correct a comment describing timer_callbackBen Harris2022-12-03
|
* 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: Switch to using the resize handle in the HTMLBen Harris2022-12-02
|
* js: Don't bother resizing offscreen canvas at startupBen Harris2022-12-02
| | | | | It will get its size set soon enough once we know the size of the puzzle anyway.
* js: Remove a JavaScript construct that confused emcc -O3Ben Harris2022-12-02
|
* js: Allow for putting a resize handle in HTMLBen Harris2022-11-29
|
* js: Correct co-ordinate-mapping function for what CSS actually doesBen Harris2022-11-25
| | | | | | | | | By default, CSS uses "object-fit: fill", which means that an object is independently scaled in both dimensions to fit its containing box. This is simpler than what I'd assumed (which was "object-fill: contain"). Obviously, the HTML could be changed to use a different object-fit, in which case this code would have to detect it, but for now following the CSS default is more correct than not.
* js: If the HTML contains a dialogue-box form, delete itBen Harris2022-11-24
| | | | | | This is so that (given time for caches to expire) I can switch to having a persistent dialogue box in HTML rather than fabricating it from scratch in JavaScript each time it's used.
* js: Disable menu keyboard controls when dialogue box is activeBen Harris2022-11-24
| | | | | I think this is broadly the wrong approach, but it's an improvement until I implement the right one.
* js: Add actions for more keys in menusBen Harris2022-11-23
| | | | | | I expect Escape to exit the menu, and SoftRight should do that as well for KaiOS. Backspace goes up one level through the menus, again because that's conventional on KaiOS and not too confusing elsewhere.
* js: Move global keyboard handler to capturing phaseBen Harris2022-11-23
| | | | | | | In the bubbling phase it managed to catch the "Enter" keypress that opened a dialogue box from the menu and use it to close the dialogue box again. I think it's probably reasonable to have it run earlier and just permanently steal any keypresses it wants.
* js: Move focus-tracking to entirely "focus" eventsBen Harris2022-11-23
| | | | | | | | | When we disable a button, it loses focus but doesn't generate a "blur" event. This means our "focus-within" class goes wrong. Instead of relying on "blur" events to remove the class, remove it from any inappropriate elements in the "focus" handler. This requires attaching the handler to the root element of the document, but I've got plans that need that anyway.
* js: Replace :focus-within with JS-maintained .focus-withinBen Harris2022-11-23
| | | | | | | Old browsers (like KaiOS 2.5) don't have :focus-within, but it's pretty easy to replace the pseudo-class with a real .focus-within class maintained by JavaScript event handlers. This is made only marginally fiddlier by the odd fact that "focus" and "blur" events don't bubble.
* 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: Tiny comment fixBen Harris2022-11-21
|
* 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: Create the puzzle resize handle only if the puzzle is resizableBen Harris2022-11-15
| | | | | If there's no resizable div to attach it to, there's not much point in creating the handle and the doing nothing with it.
* 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: Give keyboard focus to the puzzle canvas at startup againBen Harris2022-11-12
| | | | | | | I think this has been broken since a752e73, when the canvas changed to being hidden, and hence unable to receive keyboard focus, when the page loaded. I've now moved the focus() call to after the canvas gets displayed.
* js: Add a way to have environment variablesBen Harris2022-11-12
| | | | | | | | | | | | They can now be specified by sticking some JSON in a <script> element in the Web page: <script id="environment" type="application/json"> { "LOOPY_DEFAULT": "20x10t11dh" } </script> This isn't brilliantly useful, but it does allow for changing settings without recompiling.
* 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: Add various missing variable declarationsBen Harris2022-11-10
|
* 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().