82 Commits

Author SHA1 Message Date
a8b544d2aa Add USE_DRAW_POLYGON_FALLBACK build option for testing.
This new option, when enabled, forces the in-tree front ends
(Emcscripten, GTK, NestedVM, OS X, and Windows) to use the recently
introduced draw_polygon_fallback() in place of their native
draw_poly(). This will enable easy testing of this function in the
future.

This new option is off by default. To enable it, run CMake as:

$ cmake -DUSE_DRAW_POLYGON_FALLBACK=on

Note that I did _not_ update the Postscript frontend (ps.c) to use
this new option, as I don't think draw_polygon_fallback() would work
at all in Postscript, where the drawing units are no longer guaranteed
to be pixels. The envisioned use case for this option is a developer
testing changes to this function for sanity and/or performance, which
I only foresee happening on a standard GUI front end.
2024-08-15 08:45:59 +01:00
f37913002a Refine drawing API semantics to pass drawing * instead of void *
This changes the drawing API so that implementations receive a
`drawing *` pointer with each call, instead of a `void *` pointer as
they did previously. The `void *` context pointer has been moved to be
a member of the `drawing` structure (which has been made public), from
which it can be retrieved via the new `GET_HANDLE_AS_TYPE()` macro. To
signal this breaking change to downstream front end authors, I've
added a version number to the `drawing_api` struct, which will
hopefully force them to notice.

The motivation for this change is the upcoming introduction of a
draw_polygon_fallback() function, which will use a series of calls to
draw_line() to perform software polygon rasterization on platforms
without a native polygon fill primitive. This function is fairly
large, so I desired that it not be included in the binary
distribution, except on platforms which require it (e.g. my Rockbox
port). One way to achieve this is via link-time optimization (LTO,
a.k.a. "interprocedural optimization"/IPO), so that the code is
unconditionally compiled (preventing bit-rot) but only included in the
linked executable if it is actually referenced from elsewhere.
Practically, this precludes the otherwise straightforward route of
including a run-time check of the `draw_polygon` pointer in the
drawing.c middleware. Instead, Simon recommended that a front end be
able to set its `draw_polygon` field to point to
draw_polygon_fallback(). However, the old drawing API's semantics of
passing a `void *` pointer prevented this from working in practice,
since draw_polygon_fallback(), implemented in middleware, would not be
able to perform any drawing operations without a `drawing *` pointer;
with the new API, this restriction is removed, clearing the way for
that function's introduction.

This is a breaking change for front ends, which must update their
implementations of the drawing API to conform. The migration process
is fairly straightforward: every drawing API function which previously
took a `void *` context pointer should be updated to take a `drawing *`
pointer in its place. Then, where each such function would have
previously casted the `void *` pointer to a meaningful type, they now
instead retrieve the context pointer from the `handle` field of the
`drawing` structure. To make this transition easier, the
`GET_HANDLE_AS_TYPE()` macro is introduced to wrap the context pointer
retrieval (see below for usage).

As an example, an old drawing API function implementation would have
looked like this:

void frontend_draw_func(void *handle, ...)
{
    frontend *fe = (frontend *)handle;
    /* do stuff with fe */
}

After this change, that function would be rewritten as:

void frontend_draw_func(drawing *dr, ...)
{
    frontend *fe = GET_HANDLE_AS_TYPE(dr, frontend);
    /* do stuff with fe */
}

I have already made these changes to all the in-tree front ends, but
out-of-tree front ends will need to follow the procedure outlined
above.

