From 09aa8de52cb962f1ceebfb1fd44f2c54a924fc5c Mon Sep 17 00:00:00 2001 From: Franklin Wei Date: Mon, 22 Jul 2024 21:43:25 -0400 Subject: puzzles: resync with upstream This brings the puzzles source in sync with Simon's branch, commit fd304c5 (from March 2024), with some added Rockbox-specific compatibility changes: https://www.franklinwei.com/git/puzzles/commit/?h=rockbox-devel&id=516830d9d76bdfe64fe5ccf2a9b59c33f5c7c078 There are quite a lot of backend changes, including a new "Mosaic" puzzle. In addition, some new frontend changes were necessary: - New "Preferences" menu to access the user preferences system. - Enabled spacebar input for several games. Change-Id: I94c7df674089c92f32d5f07025f6a1059068af1e --- apps/plugins/puzzles/src/emcc.c | 483 +++++++++++++++++++++++++++------------- 1 file changed, 327 insertions(+), 156 deletions(-) (limited to 'apps/plugins/puzzles/src/emcc.c') diff --git a/apps/plugins/puzzles/src/emcc.c b/apps/plugins/puzzles/src/emcc.c index 9a6c742482..6aa9c6b093 100644 --- a/apps/plugins/puzzles/src/emcc.c +++ b/apps/plugins/puzzles/src/emcc.c @@ -12,19 +12,8 @@ /* * Further thoughts on possible enhancements: * - * - I think it might be feasible to have these JS puzzles permit - * loading and saving games in disk files. Saving would be done by - * constructing a data: URI encapsulating the save file, and then - * telling the browser to visit that URI with the effect that it - * would naturally pop up a 'where would you like to save this' - * dialog box. Loading, more or less similarly, might be feasible - * by using the DOM File API to ask the user to select a file and - * permit us to see its contents. - * * - I should think about whether these webified puzzles can support - * touchscreen-based tablet browsers (assuming there are any that - * can cope with the reasonably modern JS and run it fast enough to - * be worthwhile). + * touchscreen-based tablet browsers. * * - think about making use of localStorage. It might be useful to * let the user save games into there as an alternative to disk @@ -32,11 +21,7 @@ * out of your browser to (e.g.) email to me as a bug report, but * for just resuming a game you were in the middle of, you'd * probably rather have a nice simple 'quick save' and 'quick load' - * button pair. Also, that might be a useful place to store - * preferences, if I ever get round to writing a preferences UI. - * - * - some CSS to make the button bar and configuration dialogs a - * little less ugly would probably not go amiss. + * button pair. * * - this is a downright silly idea, but it does occur to me that if * I were to write a PDF output driver for the Puzzles printing @@ -57,6 +42,8 @@ /* * Extern references to Javascript functions provided in emcclib.js. */ +extern void js_init_puzzle(void); +extern void js_post_init(void); extern void js_debug(const char *); extern void js_error_box(const char *message); extern void js_remove_type_dropdown(void); @@ -65,37 +52,39 @@ extern void js_add_preset(int menuid, const char *name, int value); extern int js_add_preset_submenu(int menuid, const char *name); extern int js_get_selected_preset(void); extern void js_select_preset(int n); +extern void js_default_colour(float *output); +extern void js_set_colour(int colour_number, const char *colour_string); extern void js_get_date_64(unsigned *p); extern void js_update_permalinks(const char *desc, const char *seed); extern void js_enable_undo_redo(bool undo, bool redo); -extern void js_activate_timer(); -extern void js_deactivate_timer(); +extern void js_update_key_labels(const char *lsk, const char *csk); +extern void js_activate_timer(void); +extern void js_deactivate_timer(void); extern void js_canvas_start_draw(void); extern void js_canvas_draw_update(int x, int y, int w, int h); extern void js_canvas_end_draw(void); -extern void js_canvas_draw_rect(int x, int y, int w, int h, - const char *colour); +extern void js_canvas_draw_rect(int x, int y, int w, int h, int colour); extern void js_canvas_clip_rect(int x, int y, int w, int h); extern void js_canvas_unclip(void); extern void js_canvas_draw_line(float x1, float y1, float x2, float y2, - int width, const char *colour); -extern void js_canvas_draw_poly(int *points, int npoints, - const char *fillcolour, - const char *outlinecolour); + int width, int colour); +extern void js_canvas_draw_poly(const int *points, int npoints, + int fillcolour, int outlinecolour); extern void js_canvas_draw_circle(int x, int y, int r, - const char *fillcolour, - const char *outlinecolour); -extern int js_canvas_find_font_midpoint(int height, const char *fontptr); + int fillcolour, int outlinecolour); +extern int js_canvas_find_font_midpoint(int height, bool monospaced); extern void js_canvas_draw_text(int x, int y, int halign, - const char *colptr, const char *fontptr, - const char *text); + int colour, int height, + bool monospaced, const char *text); extern int js_canvas_new_blitter(int w, int h); extern void js_canvas_free_blitter(int id); extern void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h); extern void js_canvas_copy_from_blitter(int id, int x, int y, int w, int h); -extern void js_canvas_make_statusbar(void); +extern void js_canvas_remove_statusbar(void); extern void js_canvas_set_statusbar(const char *text); +extern bool js_canvas_get_preferred_size(int *wp, int *hp); extern void js_canvas_set_size(int w, int h); +extern double js_get_device_pixel_ratio(void); extern void js_dialog_init(const char *title); extern void js_dialog_string(int i, const char *title, const char *initvalue); @@ -106,6 +95,38 @@ extern void js_dialog_launch(void); extern void js_dialog_cleanup(void); extern void js_focus_canvas(void); +extern bool js_savefile_read(void *buf, int len); + +extern void js_save_prefs(const char *); +extern void js_load_prefs(midend *); + +/* + * These functions are called from JavaScript, so their prototypes + * need to be kept in sync with emccpre.js. + */ +bool mouseup(int x, int y, int button); +bool mousedown(int x, int y, int button); +bool mousemove(int x, int y, int buttons); +bool key(int keycode, const char *key, const char *chr, int location, + bool shift, bool ctrl); +void timer_callback(double tplus); +void command(int n); +char *get_text_format(void); +void free_save_file(char *buffer); +char *get_save_file(void); +void free_save_file(char *buffer); +void load_game(void); +void dlg_return_sval(int index, const char *val); +void dlg_return_ival(int index, int val); +void resize_puzzle(int w, int h); +void restore_puzzle_size(int w, int h); +void rescale_puzzle(void); + +/* + * Internal forward references. + */ +static void save_prefs(midend *me); + /* * Call JS to get the date, and use that to initialise our random * number generator to invent the first game seed. @@ -136,6 +157,7 @@ void fatal(const char *fmt, ...) js_error_box(buf); } +#ifdef DEBUGGING void debug_printf(const char *fmt, ...) { char buf[512]; @@ -145,33 +167,28 @@ void debug_printf(const char *fmt, ...) va_end(ap); js_debug(buf); } +#endif /* * Helper function that makes it easy to test strings that might be * NULL. */ -int strnullcmp(const char *a, const char *b) +static int strnullcmp(const char *a, const char *b) { if (a == NULL || b == NULL) return a != NULL ? +1 : b != NULL ? -1 : 0; return strcmp(a, b); } -/* - * HTMLish names for the colours allocated by the puzzle. - */ -char **colour_strings; -int ncolours; - /* * The global midend object. */ -midend *me; +static midend *me; /* ---------------------------------------------------------------------- * Timing functions. */ -bool timer_active = false; +static bool timer_active = false; void deactivate_timer(frontend *fe) { js_deactivate_timer(); @@ -196,21 +213,33 @@ void timer_callback(double tplus) */ static int canvas_w, canvas_h; -/* Called when we resize as a result of changing puzzle settings */ +/* + * Called when we resize as a result of changing puzzle settings + * or device pixel ratio. + */ static void resize(void) { int w, h; + bool user; w = h = INT_MAX; - midend_size(me, &w, &h, false); + user = js_canvas_get_preferred_size(&w, &h); + midend_size(me, &w, &h, user, js_get_device_pixel_ratio()); js_canvas_set_size(w, h); canvas_w = w; canvas_h = h; } +/* Called from JS when the device pixel ratio changes */ +void rescale_puzzle(void) +{ + resize(); + midend_force_redraw(me); +} + /* Called from JS when the user uses the resize handle */ void resize_puzzle(int w, int h) { - midend_size(me, &w, &h, true); + midend_size(me, &w, &h, true, js_get_device_pixel_ratio()); if (canvas_w != w || canvas_h != h) { js_canvas_set_size(w, h); canvas_w = w; @@ -228,12 +257,13 @@ void restore_puzzle_size(int w, int h) } /* - * HTML doesn't give us a default frontend colour of its own, so we - * just make up a lightish grey ourselves. + * Try to extract a background colour from the canvas's CSS. In case + * it doesn't have a usable one, make up a lightish grey ourselves. */ void frontend_default_colour(frontend *fe, float *output) { output[0] = output[1] = output[2] = 0.9F; + js_default_colour(output); } /* @@ -241,60 +271,85 @@ void frontend_default_colour(frontend *fe, float *output) * and redo buttons get properly enabled and disabled after every move * or undo or new-game event. */ -static void update_undo_redo(void) +static void post_move(void) { js_enable_undo_redo(midend_can_undo(me), midend_can_redo(me)); + js_update_key_labels(midend_current_key_label(me, CURSOR_SELECT2), + midend_current_key_label(me, CURSOR_SELECT)); } /* * Mouse event handlers called from JS. */ -void mousedown(int x, int y, int button) +bool mousedown(int x, int y, int button) { + bool handled; + button = (button == 0 ? LEFT_BUTTON : button == 1 ? MIDDLE_BUTTON : RIGHT_BUTTON); - midend_process_key(me, x, y, button); - update_undo_redo(); + handled = midend_process_key(me, x, y, button) != PKR_UNUSED; + post_move(); + return handled; } -void mouseup(int x, int y, int button) +bool mouseup(int x, int y, int button) { + bool handled; + button = (button == 0 ? LEFT_RELEASE : button == 1 ? MIDDLE_RELEASE : RIGHT_RELEASE); - midend_process_key(me, x, y, button); - update_undo_redo(); + handled = midend_process_key(me, x, y, button) != PKR_UNUSED; + post_move(); + return handled; } -void mousemove(int x, int y, int buttons) +bool mousemove(int x, int y, int buttons) { int button = (buttons & 2 ? MIDDLE_DRAG : buttons & 4 ? RIGHT_DRAG : LEFT_DRAG); - midend_process_key(me, x, y, button); - update_undo_redo(); + bool handled; + + handled = midend_process_key(me, x, y, button) != PKR_UNUSED; + post_move(); + return handled; } /* - * Keyboard handler called from JS. + * Keyboard handler called from JS. Returns true if the key was + * handled and hence the keydown event should be cancelled. */ -void key(int keycode, int charcode, const char *key, const char *chr, +bool key(int keycode, const char *key, const char *chr, int location, bool shift, bool ctrl) { + /* Key location constants from JavaScript. */ + #define DOM_KEY_LOCATION_STANDARD 0 + #define DOM_KEY_LOCATION_LEFT 1 + #define DOM_KEY_LOCATION_RIGHT 2 + #define DOM_KEY_LOCATION_NUMPAD 3 int keyevent = -1; + int process_key_result; - if (!strnullcmp(key, "Backspace") || !strnullcmp(key, "Del") || - keycode == 8 || keycode == 46) { + if (!strnullcmp(key, "Backspace") || !strnullcmp(key, "Delete") || + !strnullcmp(key, "Del")) keyevent = 127; /* Backspace / Delete */ - } else if (!strnullcmp(key, "Enter") || keycode == 13) { + else if (!strnullcmp(key, "Enter")) keyevent = 13; /* return */ - } else if (!strnullcmp(key, "Left") || keycode == 37) { + else if (!strnullcmp(key, "Spacebar")) + keyevent = ' '; + else if (!strnullcmp(key, "Escape")) + keyevent = 27; + else if (!strnullcmp(key, "ArrowLeft") || !strnullcmp(key, "Left")) keyevent = CURSOR_LEFT; - } else if (!strnullcmp(key, "Up") || keycode == 38) { + else if (!strnullcmp(key, "ArrowUp") || !strnullcmp(key, "Up")) keyevent = CURSOR_UP; - } else if (!strnullcmp(key, "Right") || keycode == 39) { + else if (!strnullcmp(key, "ArrowRight") || !strnullcmp(key, "Right")) keyevent = CURSOR_RIGHT; - } else if (!strnullcmp(key, "Down") || keycode == 40) { + else if (!strnullcmp(key, "ArrowDown") || !strnullcmp(key, "Down")) keyevent = CURSOR_DOWN; - } else if (!strnullcmp(key, "End") || keycode == 35) { + else if (!strnullcmp(key, "SoftLeft")) + /* Left soft key on KaiOS. */ + keyevent = CURSOR_SELECT2; + else if (!strnullcmp(key, "End")) /* * We interpret Home, End, PgUp and PgDn as numeric keypad * controls regardless of whether they're the ones on the @@ -304,40 +359,73 @@ void key(int keycode, int charcode, const char *key, const char *chr, * puzzles like Cube and Inertia. */ keyevent = MOD_NUM_KEYPAD | '1'; - } else if (!strnullcmp(key, "PageDown") || keycode==34) { + else if (!strnullcmp(key, "PageDown")) keyevent = MOD_NUM_KEYPAD | '3'; - } else if (!strnullcmp(key, "Home") || keycode==36) { + else if (!strnullcmp(key, "Home")) keyevent = MOD_NUM_KEYPAD | '7'; - } else if (!strnullcmp(key, "PageUp") || keycode==33) { + else if (!strnullcmp(key, "PageUp")) keyevent = MOD_NUM_KEYPAD | '9'; - } else if (shift && ctrl && (keycode & 0x1F) == 26) { + else if (shift && ctrl && (!strnullcmp(key, "Z") || !strnullcmp(key, "z"))) keyevent = UI_REDO; - } else if (chr && chr[0] && !chr[1]) { + else if (key && (unsigned char)key[0] < 0x80 && key[1] == '\0') + /* Key generating a single ASCII character. */ + keyevent = key[0]; + /* + * In modern browsers (since about 2017), all keys that Puzzles + * cares about should be matched by one of the clauses above. The + * code below that checks keycode and chr should be relavent only + * in older browsers. + */ + else if (keycode == 8 || keycode == 46) + keyevent = 127; /* Backspace / Delete */ + else if (keycode == 13) + keyevent = 13; /* return */ + else if (keycode == 37) + keyevent = CURSOR_LEFT; + else if (keycode == 38) + keyevent = CURSOR_UP; + else if (keycode == 39) + keyevent = CURSOR_RIGHT; + else if (keycode == 40) + keyevent = CURSOR_DOWN; + else if (keycode == 35) + keyevent = MOD_NUM_KEYPAD | '1'; + else if (keycode == 34) + keyevent = MOD_NUM_KEYPAD | '3'; + else if (keycode == 36) + keyevent = MOD_NUM_KEYPAD | '7'; + else if (keycode == 33) + keyevent = MOD_NUM_KEYPAD | '9'; + else if (shift && ctrl && (keycode & 0x1F) == 26) + keyevent = UI_REDO; + else if (chr && chr[0] && !chr[1]) keyevent = chr[0] & 0xFF; - } else if (keycode >= 96 && keycode < 106) { + else if (keycode >= 96 && keycode < 106) keyevent = MOD_NUM_KEYPAD | ('0' + keycode - 96); - } else if (keycode >= 65 && keycode <= 90) { + else if (keycode >= 65 && keycode <= 90) keyevent = keycode + (shift ? 0 : 32); - } else if (keycode >= 48 && keycode <= 57) { + else if (keycode >= 48 && keycode <= 57) keyevent = keycode; - } else if (keycode == 32) { /* space / CURSOR_SELECT2 */ + else if (keycode == 32) /* space / CURSOR_SELECT2 */ keyevent = keycode; - } if (keyevent >= 0) { - if (shift && (keyevent >= 0x100 && !IS_UI_FAKE_KEY(keyevent))) - keyevent |= MOD_SHFT; - - if (ctrl && !IS_UI_FAKE_KEY(keyevent)) { - if (keyevent >= 0x100) - keyevent |= MOD_CTRL; - else - keyevent &= 0x1F; - } + if (shift) keyevent |= MOD_SHFT; + if (ctrl) keyevent |= MOD_CTRL; + if (location == DOM_KEY_LOCATION_NUMPAD) keyevent |= MOD_NUM_KEYPAD; - midend_process_key(me, 0, 0, keyevent); - update_undo_redo(); + process_key_result = midend_process_key(me, 0, 0, keyevent); + post_move(); + /* + * Treat Backspace specially because that's expected on KaiOS. + * https://developer.kaiostech.com/docs/design-guide/key + */ + if (process_key_result == PKR_NO_EFFECT && + !strnullcmp(key, "Backspace")) + return false; + return process_key_result != PKR_UNUSED; } + return false; /* Event not handled, because we don't even recognise it. */ } /* @@ -387,14 +475,10 @@ static void js_draw_text(void *handle, int x, int y, int fonttype, int fontsize, int align, int colour, const char *text) { - char fontstyle[80]; int halign; - sprintf(fontstyle, "%dpx %s", fontsize, - fonttype == FONT_FIXED ? "monospace" : "sans-serif"); - if (align & ALIGN_VCENTRE) - y += js_canvas_find_font_midpoint(fontsize, fontstyle); + y += js_canvas_find_font_midpoint(fontsize, fonttype == FONT_FIXED); if (align & ALIGN_HCENTRE) halign = 1; @@ -403,41 +487,38 @@ static void js_draw_text(void *handle, int x, int y, int fonttype, else halign = 0; - js_canvas_draw_text(x, y, halign, colour_strings[colour], fontstyle, text); + js_canvas_draw_text(x, y, halign, colour, + fontsize, fonttype == FONT_FIXED, text); } static void js_draw_rect(void *handle, int x, int y, int w, int h, int colour) { - js_canvas_draw_rect(x, y, w, h, colour_strings[colour]); + js_canvas_draw_rect(x, y, w, h, colour); } static void js_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour) { - js_canvas_draw_line(x1, y1, x2, y2, 1, colour_strings[colour]); + js_canvas_draw_line(x1, y1, x2, y2, 1, colour); } static void js_draw_thick_line(void *handle, float thickness, float x1, float y1, float x2, float y2, int colour) { - js_canvas_draw_line(x1, y1, x2, y2, thickness, colour_strings[colour]); + js_canvas_draw_line(x1, y1, x2, y2, thickness, colour); } -static void js_draw_poly(void *handle, int *coords, int npoints, +static void js_draw_poly(void *handle, const int *coords, int npoints, int fillcolour, int outlinecolour) { - js_canvas_draw_poly(coords, npoints, - fillcolour >= 0 ? colour_strings[fillcolour] : NULL, - colour_strings[outlinecolour]); + js_canvas_draw_poly(coords, npoints, fillcolour, outlinecolour); } static void js_draw_circle(void *handle, int cx, int cy, int radius, int fillcolour, int outlinecolour) { - js_canvas_draw_circle(cx, cy, radius, - fillcolour >= 0 ? colour_strings[fillcolour] : NULL, - colour_strings[outlinecolour]); + js_canvas_draw_circle(cx, cy, radius, fillcolour, outlinecolour); } struct blitter { @@ -527,7 +608,7 @@ static char *js_text_fallback(void *handle, const char *const *strings, return dupstr(strings[0]); /* Emscripten has no trouble with UTF-8 */ } -const struct drawing_api js_drawing = { +static const struct drawing_api js_drawing = { js_draw_text, js_draw_rect, js_draw_line, @@ -554,9 +635,9 @@ const struct drawing_api js_drawing = { */ static game_params **presets; static int npresets; -bool have_presets_dropdown; +static bool have_presets_dropdown; -void populate_js_preset_menu(int menuid, struct preset_menu *menu) +static void populate_js_preset_menu(int menuid, struct preset_menu *menu) { int i; for (i = 0; i < menu->n_entries; i++) { @@ -571,7 +652,7 @@ void populate_js_preset_menu(int menuid, struct preset_menu *menu) } } -void select_appropriate_preset(void) +static void select_appropriate_preset(void) { if (have_presets_dropdown) { int preset = midend_which_preset(me); @@ -665,10 +746,20 @@ static void cfg_end(bool use_results) * open for the user to adjust them and try again. */ js_error_box(err); + } else if (cfg_which == CFG_PREFS) { + /* + * Acceptable settings for user preferences: enact them + * without blowing away the current game. + */ + resize(); + midend_redraw(me); + free_cfg(cfg); + js_dialog_cleanup(); + save_prefs(me); } else { /* - * New settings are fine; start a new game and close the - * dialog. + * Acceptable settings for the remaining configuration + * types: start a new game and close the dialog. */ select_appropriate_preset(); midend_new_game(me); @@ -686,10 +777,7 @@ static void cfg_end(bool use_results) * select Custom from the list, but change your mind and hit * Esc. The Custom option will now still be selected in the * list, whereas obviously it should show the preset you still - * _actually_ have selected. Worse still, it'll be the visible - * rather than invisible Custom option - see the comment in - * js_add_preset in emcclib.js - so you won't even be able to - * select Custom without a faffy workaround.) + * _actually_ have selected.) */ select_appropriate_preset(); @@ -731,7 +819,7 @@ void command(int n) midend_new_game(me); resize(); midend_redraw(me); - update_undo_redo(); + post_move(); js_focus_canvas(); select_appropriate_preset(); } @@ -739,30 +827,30 @@ void command(int n) break; case 3: /* OK clicked in a config box */ cfg_end(true); - update_undo_redo(); + post_move(); break; case 4: /* Cancel clicked in a config box */ cfg_end(false); - update_undo_redo(); + post_move(); break; case 5: /* New Game */ midend_process_key(me, 0, 0, UI_NEWGAME); - update_undo_redo(); + post_move(); js_focus_canvas(); break; case 6: /* Restart */ midend_restart_game(me); - update_undo_redo(); + post_move(); js_focus_canvas(); break; case 7: /* Undo */ midend_process_key(me, 0, 0, UI_UNDO); - update_undo_redo(); + post_move(); js_focus_canvas(); break; case 8: /* Redo */ midend_process_key(me, 0, 0, UI_REDO); - update_undo_redo(); + post_move(); js_focus_canvas(); break; case 9: /* Solve */ @@ -771,12 +859,25 @@ void command(int n) if (msg) js_error_box(msg); } - update_undo_redo(); + post_move(); js_focus_canvas(); break; + case 10: /* user preferences */ + cfg_start(CFG_PREFS); + break; } } +char *get_text_format(void) +{ + return midend_text_format(me); +} + +void free_text_format(char *buffer) +{ + sfree(buffer); +} + /* ---------------------------------------------------------------------- * Called from JS to prepare a save-game file, and free one after it's * been used. @@ -824,30 +925,20 @@ void free_save_file(char *buffer) sfree(buffer); } -struct savefile_read_ctx { - const char *buffer; - int len_remaining; -}; - static bool savefile_read(void *vctx, void *buf, int len) { - struct savefile_read_ctx *ctx = (struct savefile_read_ctx *)vctx; - if (ctx->len_remaining < len) - return false; - memcpy(buf, ctx->buffer, len); - ctx->len_remaining -= len; - ctx->buffer += len; - return true; + return js_savefile_read(buf, len); } -void load_game(const char *buffer, int len) +void load_game(void) { - struct savefile_read_ctx ctx; const char *err; - ctx.buffer = buffer; - ctx.len_remaining = len; - err = midend_deserialise(me, savefile_read, &ctx); + /* + * savefile_read_callback in JavaScript was set up by our caller + * as a closure that knows what file we're loading. + */ + err = midend_deserialise(me, savefile_read, NULL); if (err) { js_error_box(err); @@ -855,9 +946,69 @@ void load_game(const char *buffer, int len) select_appropriate_preset(); resize(); midend_redraw(me); + update_permalinks(); + post_move(); } } +/* ---------------------------------------------------------------------- + * Functions to load and save preferences, calling out to JS to access + * the appropriate localStorage slot. + */ + +static void save_prefs(midend *me) +{ + struct savefile_write_ctx ctx; + size_t size; + + /* First pass, to count up the size */ + ctx.buffer = NULL; + ctx.pos = 0; + midend_save_prefs(me, savefile_write, &ctx); + size = ctx.pos; + + /* Second pass, to actually write out the data. As with + * get_save_file, we append a terminating \0. */ + ctx.buffer = snewn(size+1, char); + ctx.pos = 0; + midend_save_prefs(me, savefile_write, &ctx); + assert(ctx.pos == size); + ctx.buffer[ctx.pos] = '\0'; + + js_save_prefs(ctx.buffer); + + sfree(ctx.buffer); +} + +struct prefs_read_ctx { + const char *buffer; + size_t pos, len; +}; + +static bool prefs_read(void *vctx, void *buf, int len) +{ + struct prefs_read_ctx *ctx = (struct prefs_read_ctx *)vctx; + + if (len < 0) + return false; + if (ctx->len - ctx->pos < len) + return false; + memcpy(buf, ctx->buffer + ctx->pos, len); + ctx->pos += len; + return true; +} + +void prefs_load_callback(midend *me, const char *prefs) +{ + struct prefs_read_ctx ctx; + + ctx.buffer = prefs; + ctx.len = strlen(prefs); + ctx.pos = 0; + + midend_load_prefs(me, prefs_read, &ctx); +} + /* ---------------------------------------------------------------------- * Setup function called at page load time. It's called main() because * that's the most convenient thing in Emscripten, but it's not main() @@ -870,12 +1021,18 @@ int main(int argc, char **argv) { const char *param_err; float *colours; - int i; + int i, ncolours; + + /* + * Initialise JavaScript event handlers. + */ + js_init_puzzle(); /* * Instantiate a midend. */ me = midend_new(NULL, &thegame, &js_drawing, NULL); + js_load_prefs(me); /* * Chuck in the HTML fragment ID if we have one (trimming the @@ -896,10 +1053,10 @@ int main(int argc, char **argv) resize(); /* - * Create a status bar, if needed. + * Remove the status bar, if not needed. */ - if (midend_wants_statusbar(me)) - js_canvas_make_statusbar(); + if (!midend_wants_statusbar(me)) + js_canvas_remove_statusbar(); /* * Set up the game-type dropdown with presets and/or the Custom @@ -907,23 +1064,33 @@ int main(int argc, char **argv) */ { struct preset_menu *menu = midend_get_presets(me, &npresets); + bool may_configure = false; presets = snewn(npresets, game_params *); for (i = 0; i < npresets; i++) presets[i] = NULL; populate_js_preset_menu(0, menu); - if (thegame.can_configure) - js_add_preset(0, "Custom", -1); - - have_presets_dropdown = true; - /* - * Now ensure the appropriate element of the presets menu - * starts off selected, in case it isn't the first one in the - * list (e.g. Slant). + * Crude hack to allow the "Custom..." item to be hidden on + * KaiOS, where dialogs don't yet work. */ - select_appropriate_preset(); + if (thegame.can_configure && getenv_bool("PUZZLES_ALLOW_CUSTOM", true)) + may_configure = true; + if (may_configure) + js_add_preset(0, "Custom...", -1); + + have_presets_dropdown = npresets > 1 || may_configure; + + if (have_presets_dropdown) + /* + * Now ensure the appropriate element of the presets menu + * starts off selected, in case it isn't the first one in the + * list (e.g. Slant). + */ + select_appropriate_preset(); + else + js_remove_type_dropdown(); } /* @@ -937,14 +1104,13 @@ int main(int argc, char **argv) * hex ID strings. */ colours = midend_colours(me, &ncolours); - colour_strings = snewn(ncolours, char *); for (i = 0; i < ncolours; i++) { char col[40]; sprintf(col, "#%02x%02x%02x", - (unsigned)(0.5 + 255 * colours[i*3+0]), - (unsigned)(0.5 + 255 * colours[i*3+1]), - (unsigned)(0.5 + 255 * colours[i*3+2])); - colour_strings[i] = dupstr(col); + (unsigned)(0.5F + 255 * colours[i*3+0]), + (unsigned)(0.5F + 255 * colours[i*3+1]), + (unsigned)(0.5F + 255 * colours[i*3+2])); + js_set_colour(i, col); } /* @@ -960,7 +1126,7 @@ int main(int argc, char **argv) */ midend_redraw(me); update_permalinks(); - update_undo_redo(); + post_move(); /* * If we were given an erroneous game ID in argv[1], now's the @@ -971,6 +1137,11 @@ int main(int argc, char **argv) if (param_err) js_error_box(param_err); + /* + * Reveal the puzzle! + */ + js_post_init(); + /* * Done. Return to JS, and await callbacks! */ -- cgit v1.2.3