summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/emcc.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/emcc.c')
-rw-r--r--apps/plugins/puzzles/src/emcc.c483
1 files changed, 327 insertions, 156 deletions
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 @@
12/* 12/*
13 * Further thoughts on possible enhancements: 13 * Further thoughts on possible enhancements:
14 * 14 *
15 * - I think it might be feasible to have these JS puzzles permit
16 * loading and saving games in disk files. Saving would be done by
17 * constructing a data: URI encapsulating the save file, and then
18 * telling the browser to visit that URI with the effect that it
19 * would naturally pop up a 'where would you like to save this'
20 * dialog box. Loading, more or less similarly, might be feasible
21 * by using the DOM File API to ask the user to select a file and
22 * permit us to see its contents.
23 *
24 * - I should think about whether these webified puzzles can support 15 * - I should think about whether these webified puzzles can support
25 * touchscreen-based tablet browsers (assuming there are any that 16 * touchscreen-based tablet browsers.
26 * can cope with the reasonably modern JS and run it fast enough to
27 * be worthwhile).
28 * 17 *
29 * - think about making use of localStorage. It might be useful to 18 * - think about making use of localStorage. It might be useful to
30 * let the user save games into there as an alternative to disk 19 * let the user save games into there as an alternative to disk
@@ -32,11 +21,7 @@
32 * out of your browser to (e.g.) email to me as a bug report, but 21 * out of your browser to (e.g.) email to me as a bug report, but
33 * for just resuming a game you were in the middle of, you'd 22 * for just resuming a game you were in the middle of, you'd
34 * probably rather have a nice simple 'quick save' and 'quick load' 23 * probably rather have a nice simple 'quick save' and 'quick load'
35 * button pair. Also, that might be a useful place to store 24 * button pair.
36 * preferences, if I ever get round to writing a preferences UI.
37 *
38 * - some CSS to make the button bar and configuration dialogs a
39 * little less ugly would probably not go amiss.
40 * 25 *
41 * - this is a downright silly idea, but it does occur to me that if 26 * - this is a downright silly idea, but it does occur to me that if
42 * I were to write a PDF output driver for the Puzzles printing 27 * I were to write a PDF output driver for the Puzzles printing
@@ -57,6 +42,8 @@
57/* 42/*
58 * Extern references to Javascript functions provided in emcclib.js. 43 * Extern references to Javascript functions provided in emcclib.js.
59 */ 44 */
45extern void js_init_puzzle(void);
46extern void js_post_init(void);
60extern void js_debug(const char *); 47extern void js_debug(const char *);
61extern void js_error_box(const char *message); 48extern void js_error_box(const char *message);
62extern void js_remove_type_dropdown(void); 49extern void js_remove_type_dropdown(void);
@@ -65,37 +52,39 @@ extern void js_add_preset(int menuid, const char *name, int value);
65extern int js_add_preset_submenu(int menuid, const char *name); 52extern int js_add_preset_submenu(int menuid, const char *name);
66extern int js_get_selected_preset(void); 53extern int js_get_selected_preset(void);
67extern void js_select_preset(int n); 54extern void js_select_preset(int n);
55extern void js_default_colour(float *output);
56extern void js_set_colour(int colour_number, const char *colour_string);
68extern void js_get_date_64(unsigned *p); 57extern void js_get_date_64(unsigned *p);
69extern void js_update_permalinks(const char *desc, const char *seed); 58extern void js_update_permalinks(const char *desc, const char *seed);
70extern void js_enable_undo_redo(bool undo, bool redo); 59extern void js_enable_undo_redo(bool undo, bool redo);
71extern void js_activate_timer(); 60extern void js_update_key_labels(const char *lsk, const char *csk);
72extern void js_deactivate_timer(); 61extern void js_activate_timer(void);
62extern void js_deactivate_timer(void);
73extern void js_canvas_start_draw(void); 63extern void js_canvas_start_draw(void);
74extern void js_canvas_draw_update(int x, int y, int w, int h); 64extern void js_canvas_draw_update(int x, int y, int w, int h);
75extern void js_canvas_end_draw(void); 65extern void js_canvas_end_draw(void);
76extern void js_canvas_draw_rect(int x, int y, int w, int h, 66extern void js_canvas_draw_rect(int x, int y, int w, int h, int colour);
77 const char *colour);
78extern void js_canvas_clip_rect(int x, int y, int w, int h); 67extern void js_canvas_clip_rect(int x, int y, int w, int h);
79extern void js_canvas_unclip(void); 68extern void js_canvas_unclip(void);
80extern void js_canvas_draw_line(float x1, float y1, float x2, float y2, 69extern void js_canvas_draw_line(float x1, float y1, float x2, float y2,
81 int width, const char *colour); 70 int width, int colour);
82extern void js_canvas_draw_poly(int *points, int npoints, 71extern void js_canvas_draw_poly(const int *points, int npoints,
83 const char *fillcolour, 72 int fillcolour, int outlinecolour);
84 const char *outlinecolour);
85extern void js_canvas_draw_circle(int x, int y, int r, 73extern void js_canvas_draw_circle(int x, int y, int r,
86 const char *fillcolour, 74 int fillcolour, int outlinecolour);
87 const char *outlinecolour); 75extern int js_canvas_find_font_midpoint(int height, bool monospaced);
88extern int js_canvas_find_font_midpoint(int height, const char *fontptr);
89extern void js_canvas_draw_text(int x, int y, int halign, 76extern void js_canvas_draw_text(int x, int y, int halign,
90 const char *colptr, const char *fontptr, 77 int colour, int height,
91 const char *text); 78 bool monospaced, const char *text);
92extern int js_canvas_new_blitter(int w, int h); 79extern int js_canvas_new_blitter(int w, int h);
93extern void js_canvas_free_blitter(int id); 80extern void js_canvas_free_blitter(int id);
94extern void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h); 81extern void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h);
95extern void js_canvas_copy_from_blitter(int id, int x, int y, int w, int h); 82extern void js_canvas_copy_from_blitter(int id, int x, int y, int w, int h);
96extern void js_canvas_make_statusbar(void); 83extern void js_canvas_remove_statusbar(void);
97extern void js_canvas_set_statusbar(const char *text); 84extern void js_canvas_set_statusbar(const char *text);
85extern bool js_canvas_get_preferred_size(int *wp, int *hp);
98extern void js_canvas_set_size(int w, int h); 86extern void js_canvas_set_size(int w, int h);
87extern double js_get_device_pixel_ratio(void);
99 88
100extern void js_dialog_init(const char *title); 89extern void js_dialog_init(const char *title);
101extern void js_dialog_string(int i, const char *title, const char *initvalue); 90extern void js_dialog_string(int i, const char *title, const char *initvalue);
@@ -106,6 +95,38 @@ extern void js_dialog_launch(void);
106extern void js_dialog_cleanup(void); 95extern void js_dialog_cleanup(void);
107extern void js_focus_canvas(void); 96extern void js_focus_canvas(void);
108 97
98extern bool js_savefile_read(void *buf, int len);
99
100extern void js_save_prefs(const char *);
101extern void js_load_prefs(midend *);
102
103/*
104 * These functions are called from JavaScript, so their prototypes
105 * need to be kept in sync with emccpre.js.
106 */
107bool mouseup(int x, int y, int button);
108bool mousedown(int x, int y, int button);
109bool mousemove(int x, int y, int buttons);
110bool key(int keycode, const char *key, const char *chr, int location,
111 bool shift, bool ctrl);
112void timer_callback(double tplus);
113void command(int n);
114char *get_text_format(void);
115void free_save_file(char *buffer);
116char *get_save_file(void);
117void free_save_file(char *buffer);
118void load_game(void);
119void dlg_return_sval(int index, const char *val);
120void dlg_return_ival(int index, int val);
121void resize_puzzle(int w, int h);
122void restore_puzzle_size(int w, int h);
123void rescale_puzzle(void);
124
125/*
126 * Internal forward references.
127 */
128static void save_prefs(midend *me);
129
109/* 130/*
110 * Call JS to get the date, and use that to initialise our random 131 * Call JS to get the date, and use that to initialise our random
111 * number generator to invent the first game seed. 132 * number generator to invent the first game seed.
@@ -136,6 +157,7 @@ void fatal(const char *fmt, ...)
136 js_error_box(buf); 157 js_error_box(buf);
137} 158}
138 159
160#ifdef DEBUGGING
139void debug_printf(const char *fmt, ...) 161void debug_printf(const char *fmt, ...)
140{ 162{
141 char buf[512]; 163 char buf[512];
@@ -145,12 +167,13 @@ void debug_printf(const char *fmt, ...)
145 va_end(ap); 167 va_end(ap);
146 js_debug(buf); 168 js_debug(buf);
147} 169}
170#endif
148 171
149/* 172/*
150 * Helper function that makes it easy to test strings that might be 173 * Helper function that makes it easy to test strings that might be
151 * NULL. 174 * NULL.
152 */ 175 */
153int strnullcmp(const char *a, const char *b) 176static int strnullcmp(const char *a, const char *b)
154{ 177{
155 if (a == NULL || b == NULL) 178 if (a == NULL || b == NULL)
156 return a != NULL ? +1 : b != NULL ? -1 : 0; 179 return a != NULL ? +1 : b != NULL ? -1 : 0;
@@ -158,20 +181,14 @@ int strnullcmp(const char *a, const char *b)
158} 181}
159 182
160/* 183/*
161 * HTMLish names for the colours allocated by the puzzle.
162 */
163char **colour_strings;
164int ncolours;
165
166/*
167 * The global midend object. 184 * The global midend object.
168 */ 185 */
169midend *me; 186static midend *me;
170 187
171/* ---------------------------------------------------------------------- 188/* ----------------------------------------------------------------------
172 * Timing functions. 189 * Timing functions.
173 */ 190 */
174bool timer_active = false; 191static bool timer_active = false;
175void deactivate_timer(frontend *fe) 192void deactivate_timer(frontend *fe)
176{ 193{
177 js_deactivate_timer(); 194 js_deactivate_timer();
@@ -196,21 +213,33 @@ void timer_callback(double tplus)
196 */ 213 */
197static int canvas_w, canvas_h; 214static int canvas_w, canvas_h;
198 215
199/* Called when we resize as a result of changing puzzle settings */ 216/*
217 * Called when we resize as a result of changing puzzle settings
218 * or device pixel ratio.
219 */
200static void resize(void) 220static void resize(void)
201{ 221{
202 int w, h; 222 int w, h;
223 bool user;
203 w = h = INT_MAX; 224 w = h = INT_MAX;
204 midend_size(me, &w, &h, false); 225 user = js_canvas_get_preferred_size(&w, &h);
226 midend_size(me, &w, &h, user, js_get_device_pixel_ratio());
205 js_canvas_set_size(w, h); 227 js_canvas_set_size(w, h);
206 canvas_w = w; 228 canvas_w = w;
207 canvas_h = h; 229 canvas_h = h;
208} 230}
209 231
232/* Called from JS when the device pixel ratio changes */
233void rescale_puzzle(void)
234{
235 resize();
236 midend_force_redraw(me);
237}
238
210/* Called from JS when the user uses the resize handle */ 239/* Called from JS when the user uses the resize handle */
211void resize_puzzle(int w, int h) 240void resize_puzzle(int w, int h)
212{ 241{
213 midend_size(me, &w, &h, true); 242 midend_size(me, &w, &h, true, js_get_device_pixel_ratio());
214 if (canvas_w != w || canvas_h != h) { 243 if (canvas_w != w || canvas_h != h) {
215 js_canvas_set_size(w, h); 244 js_canvas_set_size(w, h);
216 canvas_w = w; 245 canvas_w = w;
@@ -228,12 +257,13 @@ void restore_puzzle_size(int w, int h)
228} 257}
229 258
230/* 259/*
231 * HTML doesn't give us a default frontend colour of its own, so we 260 * Try to extract a background colour from the canvas's CSS. In case
232 * just make up a lightish grey ourselves. 261 * it doesn't have a usable one, make up a lightish grey ourselves.
233 */ 262 */
234void frontend_default_colour(frontend *fe, float *output) 263void frontend_default_colour(frontend *fe, float *output)
235{ 264{
236 output[0] = output[1] = output[2] = 0.9F; 265 output[0] = output[1] = output[2] = 0.9F;
266 js_default_colour(output);
237} 267}
238 268
239/* 269/*
@@ -241,60 +271,85 @@ void frontend_default_colour(frontend *fe, float *output)
241 * and redo buttons get properly enabled and disabled after every move 271 * and redo buttons get properly enabled and disabled after every move
242 * or undo or new-game event. 272 * or undo or new-game event.
243 */ 273 */
244static void update_undo_redo(void) 274static void post_move(void)
245{ 275{
246 js_enable_undo_redo(midend_can_undo(me), midend_can_redo(me)); 276 js_enable_undo_redo(midend_can_undo(me), midend_can_redo(me));
277 js_update_key_labels(midend_current_key_label(me, CURSOR_SELECT2),
278 midend_current_key_label(me, CURSOR_SELECT));
247} 279}
248 280
249/* 281/*
250 * Mouse event handlers called from JS. 282 * Mouse event handlers called from JS.
251 */ 283 */
252void mousedown(int x, int y, int button) 284bool mousedown(int x, int y, int button)
253{ 285{
286 bool handled;
287
254 button = (button == 0 ? LEFT_BUTTON : 288 button = (button == 0 ? LEFT_BUTTON :
255 button == 1 ? MIDDLE_BUTTON : RIGHT_BUTTON); 289 button == 1 ? MIDDLE_BUTTON : RIGHT_BUTTON);
256 midend_process_key(me, x, y, button); 290 handled = midend_process_key(me, x, y, button) != PKR_UNUSED;
257 update_undo_redo(); 291 post_move();
292 return handled;
258} 293}
259 294
260void mouseup(int x, int y, int button) 295bool mouseup(int x, int y, int button)
261{ 296{
297 bool handled;
298
262 button = (button == 0 ? LEFT_RELEASE : 299 button = (button == 0 ? LEFT_RELEASE :
263 button == 1 ? MIDDLE_RELEASE : RIGHT_RELEASE); 300 button == 1 ? MIDDLE_RELEASE : RIGHT_RELEASE);
264 midend_process_key(me, x, y, button); 301 handled = midend_process_key(me, x, y, button) != PKR_UNUSED;
265 update_undo_redo(); 302 post_move();
303 return handled;
266} 304}
267 305
268void mousemove(int x, int y, int buttons) 306bool mousemove(int x, int y, int buttons)
269{ 307{
270 int button = (buttons & 2 ? MIDDLE_DRAG : 308 int button = (buttons & 2 ? MIDDLE_DRAG :
271 buttons & 4 ? RIGHT_DRAG : LEFT_DRAG); 309 buttons & 4 ? RIGHT_DRAG : LEFT_DRAG);
272 midend_process_key(me, x, y, button); 310 bool handled;
273 update_undo_redo(); 311
312 handled = midend_process_key(me, x, y, button) != PKR_UNUSED;
313 post_move();
314 return handled;
274} 315}
275 316
276/* 317/*
277 * Keyboard handler called from JS. 318 * Keyboard handler called from JS. Returns true if the key was
319 * handled and hence the keydown event should be cancelled.
278 */ 320 */
279void key(int keycode, int charcode, const char *key, const char *chr, 321bool key(int keycode, const char *key, const char *chr, int location,
280 bool shift, bool ctrl) 322 bool shift, bool ctrl)
281{ 323{
324 /* Key location constants from JavaScript. */
325 #define DOM_KEY_LOCATION_STANDARD 0
326 #define DOM_KEY_LOCATION_LEFT 1
327 #define DOM_KEY_LOCATION_RIGHT 2
328 #define DOM_KEY_LOCATION_NUMPAD 3
282 int keyevent = -1; 329 int keyevent = -1;
330 int process_key_result;
283 331
284 if (!strnullcmp(key, "Backspace") || !strnullcmp(key, "Del") || 332 if (!strnullcmp(key, "Backspace") || !strnullcmp(key, "Delete") ||
285 keycode == 8 || keycode == 46) { 333 !strnullcmp(key, "Del"))
286 keyevent = 127; /* Backspace / Delete */ 334 keyevent = 127; /* Backspace / Delete */
287 } else if (!strnullcmp(key, "Enter") || keycode == 13) { 335 else if (!strnullcmp(key, "Enter"))
288 keyevent = 13; /* return */ 336 keyevent = 13; /* return */
289 } else if (!strnullcmp(key, "Left") || keycode == 37) { 337 else if (!strnullcmp(key, "Spacebar"))
338 keyevent = ' ';
339 else if (!strnullcmp(key, "Escape"))
340 keyevent = 27;
341 else if (!strnullcmp(key, "ArrowLeft") || !strnullcmp(key, "Left"))
290 keyevent = CURSOR_LEFT; 342 keyevent = CURSOR_LEFT;
291 } else if (!strnullcmp(key, "Up") || keycode == 38) { 343 else if (!strnullcmp(key, "ArrowUp") || !strnullcmp(key, "Up"))
292 keyevent = CURSOR_UP; 344 keyevent = CURSOR_UP;
293 } else if (!strnullcmp(key, "Right") || keycode == 39) { 345 else if (!strnullcmp(key, "ArrowRight") || !strnullcmp(key, "Right"))
294 keyevent = CURSOR_RIGHT; 346 keyevent = CURSOR_RIGHT;
295 } else if (!strnullcmp(key, "Down") || keycode == 40) { 347 else if (!strnullcmp(key, "ArrowDown") || !strnullcmp(key, "Down"))
296 keyevent = CURSOR_DOWN; 348 keyevent = CURSOR_DOWN;
297 } else if (!strnullcmp(key, "End") || keycode == 35) { 349 else if (!strnullcmp(key, "SoftLeft"))
350 /* Left soft key on KaiOS. */
351 keyevent = CURSOR_SELECT2;
352 else if (!strnullcmp(key, "End"))
298 /* 353 /*
299 * We interpret Home, End, PgUp and PgDn as numeric keypad 354 * We interpret Home, End, PgUp and PgDn as numeric keypad
300 * controls regardless of whether they're the ones on the 355 * 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,
304 * puzzles like Cube and Inertia. 359 * puzzles like Cube and Inertia.
305 */ 360 */
306 keyevent = MOD_NUM_KEYPAD | '1'; 361 keyevent = MOD_NUM_KEYPAD | '1';
307 } else if (!strnullcmp(key, "PageDown") || keycode==34) { 362 else if (!strnullcmp(key, "PageDown"))
308 keyevent = MOD_NUM_KEYPAD | '3'; 363 keyevent = MOD_NUM_KEYPAD | '3';
309 } else if (!strnullcmp(key, "Home") || keycode==36) { 364 else if (!strnullcmp(key, "Home"))
310 keyevent = MOD_NUM_KEYPAD | '7'; 365 keyevent = MOD_NUM_KEYPAD | '7';
311 } else if (!strnullcmp(key, "PageUp") || keycode==33) { 366 else if (!strnullcmp(key, "PageUp"))
312 keyevent = MOD_NUM_KEYPAD | '9'; 367 keyevent = MOD_NUM_KEYPAD | '9';
313 } else if (shift && ctrl && (keycode & 0x1F) == 26) { 368 else if (shift && ctrl && (!strnullcmp(key, "Z") || !strnullcmp(key, "z")))
314 keyevent = UI_REDO; 369 keyevent = UI_REDO;
315 } else if (chr && chr[0] && !chr[1]) { 370 else if (key && (unsigned char)key[0] < 0x80 && key[1] == '\0')
371 /* Key generating a single ASCII character. */
372 keyevent = key[0];
373 /*
374 * In modern browsers (since about 2017), all keys that Puzzles
375 * cares about should be matched by one of the clauses above. The
376 * code below that checks keycode and chr should be relavent only
377 * in older browsers.
378 */
379 else if (keycode == 8 || keycode == 46)
380 keyevent = 127; /* Backspace / Delete */
381 else if (keycode == 13)
382 keyevent = 13; /* return */
383 else if (keycode == 37)
384 keyevent = CURSOR_LEFT;
385 else if (keycode == 38)
386 keyevent = CURSOR_UP;
387 else if (keycode == 39)
388 keyevent = CURSOR_RIGHT;
389 else if (keycode == 40)
390 keyevent = CURSOR_DOWN;
391 else if (keycode == 35)
392 keyevent = MOD_NUM_KEYPAD | '1';
393 else if (keycode == 34)
394 keyevent = MOD_NUM_KEYPAD | '3';
395 else if (keycode == 36)
396 keyevent = MOD_NUM_KEYPAD | '7';
397 else if (keycode == 33)
398 keyevent = MOD_NUM_KEYPAD | '9';
399 else if (shift && ctrl && (keycode & 0x1F) == 26)
400 keyevent = UI_REDO;
401 else if (chr && chr[0] && !chr[1])
316 keyevent = chr[0] & 0xFF; 402 keyevent = chr[0] & 0xFF;
317 } else if (keycode >= 96 && keycode < 106) { 403 else if (keycode >= 96 && keycode < 106)
318 keyevent = MOD_NUM_KEYPAD | ('0' + keycode - 96); 404 keyevent = MOD_NUM_KEYPAD | ('0' + keycode - 96);
319 } else if (keycode >= 65 && keycode <= 90) { 405 else if (keycode >= 65 && keycode <= 90)
320 keyevent = keycode + (shift ? 0 : 32); 406 keyevent = keycode + (shift ? 0 : 32);
321 } else if (keycode >= 48 && keycode <= 57) { 407 else if (keycode >= 48 && keycode <= 57)
322 keyevent = keycode; 408 keyevent = keycode;
323 } else if (keycode == 32) { /* space / CURSOR_SELECT2 */ 409 else if (keycode == 32) /* space / CURSOR_SELECT2 */
324 keyevent = keycode; 410 keyevent = keycode;
325 }
326 411
327 if (keyevent >= 0) { 412 if (keyevent >= 0) {
328 if (shift && (keyevent >= 0x100 && !IS_UI_FAKE_KEY(keyevent))) 413 if (shift) keyevent |= MOD_SHFT;
329 keyevent |= MOD_SHFT; 414 if (ctrl) keyevent |= MOD_CTRL;
330 415 if (location == DOM_KEY_LOCATION_NUMPAD) keyevent |= MOD_NUM_KEYPAD;
331 if (ctrl && !IS_UI_FAKE_KEY(keyevent)) {
332 if (keyevent >= 0x100)
333 keyevent |= MOD_CTRL;
334 else
335 keyevent &= 0x1F;
336 }
337 416
338 midend_process_key(me, 0, 0, keyevent); 417 process_key_result = midend_process_key(me, 0, 0, keyevent);
339 update_undo_redo(); 418 post_move();
419 /*
420 * Treat Backspace specially because that's expected on KaiOS.
421 * https://developer.kaiostech.com/docs/design-guide/key
422 */
423 if (process_key_result == PKR_NO_EFFECT &&
424 !strnullcmp(key, "Backspace"))
425 return false;
426 return process_key_result != PKR_UNUSED;
340 } 427 }
428 return false; /* Event not handled, because we don't even recognise it. */
341} 429}
342 430
343/* 431/*
@@ -387,14 +475,10 @@ static void js_draw_text(void *handle, int x, int y, int fonttype,
387 int fontsize, int align, int colour, 475 int fontsize, int align, int colour,
388 const char *text) 476 const char *text)
389{ 477{
390 char fontstyle[80];
391 int halign; 478 int halign;
392 479
393 sprintf(fontstyle, "%dpx %s", fontsize,
394 fonttype == FONT_FIXED ? "monospace" : "sans-serif");
395
396 if (align & ALIGN_VCENTRE) 480 if (align & ALIGN_VCENTRE)
397 y += js_canvas_find_font_midpoint(fontsize, fontstyle); 481 y += js_canvas_find_font_midpoint(fontsize, fonttype == FONT_FIXED);
398 482
399 if (align & ALIGN_HCENTRE) 483 if (align & ALIGN_HCENTRE)
400 halign = 1; 484 halign = 1;
@@ -403,41 +487,38 @@ static void js_draw_text(void *handle, int x, int y, int fonttype,
403 else 487 else
404 halign = 0; 488 halign = 0;
405 489
406 js_canvas_draw_text(x, y, halign, colour_strings[colour], fontstyle, text); 490 js_canvas_draw_text(x, y, halign, colour,
491 fontsize, fonttype == FONT_FIXED, text);
407} 492}
408 493
409static void js_draw_rect(void *handle, int x, int y, int w, int h, int colour) 494static void js_draw_rect(void *handle, int x, int y, int w, int h, int colour)
410{ 495{
411 js_canvas_draw_rect(x, y, w, h, colour_strings[colour]); 496 js_canvas_draw_rect(x, y, w, h, colour);
412} 497}
413 498
414static void js_draw_line(void *handle, int x1, int y1, int x2, int y2, 499static void js_draw_line(void *handle, int x1, int y1, int x2, int y2,
415 int colour) 500 int colour)
416{ 501{
417 js_canvas_draw_line(x1, y1, x2, y2, 1, colour_strings[colour]); 502 js_canvas_draw_line(x1, y1, x2, y2, 1, colour);
418} 503}
419 504
420static void js_draw_thick_line(void *handle, float thickness, 505static void js_draw_thick_line(void *handle, float thickness,
421 float x1, float y1, float x2, float y2, 506 float x1, float y1, float x2, float y2,
422 int colour) 507 int colour)
423{ 508{
424 js_canvas_draw_line(x1, y1, x2, y2, thickness, colour_strings[colour]); 509 js_canvas_draw_line(x1, y1, x2, y2, thickness, colour);
425} 510}
426 511
427static void js_draw_poly(void *handle, int *coords, int npoints, 512static void js_draw_poly(void *handle, const int *coords, int npoints,
428 int fillcolour, int outlinecolour) 513 int fillcolour, int outlinecolour)
429{ 514{
430 js_canvas_draw_poly(coords, npoints, 515 js_canvas_draw_poly(coords, npoints, fillcolour, outlinecolour);
431 fillcolour >= 0 ? colour_strings[fillcolour] : NULL,
432 colour_strings[outlinecolour]);
433} 516}
434 517
435static void js_draw_circle(void *handle, int cx, int cy, int radius, 518static void js_draw_circle(void *handle, int cx, int cy, int radius,
436 int fillcolour, int outlinecolour) 519 int fillcolour, int outlinecolour)
437{ 520{
438 js_canvas_draw_circle(cx, cy, radius, 521 js_canvas_draw_circle(cx, cy, radius, fillcolour, outlinecolour);
439 fillcolour >= 0 ? colour_strings[fillcolour] : NULL,
440 colour_strings[outlinecolour]);
441} 522}
442 523
443struct blitter { 524struct blitter {
@@ -527,7 +608,7 @@ static char *js_text_fallback(void *handle, const char *const *strings,
527 return dupstr(strings[0]); /* Emscripten has no trouble with UTF-8 */ 608 return dupstr(strings[0]); /* Emscripten has no trouble with UTF-8 */
528} 609}
529 610
530const struct drawing_api js_drawing = { 611static const struct drawing_api js_drawing = {
531 js_draw_text, 612 js_draw_text,
532 js_draw_rect, 613 js_draw_rect,
533 js_draw_line, 614 js_draw_line,
@@ -554,9 +635,9 @@ const struct drawing_api js_drawing = {
554 */ 635 */
555static game_params **presets; 636static game_params **presets;
556static int npresets; 637static int npresets;
557bool have_presets_dropdown; 638static bool have_presets_dropdown;
558 639
559void populate_js_preset_menu(int menuid, struct preset_menu *menu) 640static void populate_js_preset_menu(int menuid, struct preset_menu *menu)
560{ 641{
561 int i; 642 int i;
562 for (i = 0; i < menu->n_entries; i++) { 643 for (i = 0; i < menu->n_entries; i++) {
@@ -571,7 +652,7 @@ void populate_js_preset_menu(int menuid, struct preset_menu *menu)
571 } 652 }
572} 653}
573 654
574void select_appropriate_preset(void) 655static void select_appropriate_preset(void)
575{ 656{
576 if (have_presets_dropdown) { 657 if (have_presets_dropdown) {
577 int preset = midend_which_preset(me); 658 int preset = midend_which_preset(me);
@@ -665,10 +746,20 @@ static void cfg_end(bool use_results)
665 * open for the user to adjust them and try again. 746 * open for the user to adjust them and try again.
666 */ 747 */
667 js_error_box(err); 748 js_error_box(err);
749 } else if (cfg_which == CFG_PREFS) {
750 /*
751 * Acceptable settings for user preferences: enact them
752 * without blowing away the current game.
753 */
754 resize();
755 midend_redraw(me);
756 free_cfg(cfg);
757 js_dialog_cleanup();
758 save_prefs(me);
668 } else { 759 } else {
669 /* 760 /*
670 * New settings are fine; start a new game and close the 761 * Acceptable settings for the remaining configuration
671 * dialog. 762 * types: start a new game and close the dialog.
672 */ 763 */
673 select_appropriate_preset(); 764 select_appropriate_preset();
674 midend_new_game(me); 765 midend_new_game(me);
@@ -686,10 +777,7 @@ static void cfg_end(bool use_results)
686 * select Custom from the list, but change your mind and hit 777 * select Custom from the list, but change your mind and hit
687 * Esc. The Custom option will now still be selected in the 778 * Esc. The Custom option will now still be selected in the
688 * list, whereas obviously it should show the preset you still 779 * list, whereas obviously it should show the preset you still
689 * _actually_ have selected. Worse still, it'll be the visible 780 * _actually_ have selected.)
690 * rather than invisible Custom option - see the comment in
691 * js_add_preset in emcclib.js - so you won't even be able to
692 * select Custom without a faffy workaround.)
693 */ 781 */
694 select_appropriate_preset(); 782 select_appropriate_preset();
695 783
@@ -731,7 +819,7 @@ void command(int n)
731 midend_new_game(me); 819 midend_new_game(me);
732 resize(); 820 resize();
733 midend_redraw(me); 821 midend_redraw(me);
734 update_undo_redo(); 822 post_move();
735 js_focus_canvas(); 823 js_focus_canvas();
736 select_appropriate_preset(); 824 select_appropriate_preset();
737 } 825 }
@@ -739,30 +827,30 @@ void command(int n)
739 break; 827 break;
740 case 3: /* OK clicked in a config box */ 828 case 3: /* OK clicked in a config box */
741 cfg_end(true); 829 cfg_end(true);
742 update_undo_redo(); 830 post_move();
743 break; 831 break;
744 case 4: /* Cancel clicked in a config box */ 832 case 4: /* Cancel clicked in a config box */
745 cfg_end(false); 833 cfg_end(false);
746 update_undo_redo(); 834 post_move();
747 break; 835 break;
748 case 5: /* New Game */ 836 case 5: /* New Game */
749 midend_process_key(me, 0, 0, UI_NEWGAME); 837 midend_process_key(me, 0, 0, UI_NEWGAME);
750 update_undo_redo(); 838 post_move();
751 js_focus_canvas(); 839 js_focus_canvas();
752 break; 840 break;
753 case 6: /* Restart */ 841 case 6: /* Restart */
754 midend_restart_game(me); 842 midend_restart_game(me);
755 update_undo_redo(); 843 post_move();
756 js_focus_canvas(); 844 js_focus_canvas();
757 break; 845 break;
758 case 7: /* Undo */ 846 case 7: /* Undo */
759 midend_process_key(me, 0, 0, UI_UNDO); 847 midend_process_key(me, 0, 0, UI_UNDO);
760 update_undo_redo(); 848 post_move();
761 js_focus_canvas(); 849 js_focus_canvas();
762 break; 850 break;
763 case 8: /* Redo */ 851 case 8: /* Redo */
764 midend_process_key(me, 0, 0, UI_REDO); 852 midend_process_key(me, 0, 0, UI_REDO);
765 update_undo_redo(); 853 post_move();
766 js_focus_canvas(); 854 js_focus_canvas();
767 break; 855 break;
768 case 9: /* Solve */ 856 case 9: /* Solve */
@@ -771,12 +859,25 @@ void command(int n)
771 if (msg) 859 if (msg)
772 js_error_box(msg); 860 js_error_box(msg);
773 } 861 }
774 update_undo_redo(); 862 post_move();
775 js_focus_canvas(); 863 js_focus_canvas();
776 break; 864 break;
865 case 10: /* user preferences */
866 cfg_start(CFG_PREFS);
867 break;
777 } 868 }
778} 869}
779 870
871char *get_text_format(void)
872{
873 return midend_text_format(me);
874}
875
876void free_text_format(char *buffer)
877{
878 sfree(buffer);
879}
880
780/* ---------------------------------------------------------------------- 881/* ----------------------------------------------------------------------
781 * Called from JS to prepare a save-game file, and free one after it's 882 * Called from JS to prepare a save-game file, and free one after it's
782 * been used. 883 * been used.
@@ -824,30 +925,20 @@ void free_save_file(char *buffer)
824 sfree(buffer); 925 sfree(buffer);
825} 926}
826 927
827struct savefile_read_ctx {
828 const char *buffer;
829 int len_remaining;
830};
831
832static bool savefile_read(void *vctx, void *buf, int len) 928static bool savefile_read(void *vctx, void *buf, int len)
833{ 929{
834 struct savefile_read_ctx *ctx = (struct savefile_read_ctx *)vctx; 930 return js_savefile_read(buf, len);
835 if (ctx->len_remaining < len)
836 return false;
837 memcpy(buf, ctx->buffer, len);
838 ctx->len_remaining -= len;
839 ctx->buffer += len;
840 return true;
841} 931}
842 932
843void load_game(const char *buffer, int len) 933void load_game(void)
844{ 934{
845 struct savefile_read_ctx ctx;
846 const char *err; 935 const char *err;
847 936
848 ctx.buffer = buffer; 937 /*
849 ctx.len_remaining = len; 938 * savefile_read_callback in JavaScript was set up by our caller
850 err = midend_deserialise(me, savefile_read, &ctx); 939 * as a closure that knows what file we're loading.
940 */
941 err = midend_deserialise(me, savefile_read, NULL);
851 942
852 if (err) { 943 if (err) {
853 js_error_box(err); 944 js_error_box(err);
@@ -855,10 +946,70 @@ void load_game(const char *buffer, int len)
855 select_appropriate_preset(); 946 select_appropriate_preset();
856 resize(); 947 resize();
857 midend_redraw(me); 948 midend_redraw(me);
949 update_permalinks();
950 post_move();
858 } 951 }
859} 952}
860 953
861/* ---------------------------------------------------------------------- 954/* ----------------------------------------------------------------------
955 * Functions to load and save preferences, calling out to JS to access
956 * the appropriate localStorage slot.
957 */
958
959static void save_prefs(midend *me)
960{
961 struct savefile_write_ctx ctx;
962 size_t size;
963
964 /* First pass, to count up the size */
965 ctx.buffer = NULL;
966 ctx.pos = 0;
967 midend_save_prefs(me, savefile_write, &ctx);
968 size = ctx.pos;
969
970 /* Second pass, to actually write out the data. As with
971 * get_save_file, we append a terminating \0. */
972 ctx.buffer = snewn(size+1, char);
973 ctx.pos = 0;
974 midend_save_prefs(me, savefile_write, &ctx);
975 assert(ctx.pos == size);
976 ctx.buffer[ctx.pos] = '\0';
977
978 js_save_prefs(ctx.buffer);
979
980 sfree(ctx.buffer);
981}
982
983struct prefs_read_ctx {
984 const char *buffer;
985 size_t pos, len;
986};
987
988static bool prefs_read(void *vctx, void *buf, int len)
989{
990 struct prefs_read_ctx *ctx = (struct prefs_read_ctx *)vctx;
991
992 if (len < 0)
993 return false;
994 if (ctx->len - ctx->pos < len)
995 return false;
996 memcpy(buf, ctx->buffer + ctx->pos, len);
997 ctx->pos += len;
998 return true;
999}
1000
1001void prefs_load_callback(midend *me, const char *prefs)
1002{
1003 struct prefs_read_ctx ctx;
1004
1005 ctx.buffer = prefs;
1006 ctx.len = strlen(prefs);
1007 ctx.pos = 0;
1008
1009 midend_load_prefs(me, prefs_read, &ctx);
1010}
1011
1012/* ----------------------------------------------------------------------
862 * Setup function called at page load time. It's called main() because 1013 * Setup function called at page load time. It's called main() because
863 * that's the most convenient thing in Emscripten, but it's not main() 1014 * that's the most convenient thing in Emscripten, but it's not main()
864 * in the usual sense of bounding the program's entire execution. 1015 * in the usual sense of bounding the program's entire execution.
@@ -870,12 +1021,18 @@ int main(int argc, char **argv)
870{ 1021{
871 const char *param_err; 1022 const char *param_err;
872 float *colours; 1023 float *colours;
873 int i; 1024 int i, ncolours;
1025
1026 /*
1027 * Initialise JavaScript event handlers.
1028 */
1029 js_init_puzzle();
874 1030
875 /* 1031 /*
876 * Instantiate a midend. 1032 * Instantiate a midend.
877 */ 1033 */
878 me = midend_new(NULL, &thegame, &js_drawing, NULL); 1034 me = midend_new(NULL, &thegame, &js_drawing, NULL);
1035 js_load_prefs(me);
879 1036
880 /* 1037 /*
881 * Chuck in the HTML fragment ID if we have one (trimming the 1038 * Chuck in the HTML fragment ID if we have one (trimming the
@@ -896,10 +1053,10 @@ int main(int argc, char **argv)
896 resize(); 1053 resize();
897 1054
898 /* 1055 /*
899 * Create a status bar, if needed. 1056 * Remove the status bar, if not needed.
900 */ 1057 */
901 if (midend_wants_statusbar(me)) 1058 if (!midend_wants_statusbar(me))
902 js_canvas_make_statusbar(); 1059 js_canvas_remove_statusbar();
903 1060
904 /* 1061 /*
905 * Set up the game-type dropdown with presets and/or the Custom 1062 * Set up the game-type dropdown with presets and/or the Custom
@@ -907,23 +1064,33 @@ int main(int argc, char **argv)
907 */ 1064 */
908 { 1065 {
909 struct preset_menu *menu = midend_get_presets(me, &npresets); 1066 struct preset_menu *menu = midend_get_presets(me, &npresets);
1067 bool may_configure = false;
910 presets = snewn(npresets, game_params *); 1068 presets = snewn(npresets, game_params *);
911 for (i = 0; i < npresets; i++) 1069 for (i = 0; i < npresets; i++)
912 presets[i] = NULL; 1070 presets[i] = NULL;
913 1071
914 populate_js_preset_menu(0, menu); 1072 populate_js_preset_menu(0, menu);
915 1073
916 if (thegame.can_configure)
917 js_add_preset(0, "Custom", -1);
918
919 have_presets_dropdown = true;
920
921 /* 1074 /*
922 * Now ensure the appropriate element of the presets menu 1075 * Crude hack to allow the "Custom..." item to be hidden on
923 * starts off selected, in case it isn't the first one in the 1076 * KaiOS, where dialogs don't yet work.
924 * list (e.g. Slant).
925 */ 1077 */
926 select_appropriate_preset(); 1078 if (thegame.can_configure && getenv_bool("PUZZLES_ALLOW_CUSTOM", true))
1079 may_configure = true;
1080 if (may_configure)
1081 js_add_preset(0, "Custom...", -1);
1082
1083 have_presets_dropdown = npresets > 1 || may_configure;
1084
1085 if (have_presets_dropdown)
1086 /*
1087 * Now ensure the appropriate element of the presets menu
1088 * starts off selected, in case it isn't the first one in the
1089 * list (e.g. Slant).
1090 */
1091 select_appropriate_preset();
1092 else
1093 js_remove_type_dropdown();
927 } 1094 }
928 1095
929 /* 1096 /*
@@ -937,14 +1104,13 @@ int main(int argc, char **argv)
937 * hex ID strings. 1104 * hex ID strings.
938 */ 1105 */
939 colours = midend_colours(me, &ncolours); 1106 colours = midend_colours(me, &ncolours);
940 colour_strings = snewn(ncolours, char *);
941 for (i = 0; i < ncolours; i++) { 1107 for (i = 0; i < ncolours; i++) {
942 char col[40]; 1108 char col[40];
943 sprintf(col, "#%02x%02x%02x", 1109 sprintf(col, "#%02x%02x%02x",
944 (unsigned)(0.5 + 255 * colours[i*3+0]), 1110 (unsigned)(0.5F + 255 * colours[i*3+0]),
945 (unsigned)(0.5 + 255 * colours[i*3+1]), 1111 (unsigned)(0.5F + 255 * colours[i*3+1]),
946 (unsigned)(0.5 + 255 * colours[i*3+2])); 1112 (unsigned)(0.5F + 255 * colours[i*3+2]));
947 colour_strings[i] = dupstr(col); 1113 js_set_colour(i, col);
948 } 1114 }
949 1115
950 /* 1116 /*
@@ -960,7 +1126,7 @@ int main(int argc, char **argv)
960 */ 1126 */
961 midend_redraw(me); 1127 midend_redraw(me);
962 update_permalinks(); 1128 update_permalinks();
963 update_undo_redo(); 1129 post_move();
964 1130
965 /* 1131 /*
966 * If we were given an erroneous game ID in argv[1], now's the 1132 * If we were given an erroneous game ID in argv[1], now's the
@@ -972,6 +1138,11 @@ int main(int argc, char **argv)
972 js_error_box(param_err); 1138 js_error_box(param_err);
973 1139
974 /* 1140 /*
1141 * Reveal the puzzle!
1142 */
1143 js_post_init();
1144
1145 /*
975 * Done. Return to JS, and await callbacks! 1146 * Done. Return to JS, and await callbacks!
976 */ 1147 */
977 return 0; 1148 return 0;