* by using the DOM File API to ask the user to select a file and
* permit us to see its contents.
*
- * - it ought to be possible to make the puzzle canvases resizable,
- * by superimposing some kind of draggable resize handle. Also I
- * quite like the idea of having a few buttons for standard sizes:
- * reset to default size, maximise to the browser window dimensions
- * (if we can find those out), and perhaps even go full-screen.
- *
* - 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
* that using whatever they normally use to print PDFs!)
*/
+#include <assert.h>
+#include <stdio.h>
#include <string.h>
+#include <stdarg.h>
#include "puzzles.h"
extern void js_error_box(const char *message);
extern void js_remove_type_dropdown(void);
extern void js_remove_solve_button(void);
-extern void js_add_preset(const char *name);
+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_get_date_64(unsigned *p);
}
/* ----------------------------------------------------------------------
- * Helper function to resize the canvas, and variables to remember its
- * size for other functions (e.g. trimming blitter rectangles).
+ * Helper functions to resize the canvas, and variables to remember
+ * its size for other functions (e.g. trimming blitter rectangles).
*/
static int canvas_w, canvas_h;
+
+/* Called when we resize as a result of changing puzzle settings */
static void resize(void)
{
int w, h;
canvas_h = h;
}
+/* Called from JS when the user uses the resize handle */
+void resize_puzzle(int w, int h)
+{
+ midend_size(me, &w, &h, TRUE);
+ if (canvas_w != w || canvas_h != h) {
+ js_canvas_set_size(w, h);
+ canvas_w = w;
+ canvas_h = h;
+ midend_force_redraw(me);
+ }
+}
+
+/* Called from JS when the user uses the restore button */
+void restore_puzzle_size(int w, int h)
+{
+ midend_reset_tilesize(me);
+ resize();
+ midend_force_redraw(me);
+}
+
/*
* HTML doesn't give us a default frontend colour of its own, so we
* just make up a lightish grey ourselves.
keyevent = MOD_NUM_KEYPAD | '7';
} else if (!strnullcmp(key, "PageUp") || 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) {
keyevent = keycode + (shift ? 0 : 32);
} else if (keycode >= 48 && keycode <= 57) {
keyevent = keycode;
+ } else if (keycode == 32) { /* space / CURSOR_SELECT2 */
+ keyevent = keycode;
}
if (keyevent >= 0) {
- if (shift && keyevent >= 0x100)
+ if (shift && (keyevent >= 0x100 && !IS_UI_FAKE_KEY(keyevent)))
keyevent |= MOD_SHFT;
- if (ctrl) {
+ if (ctrl && !IS_UI_FAKE_KEY(keyevent)) {
if (keyevent >= 0x100)
keyevent |= MOD_CTRL;
else
* Presets and game-configuration dialog support.
*/
static game_params **presets;
-static int custom_preset;
+static int npresets;
int have_presets_dropdown;
+void populate_js_preset_menu(int menuid, struct preset_menu *menu)
+{
+ int i;
+ for (i = 0; i < menu->n_entries; i++) {
+ struct preset_menu_entry *entry = &menu->entries[i];
+ if (entry->params) {
+ presets[entry->id] = entry->params;
+ js_add_preset(menuid, entry->title, entry->id);
+ } else {
+ int js_submenu = js_add_preset_submenu(menuid, entry->title);
+ populate_js_preset_menu(js_submenu, entry->submenu);
+ }
+ }
+}
+
void select_appropriate_preset(void)
{
if (have_presets_dropdown) {
int preset = midend_which_preset(me);
- js_select_preset(preset < 0 ? custom_preset : preset);
+ js_select_preset(preset < 0 ? -1 : preset);
}
}
case 2: /* game parameter dropdown changed */
{
int i = js_get_selected_preset();
- if (i == custom_preset) {
+ if (i < 0) {
/*
* The user selected 'Custom', so launch the config
* box.
* The user selected a preset, so just switch straight
* to that.
*/
+ assert(i < npresets);
midend_set_params(me, presets[i]);
midend_new_game(me);
resize();
midend_redraw(me);
update_undo_redo();
js_focus_canvas();
+ select_appropriate_preset();
}
}
break;
update_undo_redo();
break;
case 5: /* New Game */
- midend_process_key(me, 0, 0, 'n');
+ midend_process_key(me, 0, 0, UI_NEWGAME);
update_undo_redo();
js_focus_canvas();
break;
js_focus_canvas();
break;
case 7: /* Undo */
- midend_process_key(me, 0, 0, 'u');
+ midend_process_key(me, 0, 0, UI_UNDO);
update_undo_redo();
js_focus_canvas();
break;
case 8: /* Redo */
- midend_process_key(me, 0, 0, 'r');
+ midend_process_key(me, 0, 0, UI_REDO);
update_undo_redo();
js_focus_canvas();
break;
}
}
+/* ----------------------------------------------------------------------
+ * Called from JS to prepare a save-game file, and free one after it's
+ * been used.
+ */
+
+struct savefile_write_ctx {
+ char *buffer;
+ size_t pos;
+};
+
+static void savefile_write(void *vctx, void *buf, int len)
+{
+ struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)vctx;
+ if (ctx->buffer)
+ memcpy(ctx->buffer + ctx->pos, buf, len);
+ ctx->pos += len;
+}
+
+char *get_save_file(void)
+{
+ struct savefile_write_ctx ctx;
+ size_t size;
+
+ /* First pass, to count up the size */
+ ctx.buffer = NULL;
+ ctx.pos = 0;
+ midend_serialise(me, savefile_write, &ctx);
+ size = ctx.pos;
+
+ /* Second pass, to actually write out the data */
+ ctx.buffer = snewn(size, char);
+ ctx.pos = 0;
+ midend_serialise(me, savefile_write, &ctx);
+ assert(ctx.pos == size);
+
+ return ctx.buffer;
+}
+
+void free_save_file(char *buffer)
+{
+ sfree(buffer);
+}
+
+struct savefile_read_ctx {
+ const char *buffer;
+ int len_remaining;
+};
+
+static int 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;
+}
+
+void load_game(const char *buffer, int len)
+{
+ struct savefile_read_ctx ctx;
+ const char *err;
+
+ ctx.buffer = buffer;
+ ctx.len_remaining = len;
+ err = midend_deserialise(me, savefile_read, &ctx);
+
+ if (err) {
+ js_error_box(err);
+ } else {
+ select_appropriate_preset();
+ resize();
+ midend_redraw(me);
+ }
+}
+
/* ----------------------------------------------------------------------
* 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()
/*
* Set up the game-type dropdown with presets and/or the Custom
- * option. We remember the index of the Custom option (as
- * custom_preset) so that we can easily treat it specially when
- * it's selected.
+ * option.
*/
- custom_preset = midend_num_presets(me);
- if (custom_preset == 0) {
- /*
- * This puzzle doesn't have selectable game types at all.
- * Completely remove the drop-down list from the page.
- */
- js_remove_type_dropdown();
- have_presets_dropdown = FALSE;
- } else {
- int preset;
+ {
+ struct preset_menu *menu = midend_get_presets(me, &npresets);
+ presets = snewn(npresets, game_params *);
+ for (i = 0; i < npresets; i++)
+ presets[i] = NULL;
+
+ populate_js_preset_menu(0, menu);
- presets = snewn(custom_preset, game_params *);
- for (i = 0; i < custom_preset; i++) {
- char *name;
- midend_fetch_preset(me, i, &name, &presets[i]);
- js_add_preset(name);
- }
if (thegame.can_configure)
- js_add_preset(NULL); /* the 'Custom' entry in the dropdown */
+ js_add_preset(0, "Custom", -1);
have_presets_dropdown = TRUE;