96 Commits

Author SHA1 Message Date
7982002a64 js: Switch to window.requestAnimationFrame() for timing
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.
2022-11-09 21:40:27 +00:00
c5a2446fae js: Cancel UI events when the mid end says they've been handled
This means that if a key doesn't do anything in a puzzle, it can operate
the browser instead.
2022-11-08 10:27:19 +00:00
ad9ee5a524 js: Move much of the handling of device pixel ratios to the mid-end
Now that the mid-end knows how to do it, we can remove some complexity
from the front end.
2022-11-08 10:27:02 +00:00
fba22f04d6 js: Make update_pixel_ratio() more robust
With very small tile sizes, js_canvas_find_font_midpoint() can throw an
exception.  When it was called from update_pixel_ratio(), this prevented
the new MediaQueryList from being created, which meant that the puzzle
stopped noticing changes of device pixel ratio.

Now update_pixel_ratio() establishes a new MediaQueryList before calling
rescale_puzzle(), so the exception can't break it.  Catching the
exception properly would be even better, of course.
2022-11-08 00:57:32 +00:00
06f6e878a0 js: Tolerate the non-existence of some HTML elements
Specifically, the permalinks, the apology, and the resizable div.
2022-10-29 11:58:37 +01:00
fa58dd85b7 js: Distinguish manual resizes from device pixel ratio changes
This adds a new callback, rescale_puzzle(), that's called when the
device pixel ratio changes.  This means that resize_puzzle() can safely
set the nominal canvas size, which means that manual resizing of the
puzzle now sticks.

Still missing: paying attention to the device pixel ratio when choosing
the initial (or reset) size.
2022-10-27 22:51:54 +01:00
532d662722 js: Very bad attempt at making puzzles change size when zooming
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.
2022-10-27 22:51:48 +01:00
6f5debe417 js: Make update_pixel_ratio more backward-compatible
Despite my stylistic downgrades, it still used two features not present
in Firefox 48, and hence KaiOS 2.5: passing options to addEventListener,
and calling addEventListener on a MediaQueryList at all.  Now it uses
the older addListener method and explicitly removes each listener as
soon as it's called.
2022-10-26 21:57:53 +01:00
5fae5ca0db js: Be more subtle about cancelling keydown events
Now we only cancel a keydown event if the C keyboard handler recognises
the key and passes it on to the midend.  This doesn't necessarily mean
that the midend has actually done anything with it, of course.  Still,
this is enough to allow F12 to open the developer tools even when the
input focus is on the puzzle.  It also allows for tabbing out of the
puzzle into the links below it.
2022-10-25 20:38:37 +01:00
e6faebeb9a js: Remove keypress handler
At least in modern browsers (and I suspect in all browsers), cancelling
a keydown event ensures that the subsequent keypress event doesn't fire.
See <https://w3c.github.io/uievents/#keys-cancelable-keys>.

So there's no point in having a handler on keypress events that just
tries to cancel them as well.  Removing the handler doesn't do much now,
but it opens the possibility of being a bit more selective about which
keydown events we cancel.
2022-10-25 20:13:59 +01:00
768ef770a3 js: Use KeyboardEvent.key for ASCII keystrokes
This requires passing in KeyboardEvent.location from JavaScript so
that we can detect the numeric keypad properly.  Out of caution we
currently only set MOD_NUM_KEYPAD on numbers, like we always have,
but we have enough information to set it on arrow keys, Enter, "+",
etc.

This finally gets '/' and '\' working in Slant again.
2022-10-24 23:13:33 +01:00
0db5fb525b js: Remove the charCode argument from key()
It hasn't been used in a while.
2022-10-24 22:37:30 +01:00
a62217e9b4 js: Use less-modern syntax in update_pixel_ratio
Stealing code from the MDN has the consequence that it uses shiny ES6
features like "const", "let", and "=>".  This looks a bit odd among
the more conservative style of the rest of Puzzles, so I've
downgraded it to "var" and "function".  I'll let the template string
stay because that actually helps readability.
2022-10-23 11:15:31 +01:00
0254a163ff js: Make resizing of puzzles work properly again
This requires looking at the CSS size of the puzzle canvas rather than
its internal size, and then adjusting the new size to account for the
device pixel ratio.
2022-10-22 18:50:34 +01:00
989c6defb0 js: Pay attention to changes in device pixel ratio
Because it's the simplest thing to do, when we notice such a change we
keep the current puzzle at its existing size measured in device
pixels.  This has the rather odd consequence that when changing the
text size in Firefox, the size of the puzzle remains constant.
2022-10-22 18:40:01 +01:00
27f0dafcf0 js: Map mouse co-ordinates correctly even when CSS scales our canvas
Our system for mapping mouse coordinates to canvas coordinates assumed
that the puzzle canvas had the same dimensions in CSS as its own
internal width and height.  This is true in the current wrapper HTML,
but it's very easy to accidentally change and there are circumstances
where we might want to deliberately change it in future.

