diff options
author | Franklin Wei <git@fwei.tk> | 2017-04-29 18:21:56 -0400 |
---|---|---|
committer | Franklin Wei <git@fwei.tk> | 2017-04-29 18:24:42 -0400 |
commit | 881746789a489fad85aae8317555f73dbe261556 (patch) | |
tree | cec2946362c4698c8db3c10f3242ef546c2c22dd /apps/plugins/puzzles/src/emccpre.js | |
parent | 03dd4b92be7dcd5c8ab06da3810887060e06abd5 (diff) | |
download | rockbox-881746789a489fad85aae8317555f73dbe261556.tar.gz rockbox-881746789a489fad85aae8317555f73dbe261556.zip |
puzzles: refactor and resync with upstream
This brings puzzles up-to-date with upstream revision
2d333750272c3967cfd5cd3677572cddeaad5932, though certain changes made
by me, including cursor-only Untangle and some compilation fixes
remain. Upstream code has been moved to its separate subdirectory and
future syncs can be done by simply copying over the new sources.
Change-Id: Ia6506ca5f78c3627165ea6791d38db414ace0804
Diffstat (limited to 'apps/plugins/puzzles/src/emccpre.js')
-rw-r--r-- | apps/plugins/puzzles/src/emccpre.js | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/src/emccpre.js b/apps/plugins/puzzles/src/emccpre.js new file mode 100644 index 0000000000..d715858883 --- /dev/null +++ b/apps/plugins/puzzles/src/emccpre.js | |||
@@ -0,0 +1,359 @@ | |||
1 | /* | ||
2 | * emccpre.js: one of the Javascript components of an Emscripten-based | ||
3 | * web/Javascript front end for Puzzles. | ||
4 | * | ||
5 | * The other parts of this system live in emcc.c and emcclib.js. It | ||
6 | * also depends on being run in the context of a web page containing | ||
7 | * an appropriate collection of bits and pieces (a canvas, some | ||
8 | * buttons and links etc), which is generated for each puzzle by the | ||
9 | * script html/jspage.pl. | ||
10 | * | ||
11 | * This file contains the Javascript code which is prefixed unmodified | ||
12 | * to Emscripten's output via the --pre-js option. It declares all our | ||
13 | * global variables, and provides the puzzle init function and a | ||
14 | * couple of other helper functions. | ||
15 | */ | ||
16 | |||
17 | // To avoid flicker while doing complicated drawing, we use two | ||
18 | // canvases, the same size. One is actually on the web page, and the | ||
19 | // other is off-screen. We do all our drawing on the off-screen one | ||
20 | // first, and then copy rectangles of it to the on-screen canvas in | ||
21 | // response to draw_update() calls by the game backend. | ||
22 | var onscreen_canvas, offscreen_canvas; | ||
23 | |||
24 | // A persistent drawing context for the offscreen canvas, to save | ||
25 | // constructing one per individual graphics operation. | ||
26 | var ctx; | ||
27 | |||
28 | // Bounding rectangle for the copy to the onscreen canvas that will be | ||
29 | // done at drawing end time. Updated by js_canvas_draw_update and used | ||
30 | // by js_canvas_end_draw. | ||
31 | var update_xmin, update_xmax, update_ymin, update_ymax; | ||
32 | |||
33 | // Module object for Emscripten. We fill in these parameters to ensure | ||
34 | // that Module.run() won't be called until we're ready (we want to do | ||
35 | // our own init stuff first), and that when main() returns nothing | ||
36 | // will get cleaned up so we remain able to call the puzzle's various | ||
37 | // callbacks. | ||
38 | var Module = { | ||
39 | 'noInitialRun': true, | ||
40 | 'noExitRuntime': true | ||
41 | }; | ||
42 | |||
43 | // Variables used by js_canvas_find_font_midpoint(). | ||
44 | var midpoint_test_str = "ABCDEFGHIKLMNOPRSTUVWXYZ0123456789"; | ||
45 | var midpoint_cache = []; | ||
46 | |||
47 | // Variables used by js_activate_timer() and js_deactivate_timer(). | ||
48 | var timer = null; | ||
49 | var timer_reference_date; | ||
50 | |||
51 | // void timer_callback(double tplus); | ||
52 | // | ||
53 | // Called every 20ms while timing is active. | ||
54 | var timer_callback; | ||
55 | |||
56 | // The status bar object, if we create one. | ||
57 | var statusbar = null; | ||
58 | |||
59 | // Currently live blitters. We keep an integer id for each one on the | ||
60 | // JS side; the C side, which expects a blitter to look like a struct, | ||
61 | // simply defines the struct to contain that integer id. | ||
62 | var blittercount = 0; | ||
63 | var blitters = []; | ||
64 | |||
65 | // State for the dialog-box mechanism. dlg_dimmer and dlg_form are the | ||
66 | // page-darkening overlay and the actual dialog box respectively; | ||
67 | // dlg_next_id is used to allocate each checkbox a unique id to use | ||
68 | // for linking its label to it (see js_dialog_boolean); | ||
69 | // dlg_return_funcs is a list of JS functions to be called when the OK | ||
70 | // button is pressed, to pass the results back to C. | ||
71 | var dlg_dimmer = null, dlg_form = null; | ||
72 | var dlg_next_id = 0; | ||
73 | var dlg_return_funcs = null; | ||
74 | |||
75 | // void dlg_return_sval(int index, const char *val); | ||
76 | // void dlg_return_ival(int index, int val); | ||
77 | // | ||
78 | // C-side entry points called by functions in dlg_return_funcs, to | ||
79 | // pass back the final value in each dialog control. | ||
80 | var dlg_return_sval, dlg_return_ival; | ||
81 | |||
82 | // The <ul> object implementing the game-type drop-down, and a list of | ||
83 | // the <li> objects inside it. Used by js_add_preset(), | ||
84 | // js_get_selected_preset() and js_select_preset(). | ||
85 | var gametypelist = null, gametypeitems = []; | ||
86 | var gametypeselectedindex = null; | ||
87 | var gametypesubmenus = []; | ||
88 | |||
89 | // The two anchors used to give permalinks to the current puzzle. Used | ||
90 | // by js_update_permalinks(). | ||
91 | var permalink_seed, permalink_desc; | ||
92 | |||
93 | // The undo and redo buttons. Used by js_enable_undo_redo(). | ||
94 | var undo_button, redo_button; | ||
95 | |||
96 | // A div element enclosing both the puzzle and its status bar, used | ||
97 | // for positioning the resize handle. | ||
98 | var resizable_div; | ||
99 | |||
100 | // Helper function to find the absolute position of a given DOM | ||
101 | // element on a page, by iterating upwards through the DOM finding | ||
102 | // each element's offset from its parent, and thus calculating the | ||
103 | // page-relative position of the target element. | ||
104 | function element_coords(element) { | ||
105 | var ex = 0, ey = 0; | ||
106 | while (element.offsetParent) { | ||
107 | ex += element.offsetLeft; | ||
108 | ey += element.offsetTop; | ||
109 | element = element.offsetParent; | ||
110 | } | ||
111 | return {x: ex, y:ey}; | ||
112 | } | ||
113 | |||
114 | // Helper function which is passed a mouse event object and a DOM | ||
115 | // element, and returns the coordinates of the mouse event relative to | ||
116 | // the top left corner of the element by subtracting element_coords | ||
117 | // from event.page{X,Y}. | ||
118 | function relative_mouse_coords(event, element) { | ||
119 | var ecoords = element_coords(element); | ||
120 | return {x: event.pageX - ecoords.x, | ||
121 | y: event.pageY - ecoords.y}; | ||
122 | } | ||
123 | |||
124 | // Enable and disable items in the CSS menus. | ||
125 | function disable_menu_item(item, disabledFlag) { | ||
126 | if (disabledFlag) | ||
127 | item.className = "disabled"; | ||
128 | else | ||
129 | item.className = ""; | ||
130 | } | ||
131 | |||
132 | // Init function called from body.onload. | ||
133 | function initPuzzle() { | ||
134 | // Construct the off-screen canvas used for double buffering. | ||
135 | onscreen_canvas = document.getElementById("puzzlecanvas"); | ||
136 | offscreen_canvas = document.createElement("canvas"); | ||
137 | offscreen_canvas.width = onscreen_canvas.width; | ||
138 | offscreen_canvas.height = onscreen_canvas.height; | ||
139 | |||
140 | // Stop right-clicks on the puzzle from popping up a context menu. | ||
141 | // We need those right-clicks! | ||
142 | onscreen_canvas.oncontextmenu = function(event) { return false; } | ||
143 | |||
144 | // Set up mouse handlers. We do a bit of tracking of the currently | ||
145 | // pressed mouse buttons, to avoid sending mousemoves with no | ||
146 | // button down (our puzzles don't want those events). | ||
147 | mousedown = Module.cwrap('mousedown', 'void', | ||
148 | ['number', 'number', 'number']); | ||
149 | buttons_down = 0; | ||
150 | onscreen_canvas.onmousedown = function(event) { | ||
151 | var xy = relative_mouse_coords(event, onscreen_canvas); | ||
152 | mousedown(xy.x, xy.y, event.button); | ||
153 | buttons_down |= 1 << event.button; | ||
154 | onscreen_canvas.setCapture(true); | ||
155 | }; | ||
156 | mousemove = Module.cwrap('mousemove', 'void', | ||
157 | ['number', 'number', 'number']); | ||
158 | onscreen_canvas.onmousemove = function(event) { | ||
159 | if (buttons_down) { | ||
160 | var xy = relative_mouse_coords(event, onscreen_canvas); | ||
161 | mousemove(xy.x, xy.y, buttons_down); | ||
162 | } | ||
163 | }; | ||
164 | mouseup = Module.cwrap('mouseup', 'void', | ||
165 | ['number', 'number', 'number']); | ||
166 | onscreen_canvas.onmouseup = function(event) { | ||
167 | if (buttons_down & (1 << event.button)) { | ||
168 | buttons_down ^= 1 << event.button; | ||
169 | var xy = relative_mouse_coords(event, onscreen_canvas); | ||
170 | mouseup(xy.x, xy.y, event.button); | ||
171 | } | ||
172 | }; | ||
173 | |||
174 | // Set up keyboard handlers. We do all the actual keyboard | ||
175 | // handling in onkeydown; but we also call event.preventDefault() | ||
176 | // in both the keydown and keypress handlers. This means that | ||
177 | // while the canvas itself has focus, _all_ keypresses go only to | ||
178 | // the puzzle - so users of this puzzle collection in other media | ||
179 | // can indulge their instinct to press ^R for redo, for example, | ||
180 | // without accidentally reloading the page. | ||
181 | key = Module.cwrap('key', 'void', ['number', 'number', 'string', | ||
182 | 'string', 'number', 'number']); | ||
183 | onscreen_canvas.onkeydown = function(event) { | ||
184 | key(event.keyCode, event.charCode, event.key, event.char, | ||
185 | event.shiftKey ? 1 : 0, event.ctrlKey ? 1 : 0); | ||
186 | event.preventDefault(); | ||
187 | }; | ||
188 | onscreen_canvas.onkeypress = function(event) { | ||
189 | event.preventDefault(); | ||
190 | }; | ||
191 | |||
192 | // command() is a C function called to pass back events which | ||
193 | // don't fall into other categories like mouse and key events. | ||
194 | // Mostly those are button presses, but there's also one for the | ||
195 | // game-type dropdown having been changed. | ||
196 | command = Module.cwrap('command', 'void', ['number']); | ||
197 | |||
198 | // Event handlers for buttons and things, which call command(). | ||
199 | document.getElementById("specific").onclick = function(event) { | ||
200 | // Ensure we don't accidentally process these events when a | ||
201 | // dialog is actually active, e.g. because the button still | ||
202 | // has keyboard focus | ||
203 | if (dlg_dimmer === null) | ||
204 | command(0); | ||
205 | }; | ||
206 | document.getElementById("random").onclick = function(event) { | ||
207 | if (dlg_dimmer === null) | ||
208 | command(1); | ||
209 | }; | ||
210 | document.getElementById("new").onclick = function(event) { | ||
211 | if (dlg_dimmer === null) | ||
212 | command(5); | ||
213 | }; | ||
214 | document.getElementById("restart").onclick = function(event) { | ||
215 | if (dlg_dimmer === null) | ||
216 | command(6); | ||
217 | }; | ||
218 | undo_button = document.getElementById("undo"); | ||
219 | undo_button.onclick = function(event) { | ||
220 | if (dlg_dimmer === null) | ||
221 | command(7); | ||
222 | }; | ||
223 | redo_button = document.getElementById("redo"); | ||
224 | redo_button.onclick = function(event) { | ||
225 | if (dlg_dimmer === null) | ||
226 | command(8); | ||
227 | }; | ||
228 | document.getElementById("solve").onclick = function(event) { | ||
229 | if (dlg_dimmer === null) | ||
230 | command(9); | ||
231 | }; | ||
232 | |||
233 | gametypelist = document.getElementById("gametype"); | ||
234 | gametypesubmenus.push(gametypelist); | ||
235 | |||
236 | // In IE, the canvas doesn't automatically gain focus on a mouse | ||
237 | // click, so make sure it does | ||
238 | onscreen_canvas.addEventListener("mousedown", function(event) { | ||
239 | onscreen_canvas.focus(); | ||
240 | }); | ||
241 | |||
242 | // In our dialog boxes, Return and Escape should be like pressing | ||
243 | // OK and Cancel respectively | ||
244 | document.addEventListener("keydown", function(event) { | ||
245 | |||
246 | if (dlg_dimmer !== null && event.keyCode == 13) { | ||
247 | for (var i in dlg_return_funcs) | ||
248 | dlg_return_funcs[i](); | ||
249 | command(3); | ||
250 | } | ||
251 | |||
252 | if (dlg_dimmer !== null && event.keyCode == 27) | ||
253 | command(4); | ||
254 | }); | ||
255 | |||
256 | // Set up the function pointers we haven't already grabbed. | ||
257 | dlg_return_sval = Module.cwrap('dlg_return_sval', 'void', | ||
258 | ['number','string']); | ||
259 | dlg_return_ival = Module.cwrap('dlg_return_ival', 'void', | ||
260 | ['number','number']); | ||
261 | timer_callback = Module.cwrap('timer_callback', 'void', ['number']); | ||
262 | |||
263 | // Save references to the two permalinks. | ||
264 | permalink_desc = document.getElementById("permalink-desc"); | ||
265 | permalink_seed = document.getElementById("permalink-seed"); | ||
266 | |||
267 | // Default to giving keyboard focus to the puzzle. | ||
268 | onscreen_canvas.focus(); | ||
269 | |||
270 | // Create the resize handle. | ||
271 | var resize_handle = document.createElement("canvas"); | ||
272 | resize_handle.width = 10; | ||
273 | resize_handle.height = 10; | ||
274 | { | ||
275 | var ctx = resize_handle.getContext("2d"); | ||
276 | ctx.beginPath(); | ||
277 | for (var i = 1; i <= 7; i += 3) { | ||
278 | ctx.moveTo(8.5, i + 0.5); | ||
279 | ctx.lineTo(i + 0.5, 8.5); | ||
280 | } | ||
281 | ctx.lineWidth = '1px'; | ||
282 | ctx.lineCap = 'round'; | ||
283 | ctx.lineJoin = 'round'; | ||
284 | ctx.strokeStyle = '#000000'; | ||
285 | ctx.stroke(); | ||
286 | } | ||
287 | resizable_div = document.getElementById("resizable"); | ||
288 | resizable_div.appendChild(resize_handle); | ||
289 | resize_handle.style.position = 'absolute'; | ||
290 | resize_handle.style.zIndex = 98; | ||
291 | resize_handle.style.bottom = "0"; | ||
292 | resize_handle.style.right = "0"; | ||
293 | resize_handle.style.cursor = "se-resize"; | ||
294 | resize_handle.title = "Drag to resize the puzzle. Right-click to restore the default size."; | ||
295 | var resize_xbase = null, resize_ybase = null, restore_pending = false; | ||
296 | var resize_xoffset = null, resize_yoffset = null; | ||
297 | var resize_puzzle = Module.cwrap('resize_puzzle', | ||
298 | 'void', ['number', 'number']); | ||
299 | var restore_puzzle_size = Module.cwrap('restore_puzzle_size', 'void', []); | ||
300 | resize_handle.oncontextmenu = function(event) { return false; } | ||
301 | resize_handle.onmousedown = function(event) { | ||
302 | if (event.button == 0) { | ||
303 | var xy = element_coords(onscreen_canvas); | ||
304 | resize_xbase = xy.x + onscreen_canvas.width / 2; | ||
305 | resize_ybase = xy.y; | ||
306 | resize_xoffset = xy.x + onscreen_canvas.width - event.pageX; | ||
307 | resize_yoffset = xy.y + onscreen_canvas.height - event.pageY; | ||
308 | } else { | ||
309 | restore_pending = true; | ||
310 | } | ||
311 | resize_handle.setCapture(true); | ||
312 | event.preventDefault(); | ||
313 | }; | ||
314 | window.addEventListener("mousemove", function(event) { | ||
315 | if (resize_xbase !== null && resize_ybase !== null) { | ||
316 | resize_puzzle((event.pageX + resize_xoffset - resize_xbase) * 2, | ||
317 | (event.pageY + resize_yoffset - resize_ybase)); | ||
318 | event.preventDefault(); | ||
319 | // Chrome insists on selecting text during a resize drag | ||
320 | // no matter what I do | ||
321 | if (window.getSelection) | ||
322 | window.getSelection().removeAllRanges(); | ||
323 | else | ||
324 | document.selection.empty(); } | ||
325 | }); | ||
326 | window.addEventListener("mouseup", function(event) { | ||
327 | if (resize_xbase !== null && resize_ybase !== null) { | ||
328 | resize_xbase = null; | ||
329 | resize_ybase = null; | ||
330 | onscreen_canvas.focus(); // return focus to the puzzle | ||
331 | event.preventDefault(); | ||
332 | } else if (restore_pending) { | ||
333 | // If you have the puzzle at larger than normal size and | ||
334 | // then right-click to restore, I haven't found any way to | ||
335 | // stop Chrome and IE popping up a context menu on the | ||
336 | // revealed piece of document when you release the button | ||
337 | // except by putting the actual restore into a setTimeout. | ||
338 | // Gah. | ||
339 | setTimeout(function() { | ||
340 | restore_pending = false; | ||
341 | restore_puzzle_size(); | ||
342 | onscreen_canvas.focus(); | ||
343 | }, 20); | ||
344 | event.preventDefault(); | ||
345 | } | ||
346 | }); | ||
347 | |||
348 | // Run the C setup function, passing argv[1] as the fragment | ||
349 | // identifier (so that permalinks of the form puzzle.html#game-id | ||
350 | // can launch the specified id). | ||
351 | Module.callMain([location.hash]); | ||
352 | |||
353 | // And if we get here with everything having gone smoothly, i.e. | ||
354 | // we haven't crashed for one reason or another during setup, then | ||
355 | // it's probably safe to hide the 'sorry, no puzzle here' div and | ||
356 | // show the div containing the actual puzzle. | ||
357 | document.getElementById("apology").style.display = "none"; | ||
358 | document.getElementById("puzzle").style.display = "inline"; | ||
359 | } | ||