Simon pointed out that changing the drawing API function pointer
signatures to take `drawing *` instead of `void *` results only in a
compiler warning, not an outright error. Thus, I've introduced a
version field to the beginning of the `drawing_api` struct, which will
cause a compilation error and hopefully force front ends to notice
this. This field should be set to 1 for now. Going forward, it will
provide a clear means of communicating future breaking API changes.
2024-08-15 08:45:59 +01:00
26a3b98f4f Remove a comment suggesting use of localStorage for prefs
Preferences in the JavaScript version _are_ stored in localStorage
now.
2023-08-21 09:54:49 +01:00
76da6ec140 js: keep colour strings in JavaScript rather than in C
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.
2023-07-30 11:50:25 +01:00
ad7042db98 js: Copy-to-clipboard support
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.
2023-07-05 19:39:57 +01:00
1547154efb Expose the NO_EFFECT/UNUSED distinction through midend_process_key()
This removed the "handled" pointer and instead extends the existing
boolean return value (quit or don't quit) into an enumeration.  One of
the values still quits the program, but now there are different values
for keys that had an effect, had no effect, and are not used by the
puzzle at all.  The mapping from interpret_move results to process_key
results is roughly:

move string    -> PKR_SOME_EFFECT
MOVE_UI_UPDATE -> PKR_SOME_EFFECT
MOVE_NO_EFFECT -> PKR_NO_EFFECT
MOVE_UNUSED    -> PKR_UNUSED

The mid-end can also generate results internally, and is the only place
that PKR_QUIT can arise.

For compatibility, PKR_QUIT is zero, so anything expecting a false
return value to mean quit will be unsurprised.  The other values are
ordered so that lower values indicate a greater amount of handling of
the key.
2023-06-11 00:33:28 +01:00
43db4aa38e Support user preferences in the Emscripten frontend.
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.
2023-04-24 10:17:33 +01:00
2b6d34adbd emcc.c: remove savefile_read_ctx.
It wasn't ever used! Looks as if I pasted it in here from one of the
other implementations, before realising that wasn't how I was going to
read save files at all.
2023-04-24 10:04:20 +01:00
8c968483f8 emcc.c: missing (void) in a function definition.
This isn't C++, ahem.
2023-04-24 10:04:20 +01:00
36c282aaa9 js: Load save files into the C side incrementally
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.
2023-04-03 21:09:57 +01:00
09c15f206e New shared function, getenv_bool()
This provides a standard way to get a boolean from an environment
variable.  It treats the variable as true iff its value begins with 'y'
or 'Y', like most of the current implementations.  The function takes a
default value which it returns if the environment variable is undefined.

This replaces the various ad-hoc tests of environment variable scattered
around and mostly doesn't change their behaviour.  The exceptions are
TOWERS_2D in Towers and DEBUG_PUZZLES in the Windows front end.  Both of
those were treated as true if they were defined at all, but now follow
the same rules as other boolean environment variables.
2023-03-22 16:06:18 +00:00
9dbcfa765b More cleverness in midend_process_key()
It now strips off modifier flags from keys that shouldn't have them and
maps printable characters with MOD_CTRL to the corresponding control
characters.  It also catches Ctrl+Shift+Z because that obviously belongs
in the midend.

I've updated the JavaScript front-end to take advantage of these
changes.  Other front ends are unchanged and should work just as they
did before.
2023-02-23 23:16:18 +00:00
e8ac0381f9 Convert a lot of floating-point constants to single precision
For reasons now lost to history, Puzzles generally uses single-precision
floating point.  However, C floating-point constants are by default
double-precision, and if they're then operated on along with a
single-precision variable the value of the variable gets promoted to
double precision, then the operation gets done, and then often the
result gets converted back to single precision again.

This is obviously silly, so I've used Clang's "-Wdouble-promotion" to
find instances of this and mark the constants as single-precision as
well.  This is a bit awkward for PI, which ends up with a cast.  Maybe
there should be a PIF, or maybe PI should just be single-precision.

This doesn't eliminate all warnings from -Wdouble-promotion.  Some of
the others might merit fixing but adding explicit casts to double just
to shut the compiler up would be going too far, I feel.
2023-02-19 12:41:13 +00:00
a35405ca35 Make emcc.c clean under -Wmissing-prototypes etc
Also -Wstrict-prototypes and -Wmissing-variable-declarations.

Functions that are called from JavaScript now have a separate
declaration at the top of the file with a comment reminding one to
update emcclib.js if they're changed.
2023-02-18 18:12:49 +00:00
ec4335e07f js: Hide type menu if there's only one preset and no configuration
It seems a bit silly to display it when there's only one option.
2023-02-16 19:15:42 +00:00
1eba6388bf kaios: Hack out everything that needs dialogue boxes
They're proving to be a right nuisance and will probably require a
substantial overhaul to how they work across the entire JavaScript
front-end.  I don't think any of the functionality provided by the
dialogue boxes is critical, so in the interests of getting a minimum
viable product actually released I've disabled those features.

In most cases, this just involves commenting out bits of HTML.  The
"Custom..." menu item is added by C code, though, so there I've fallen
back to the standard Puzzles way to implement a nasty hack: an
environment variable.
2023-01-19 20:34:48 +00:00
81b6bccab6 js: Remove an outdated reference to the "invisible Custom option" 2023-01-19 20:34:48 +00:00
9d7b044c01 js: Simpler and more robust startup procedure
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.
2023-01-19 20:34:48 +00:00
420663d477 js: Use current_key_label() to label feature phone softkeys 2023-01-19 20:34:48 +00:00
e5604ccf37 js: Rename update_undo_redo() as post_move()
I want to do more things with it.
2023-01-19 20:34:48 +00:00
ebb7b4277e js: Don't treat SoftRight as CURSOR_SELECT2
I was hoping that I could treat SoftLeft and SoftRight the same in C and
arrange to filter one of them out in JavaScript, but that turned out to
be impractical.
2022-12-10 19:07:47 +00:00
33b5c48429 js: Add a new function whereby C can ask JS for a preferred board size
Currently JS has no opinion.
2022-12-10 18:33:00 +00:00
14eb35da4a js: Set the default colour from the CSS background of the canvas
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.
2022-12-10 17:58:41 +00:00
02f1d55a02 js: Allow CSS to set the font used by the puzzle
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.
2022-12-10 15:30:49 +00:00
ea223a2350 js: Put the puzzle background colour in a CSS variable
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.
2022-12-05 23:25:12 +00:00
b967a3ea86 js: Remove support for creating the status bar in JavaScript
Now we depend on its being in the HTML.
2022-11-28 22:51:15 +00:00
271fb7f47c js: More conventional marking of menu item types
Menu items that open dialogue boxes are now marked with ellipses, while
menu items that lead to submenus have pointing triangles.

The triangles are implemented as SVG files embedded in data: URLs in the
CSS, which is kind of silly but also works really well.  There are
suitable characters in Unicode, but some of my test systems don't have
fonts containing them, so maybe the SVG is better.
2022-11-24 16:24:59 +00:00
77c8b50834 js: Allow status bar to be present in the HTML
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.
2022-11-20 19:10:23 +00:00
a90bb4a4ef js: Better handling of games without presets and/or solve
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.
2022-11-13 14:05:55 +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
4a37f7cf78 Add a way for midend_process_key() to report whether it handled a keypress
This adds a new bool * argument, which can be NULL if front ends don't
care whether the keypress was handled.  Currently they all do that.

Currently, "undo" and "redo" keys are treated as not handled if there's
no move to undo or redo.  This may be a little too strict.
2022-11-08 10:27:19 +00:00
4fdcc54975 js: Make SoftRight act as CURSOR_SELECT2 as well
This way, the front end can intercept one of SoftLeft and SoftRight as a
menu key and leave the other one for the puzzle.  And while we don't
have a working menu, I can use whichever is more convenient.
2022-11-08 10:27:19 +00:00
ee5b02b0ca js: Map the "SoftLeft" key to CURSOR_SELECT2
This is the left soft key on KaiOS phones.  The centre soft key
already sends "Enter", which eventually becomes CURSOR_SELECT.  The
right soft key I'm planning to use to open the menu.
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
e45cd43aaa Teach the mid-end about device pixel ratios
The device pixel ratio indicates how many physical pixels there are in
the platonic ideal of a pixel, at least approximately.  In Web browsers,
the device pixel ratio is used to represent "retina" displays with
particularly high pixel densities, and also to reflect user-driven
zooming of the page to different text sizes.

The mid-end uses the device pixel ratio to adjust the tile size at
startup, and can also respond to changes in device pixel ratio by
adjusting the time size later.  This is accomplished through a new
argument to midend_size() which can simply be passed as 1.0 in any front
end that doesn't care about this.
2022-11-08 00:57:36 +00:00
1e8169ea94 js: Take device pixel ratio into account when setting default size
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.
2022-10-27 22:51:54 +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
9783bbfbc0 js: Split setting nominal and actual canvas size
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.
2022-10-27 22:51:54 +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
43c89dd5e1 js: Add a comment explaining the two halves of the key-matching code 2022-10-25 00:50:47 +01:00
d94d671bf9 js: Handle KeyboardEvent.key == "Spacebar"
This is apparently generated in place of " " by Internet Explorer.
2022-10-25 00:48:58 +01:00
35d382019f js: Recognise KeyboardEvent.key == "Escape" 2022-10-24 23:33:09 +01:00
f5ac13c847 js: Add mapping for UI_REDO based on KeyboardEvent.key 2022-10-24 23:19:56 +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
9698732d65 js: Add modern "key" values for Delete and arrow keys
Firefox has emitted "Delete", "ArrowDown" etc since 2015.
2022-10-24 22:22:33 +01:00
322a439d80 js: Use KeyboardEvent.keyCode and .char only as fallbacks
KeyboardEvent.keyCode is a platform-dependent mess.
KeyboardEvent.char is better-defined, but in my browser it's always
null.  KeyboardEvent.key is the right thing to use when we want to
know the semantics of a key.

This commit just re-organises the big "else if" chain in key() so
that all the tests on "key" come first.  That way at least if key and
keyCode disagree, we'll use the more trustworthy one.
2022-10-24 22:07:12 +01:00
c1059c07fb js: Remove braces from big else-if chain in keyboard handler
If there's ever a case where they're unnecessary noise, it's a long
chain of "else if"s guarding single assignment statements.
2022-10-24 21:57:53 +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
879a6922b0 js: Update permalinks and undo/redo buttons when loading
Without this, the "Undo" button ends up greyed even though it actually
works.  I'm not sure about whether updating the permalinks is necessary:
maybe we don't need that in new_game() either.
2022-10-15 18:23:15 +01:00