To fix this, we now inspect the CSS size of the canvas when processing
mouse events, and map the coordinates through the scaling and
translation necessary to convert CSS pixels into canvas pixels.
2022-10-22 13:55:50 +01:00
0197ca4359 js: Percent-encode game IDs in URLs and decode them again on input
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.
2022-10-21 10:28:01 +01:00
a46f0c2ba9 js: Read save files as text rather than binary strings
If I'm going to insist they're text I should be consistent about it.
2022-10-21 00:30:00 +01:00
47905e9547 Revert "WASM: move save file encoding from JS into C."
Now that save files are (even more) officially ASCII, it's perfectly
safe to pass them to JavaScript as UTF-8 strings.

This means that the form in which save files are shipped from C to
JavaScript is the same is the form in which they're shipped from
JavaScript to C.  That allows for doing new things with them, like
writing them to local storage.

This reverts commit f729f51e475ff98d0caf529f0723ef810b1c88ef.
2022-10-21 00:25:34 +01:00
dbbe9d3750 js: Make the dialogue box heading actually be an <h2>
This is semantically more correct and less ugly as well.
2022-10-18 01:00:49 +01:00
5c180cfa6f js: When making a hidden element visible, just remove "display: none"
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.
2022-10-17 23:17:59 +01:00
49849e40ec js: Move dialogue-box sizing and positioning from JavaScript to CSS
This has the advantage that if you resize the window while a dialogue
box is active, the dialogue box adjusts itself accordingly.
2022-10-17 23:16:31 +01:00
c90d64f243 js: Move most style settings from JavaScript to CSS
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.
2022-10-17 22:04:16 +01:00
f729f51e47 WASM: move save file encoding from JS into C.
The previous fix worked OK, but it was conceptually wrong. Puzzles
save files are better regarded as binary, not text: the length fields
are measured in bytes, so translating the file into a different
multibyte character encoding would invalidate them.

So it was wrong to fetch a C byte string containing the exactly right
binary data, then translate it into a Javascript string as if decoding
from UTF-8, then retranslate to a representation of a bytewise
encoding via encodeURIComponent, and then label the result as
application/octet-stream.

This probably wouldn't have caused any problems in practice, because I
don't remember any situation in which my save files use characters
outside printable ASCII (plus newline). But it's not actually
forbidden, so a save file might choose to do that some day, so that
UTF-8 decode/reencode hidden in the JS was a latent bug.

Now the URI-encoding is done on the C side, while we still know
exactly what the binary data ought to look like and can be sure we're
translating it byte for byte into the output encoding for the data:
URI. By the time the JS receives a string pointer from get_save_file,
it's already URI-encoded, which _guarantees_ that it's in ASCII and
won't be messed about with by Emscripten's UTF8ToString.
2021-05-23 08:45:55 +01:00
1c760b2ee8 WASM: fix save-file generation.
In commit f6434e84964d840 I said I had replaced all uses of
old-Emscripten's Pointer_stringify() function with new-Emscripten's
UTF8ToString(). In fact, I only replaced the ones in emcclib.js, but I
missed one in emccpre.js used in preparing downloadable save files.
Those were therefore broken, with a Javascript undefined-name error.
2021-05-22 21:04:22 +01:00
77866e1335 wasm/js/emscripten: Fix page loading race
Using a stunt webserver which artificially introduces a 3s delay just
before the last line of the HTML output, I have reproduced a
uwer-reported loading/startup race bug:

Previously the wasm loading was started by the <script> element,
synchronously. If the wasm loading is fast, and finishes before the
HTML loading, the onRuntimeInitialized event may occur before
initPuzzles.  But initPuzzles sets up the event handler.

Fix this bug, and introduce a new comment containing an argument for
the correctness of the new approach.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
2021-04-22 21:22:48 +01:00
f6434e8496 Update web puzzles to current WASM-based Emscripten.
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.
2021-04-03 09:22:49 +01:00
866354ef62 Javascript frontend: make Shift- and Ctrl-click work.
In other front ends, Shift-click is an alternative to the middle
button, and Ctrl-click the right button. Apparently I completely
forgot to implement this in the JS front end. Better late than never.
2019-04-12 23:38:42 +01:00
721119e4a6 Support for loading games in Javascript puzzles.
This is done by showing a dialog containing an <input type="file">
through which the user can 'upload' a save file - though, of course,
the 'upload' doesn't go to any HTTP server, but only into the mind of
the Javascript running in the same browser.

It would be even nicer to support drag-and-drop as an alternative UI
for getting the save file into the browser, but that isn't critical to
getting the first version of this feature out of the door.
2017-09-05 20:58:05 +01:00
1bf591a573 Support for saving games in Javascript puzzles.
This is done by getting midend_serialise to produce the complete
saved-game file as an in-memory string buffer, and then encoding that
into a data: URI which we provide to the user as a hyperlink in a
dialog box. The hyperlink has the 'download' attribute, which means
clicking on it should automatically offer to save the file, and also
lets me specify a not-too-silly default file name.
2017-09-05 20:56:55 +01:00
b31ea22167 Factor some HTML dialog functions out of emcclib.
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.
2017-09-05 20:55:11 +01:00
a7dc17c425 Rework the preset menu system to permit submenus.
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.
2017-04-26 21:51:23 +01:00
bc2c1f69fd Javascript puzzles: switch to a CSS-based drop-down system.
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.)
2017-04-26 21:48:11 +01:00
dd9e24a42f emcc frontend: stop indiscriminately squashing mouseups.
The mouseup listener was calling event.preventDefault(), as part of
the mechanism for making mouse clicks and drags on the puzzle's resize
handle have resizing effects _instead_ of the normal browser
behaviour. However, calling event.preventDefault() on _every_ mouseup,
rather than just the ones associated with the resize handle, was
overkill, and I've recently noticed that it's breaking attempts to
select from the game type dropdown by clicking the mouse. So now I'm
only calling preventDefault() on the mouseups that I have reason to
think are actually relevant to what I'm trying to do.

(I don't know why I've only just noticed this. I suppose a change of
behaviour between Firefox versions is the most likely cause.)
2015-08-14 19:42:42 +01:00
a7ddd63375 Change our method of calling main() in emccpre.js.
I've just upgraded to emcc 1.16.0, in which something fiddly has
happened to the semantics of Module.run() vs noInitialRun - now
setting the latter seems to cause the former to do everything except
calling main(), and then refuse to ever do anything again. So now I
have to use Module.callMain() in place of Module.run() when I finally
do get round to wanting to call main().

[originally from svn r10180]
2014-04-20 08:47:27 +00:00
c0fff857fd Add a draggable resize handle to the JS puzzles.
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]
2013-04-07 10:24:37 +00:00
ea25b606cb Small refactor to relative_mouse_coords: now the functionality which
returns an element's absolute position on the web page is split out
into a subfunction that can be called directly.

[originally from svn r9819]
2013-04-07 10:24:34 +00:00
36f35f5db8 Regretfully remove my trickery with a hidden <option> element inside
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]
2013-04-05 15:49:29 +00:00
2360b77812 Rewrite the JS keyboard handling to cope with IE and Chrome.
Unlike Firefox, IE and Chrome don't generate keypress events at all if
you suppress the default handling of keydowns. Therefore, we have to
figure out everything from the keydown event, because if we unsuppress
the default handling of any keydowns then we'll get annoyances like ^R
going back to meaning reload-page rather than redo-move.

[originally from svn r9810]
2013-04-05 15:49:27 +00:00
7479c2882d Stop accidentally subtracting onscreen_canvas.offset{Left,Top} from
the return value of relative_mouse_coords! I only got away with that
error because the canvas was at offset zero compared to its immediate
parent element.

[originally from svn r9808]
2013-04-05 15:49:24 +00:00
e6afc02942 IE doesn't default to giving focus to the puzzle canvas on a mouse
click, so manually implement that behaviour. (Without it, it's quite
hard to focus the canvas at all in IE.)

[originally from svn r9806]
2013-04-05 15:49:21 +00:00
841c9318f3 Remove trailing commas at the ends of initialiser lists. IE 8 and 9
didn't like them, which doesn't matter as such since they won't run
these JS puzzles anyway (no TypedArray support) but it hints that
other JS implementations might be picky about this too.

[originally from svn r9804]
2013-04-05 15:49:19 +00:00
3bc0e5cc24 Clarify header comments in the Emscripten frontend's source files to
mention that the HTML pages generated by html/jspage.pl are also an
integral part of this front end. (The NestedVM frontend is more
self-contained, needing only an appropriate <applet> tag, but this one
expects quite a few components to exist on the page and have the right
ids.)

[originally from svn r9803]
2013-04-05 15:49:18 +00:00
a752e73720 Try to give a more friendly message if anything goes wrong during
puzzle startup. The puzzle web pages now enclose the whole puzzle
(buttons, canvas, permalinks) in a div set to display:none, and
instead display an apologetic message saying 'sorry, it didn't work';
then, if we get through the whole init function without crashing, we
show the puzzle and hide the apology.

[originally from svn r9802]
2013-04-03 19:04:00 +00:00
9826ecd5c3 Apply a bodge to arrange that if the user selects Custom from the game
type dropdown, we still get an 'onchange' event if they select it a
second time. Normally this wouldn't happen, because onchange means
what it says and we only get it if a _different_ element is selected.

My solution is to create two list items called Custom, set one of them
as display:none to stop it showing up when the list is dropped down,
and to select it after the configuration box closes.

[originally from svn r9788]
2013-03-31 09:58:46 +00:00
49fba922ea New front end! To complement the webification of my puzzles via Java
applets, here's an alternative webification in Javascript, using
Emscripten in asm.js mode (so that as browsers incorporate asm.js
optimisation, the game generation should run really fast).

[originally from svn r9781]
2013-03-30 20:16:21 +00:00