1 | // MIT License |
2 | |
3 | // Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com |
4 | |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
6 | // of this software and associated documentation files (the "Software"), to deal |
7 | // in the Software without restriction, including without limitation the rights |
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
9 | // copies of the Software, and to permit persons to whom the Software is |
10 | // furnished to do so, subject to the following conditions: |
11 | |
12 | // The above copyright notice and this permission notice shall be included in all |
13 | // copies or substantial portions of the Software. |
14 | |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
21 | // SOFTWARE. |
22 | |
23 | #include "studio/studio.h" |
24 | #include "studio/config.h" |
25 | #include "studio/screens/menu.h" |
26 | #include "mainmenu.h" |
27 | |
28 | typedef struct |
29 | { |
30 | tic_mapping mapping; |
31 | s32 index; |
32 | s32 key; |
33 | } Gamepads; |
34 | |
35 | struct StudioMainMenu |
36 | { |
37 | Studio* studio; |
38 | tic_mem* tic; |
39 | Menu* menu; |
40 | |
41 | MenuItem* items; |
42 | s32 count; |
43 | |
44 | Gamepads gamepads; |
45 | struct StudioOptions* options; |
46 | }; |
47 | |
48 | #define OPTION_VALUES(...) \ |
49 | .values = (const char*[])__VA_ARGS__, \ |
50 | .count = COUNT_OF(((const char*[])__VA_ARGS__)) |
51 | |
52 | static void showMainMenu(void* data, s32 pos); |
53 | |
54 | StudioMainMenu* studio_mainmenu_init(Menu *, Config *config) |
55 | { |
56 | StudioMainMenu* main = NEW(StudioMainMenu); |
57 | |
58 | *main = (StudioMainMenu) |
59 | { |
60 | .menu = menu, |
61 | .options = &config->data.options, |
62 | .studio = config->studio, |
63 | .tic = config->tic, |
64 | .gamepads.key = -1, |
65 | }; |
66 | |
67 | showMainMenu(main, 0); |
68 | |
69 | return main; |
70 | } |
71 | |
72 | static void initGamepadMenu(StudioMainMenu* ); |
73 | |
74 | bool studio_mainmenu_keyboard(StudioMainMenu* main) |
75 | { |
76 | if(main && main->gamepads.key >= 0) |
77 | { |
78 | tic_key key = *main->tic->ram->input.keyboard.keys; |
79 | if(key > tic_key_unknown) |
80 | { |
81 | main->gamepads.mapping.data[main->gamepads.index * TIC_BUTTONS + main->gamepads.key] = key; |
82 | initGamepadMenu(main); |
83 | } |
84 | |
85 | return true; |
86 | } |
87 | |
88 | return false; |
89 | } |
90 | |
91 | static s32 optionFullscreenGet(void* data) |
92 | { |
93 | return tic_sys_fullscreen_get() ? 1 : 0; |
94 | } |
95 | |
96 | static void optionFullscreenSet(void* data, s32 pos) |
97 | { |
98 | StudioMainMenu* main = data; |
99 | tic_sys_fullscreen_set(main->options->fullscreen = (pos == 1)); |
100 | } |
101 | |
102 | static const char OffValue[] = "OFF" ; |
103 | static const char OnValue[] = "ON" ; |
104 | |
105 | static MenuOption FullscreenOption = |
106 | { |
107 | OPTION_VALUES({OffValue, OnValue}), |
108 | optionFullscreenGet, |
109 | optionFullscreenSet, |
110 | }; |
111 | |
112 | #if defined(CRT_SHADER_SUPPORT) |
113 | static s32 optionCrtMonitorGet(void* data) |
114 | { |
115 | StudioMainMenu* main = data; |
116 | return main->options->crt ? 1 : 0; |
117 | } |
118 | |
119 | static void optionCrtMonitorSet(void* data, s32 pos) |
120 | { |
121 | StudioMainMenu* main = data; |
122 | main->options->crt = pos == 1; |
123 | } |
124 | |
125 | static MenuOption CrtMonitorOption = |
126 | { |
127 | OPTION_VALUES({OffValue, OnValue}), |
128 | optionCrtMonitorGet, |
129 | optionCrtMonitorSet, |
130 | }; |
131 | |
132 | #endif |
133 | |
134 | static s32 optionVSyncGet(void* data) |
135 | { |
136 | StudioMainMenu* main = data; |
137 | return main->options->vsync ? 1 : 0; |
138 | } |
139 | |
140 | static void optionVSyncSet(void* data, s32 pos) |
141 | { |
142 | StudioMainMenu* main = data; |
143 | main->options->vsync = pos == 1; |
144 | } |
145 | |
146 | static MenuOption VSyncOption = |
147 | { |
148 | OPTION_VALUES({OffValue, OnValue}), |
149 | optionVSyncGet, |
150 | optionVSyncSet, |
151 | }; |
152 | |
153 | static s32 optionVolumeGet(void* data) |
154 | { |
155 | StudioMainMenu* main = data; |
156 | return main->options->volume; |
157 | } |
158 | |
159 | static void optionVolumeSet(void* data, s32 pos) |
160 | { |
161 | StudioMainMenu* main = data; |
162 | main->options->volume = pos; |
163 | } |
164 | |
165 | static MenuOption VolumeOption = |
166 | { |
167 | OPTION_VALUES( |
168 | { |
169 | "00" , "01" , "02" , "03" , |
170 | "04" , "05" , "06" , "07" , |
171 | "08" , "09" , "10" , "11" , |
172 | "12" , "13" , "14" , "15" , |
173 | }), |
174 | optionVolumeGet, |
175 | optionVolumeSet, |
176 | }; |
177 | |
178 | #if defined(BUILD_EDITORS) |
179 | |
180 | static s32 optionDevModeGet(void* data) |
181 | { |
182 | StudioMainMenu* main = data; |
183 | return main->options->devmode ? 1 : 0; |
184 | } |
185 | |
186 | static void optionDevModeSet(void* data, s32 pos) |
187 | { |
188 | StudioMainMenu* main = data; |
189 | main->options->devmode = pos == 1; |
190 | } |
191 | |
192 | static MenuOption DevModeOption = |
193 | { |
194 | OPTION_VALUES({OffValue, OnValue}), |
195 | optionDevModeGet, |
196 | optionDevModeSet, |
197 | }; |
198 | |
199 | #endif |
200 | |
201 | static void (void* data, s32 pos) |
202 | { |
203 | StudioMainMenu* main = data; |
204 | main->gamepads.index = 0; |
205 | main->gamepads.mapping = main->options->mapping; |
206 | |
207 | initGamepadMenu(main); |
208 | } |
209 | |
210 | static const MenuItem [] = |
211 | { |
212 | #if defined(CRT_SHADER_SUPPORT) |
213 | {"CRT MONITOR" , NULL, &CrtMonitorOption}, |
214 | #endif |
215 | #if defined(BUILD_EDITORS) |
216 | {"DEV MODE" , NULL, &DevModeOption, "The game menu is disabled in dev mode." }, |
217 | #endif |
218 | {"VSYNC" , NULL, &VSyncOption, "VSYNC needs restart!" }, |
219 | {"FULLSCREEN" , NULL, &FullscreenOption}, |
220 | {"VOLUME" , NULL, &VolumeOption}, |
221 | {"SETUP GAMEPAD" , showGamepadMenu}, |
222 | {"" }, |
223 | {"BACK" , showMainMenu, .back = true}, |
224 | }; |
225 | |
226 | static void showOptionsMenu(void* data, s32 pos); |
227 | static void gameMenuHandler(void* data, s32 pos) |
228 | { |
229 | StudioMainMenu* main = data; |
230 | tic_mem* tic = main->tic; |
231 | tic_core_script_config(tic)->callback.menu(tic, pos, NULL); |
232 | resumeGame(main->studio); |
233 | } |
234 | |
235 | static void freeItems(StudioMainMenu* ) |
236 | { |
237 | if(menu && menu->items) |
238 | { |
239 | for(MenuItem *it = menu->items, *end = it + menu->count; it != end; ++it) |
240 | free((void*)it->label); |
241 | |
242 | free(menu->items); |
243 | menu->count = 0; |
244 | menu->items = NULL; |
245 | } |
246 | } |
247 | |
248 | void studio_mainmenu_free(StudioMainMenu* ) |
249 | { |
250 | freeItems(menu); |
251 | FREE(menu); |
252 | } |
253 | |
254 | static void (StudioMainMenu* main) |
255 | { |
256 | tic_mem* tic = main->tic; |
257 | |
258 | freeItems(main); |
259 | |
260 | char* = tic_tool_metatag(tic->cart.code.data, "menu" , tic_core_script_config(tic)->singleComment); |
261 | |
262 | if(menu) SCOPE(free(menu)) |
263 | { |
264 | MenuItem *items = NULL; |
265 | s32 count = 0; |
266 | |
267 | char* label = strtok(menu, " " ); |
268 | while(label) |
269 | { |
270 | items = realloc(items, sizeof(MenuItem) * ++count); |
271 | items[count - 1] = (MenuItem){strdup(label), gameMenuHandler}; |
272 | |
273 | label = strtok(NULL, " " ); |
274 | } |
275 | |
276 | count += 2; |
277 | items = realloc(items, sizeof(MenuItem) * count); |
278 | items[count - 2] = (MenuItem){strdup("" )}; |
279 | items[count - 1] = (MenuItem){strdup("BACK" ), showMainMenu, .back = true}; |
280 | |
281 | main->items = items; |
282 | main->count = count; |
283 | } |
284 | } |
285 | |
286 | static void (void* data, s32 pos) |
287 | { |
288 | StudioMainMenu* main = data; |
289 | studio_menu_init(main->menu, main->items, main->count, 0, 0, showMainMenu, main); |
290 | } |
291 | |
292 | static inline s32 mainMenuStart(StudioMainMenu* ) |
293 | { |
294 | return menu->count ? 0 : 1; |
295 | } |
296 | |
297 | static void onResumeGame(void* data, s32 pos) |
298 | { |
299 | StudioMainMenu* main = data; |
300 | resumeGame(main->studio); |
301 | } |
302 | |
303 | static void onResetGame(void* data, s32 pos) |
304 | { |
305 | StudioMainMenu* main = data; |
306 | tic_api_reset(main->tic); |
307 | setStudioMode(main->studio, TIC_RUN_MODE); |
308 | } |
309 | |
310 | static void onExitStudio(void* data, s32 pos) |
311 | { |
312 | StudioMainMenu* main = data; |
313 | exitStudio(main->studio); |
314 | } |
315 | |
316 | static void onExitGame(void* data, s32 pos) |
317 | { |
318 | StudioMainMenu* main = data; |
319 | exitGame(main->studio); |
320 | } |
321 | |
322 | static const MenuItem MainMenu[] = |
323 | { |
324 | {"GAME MENU" , showGameMenu}, |
325 | {"RESUME GAME" , onResumeGame}, |
326 | {"RESET GAME" , onResetGame}, |
327 | #if defined(BUILD_EDITORS) |
328 | {"CLOSE GAME" , onExitGame}, |
329 | #endif |
330 | {"OPTIONS" , showOptionsMenu}, |
331 | {"" }, |
332 | {"QUIT TIC-80" , onExitStudio}, |
333 | }; |
334 | |
335 | static void showMainMenu(void* data, s32 pos) |
336 | { |
337 | StudioMainMenu* main = data; |
338 | initGameMenu(main); |
339 | |
340 | s32 start = mainMenuStart(main); |
341 | |
342 | studio_menu_init(main->menu, MainMenu + start, COUNT_OF(MainMenu) - start, 0, 0, onResumeGame, main); |
343 | } |
344 | |
345 | static void (void* data, s32 pos) |
346 | { |
347 | StudioMainMenu* main = data; |
348 | |
349 | studio_menu_init(main->menu, OptionMenu, |
350 | COUNT_OF(OptionMenu), pos, COUNT_OF(MainMenu) - 3 - mainMenuStart(main), showMainMenu, main); |
351 | } |
352 | |
353 | static void (void* data, s32 pos) |
354 | { |
355 | showOptionsMenuPos(data, COUNT_OF(OptionMenu) - 4); |
356 | } |
357 | |
358 | static void (void* data, s32 pos) |
359 | { |
360 | StudioMainMenu* main = data; |
361 | |
362 | main->options->mapping = main->gamepads.mapping; |
363 | showOptionsMenuPos(data, COUNT_OF(OptionMenu) - 3); |
364 | } |
365 | |
366 | static void resetGamepadMenu(void* data, s32 pos); |
367 | |
368 | static char MappingItems[TIC_BUTTONS][sizeof "RIGHT - RIGHT" ]; |
369 | |
370 | static const char* const ButtonLabels[] = |
371 | { |
372 | "UP" , |
373 | "DOWN" , |
374 | "LEFT" , |
375 | "RIGHT" , |
376 | "A" , |
377 | "B" , |
378 | "X" , |
379 | "Y" , |
380 | }; |
381 | |
382 | enum{KeyMappingStart = 2}; |
383 | |
384 | static void assignMapping(void* data, s32 pos) |
385 | { |
386 | StudioMainMenu* main = data; |
387 | |
388 | main->gamepads.key = pos - KeyMappingStart; |
389 | |
390 | static const char Fmt[] = "to assign to (%s) button..." ; |
391 | static char str[sizeof Fmt + STRLEN("RIGHT" )]; |
392 | |
393 | static const MenuItem [] = |
394 | { |
395 | {"Please, press a key you want" }, |
396 | {str}, |
397 | }; |
398 | |
399 | sprintf(str, Fmt, ButtonLabels[main->gamepads.key]); |
400 | |
401 | studio_menu_init(main->menu, AssignKeyMenu, COUNT_OF(AssignKeyMenu), 1, 0, NULL, main); |
402 | } |
403 | |
404 | static void initGamepadButtons(StudioMainMenu* ) |
405 | { |
406 | static const char* const KeysList[] = |
407 | { |
408 | "..." , |
409 | "A" , "B" , "C" , "D" , "E" , "F" , "G" , "H" , |
410 | "I" , "J" , "K" , "L" , "M" , "N" , "O" , "P" , |
411 | "Q" , "R" , "S" , "T" , "U" , "V" , "W" , "X" , |
412 | "Y" , "Z" , "0" , "1" , "2" , "3" , "4" , "5" , |
413 | "6" , "7" , "8" , "9" , "-" , "=" , "[" , "]" , |
414 | "\\" , ";" , "'" , "`" , "," , "." , "/" , "SPCE" , |
415 | "TAB" , "RET" , "BACKS" ,"DEL" , "INS" , "PGUP" , "PGDN" , "HOME" , |
416 | "END" , "UP" , "DOWN" , "LEFT" , "RIGHT" ,"CAPS" , "CTRL" , "SHIFT" , |
417 | "ALT" , "ESC" , "F1" , "F2" , "F3" , "F4" , "F5" , "F6" , |
418 | "F7" , "F8" , "F9" , "F10" , "F11" , "F12" , |
419 | }; |
420 | |
421 | for(s32 i = 0, index = menu->gamepads.index * TIC_BUTTONS; i != TIC_BUTTONS; ++i) |
422 | sprintf(MappingItems[i], "%-5s - %-5s" , ButtonLabels[i], KeysList[menu->gamepads.mapping.data[index++]]); |
423 | } |
424 | |
425 | static s32 optionGamepadGet(void* data) |
426 | { |
427 | StudioMainMenu* main = data; |
428 | return main->gamepads.index; |
429 | } |
430 | |
431 | static void optionGamepadSet(void* data, s32 pos) |
432 | { |
433 | StudioMainMenu* main = data; |
434 | main->gamepads.index = pos; |
435 | initGamepadButtons(main); |
436 | } |
437 | |
438 | static MenuOption GamepadOption = |
439 | { |
440 | OPTION_VALUES({"1" , "2" , "3" , "4" }), |
441 | optionGamepadGet, |
442 | optionGamepadSet, |
443 | }; |
444 | |
445 | static void (void* data, s32 pos) |
446 | { |
447 | StudioMainMenu* main = data; |
448 | ZEROMEM(main->gamepads.mapping); |
449 | initGamepadMenu(main); |
450 | } |
451 | |
452 | static void (StudioMainMenu* main) |
453 | { |
454 | static const MenuItem [] = |
455 | { |
456 | {"GAMEPAD" , NULL, &GamepadOption}, |
457 | {"" }, |
458 | |
459 | {MappingItems[0], assignMapping}, |
460 | {MappingItems[1], assignMapping}, |
461 | {MappingItems[2], assignMapping}, |
462 | {MappingItems[3], assignMapping}, |
463 | {MappingItems[4], assignMapping}, |
464 | {MappingItems[5], assignMapping}, |
465 | {MappingItems[6], assignMapping}, |
466 | {MappingItems[7], assignMapping}, |
467 | |
468 | {"" }, |
469 | {"SAVE MAPPING" , saveGamepadMenu}, |
470 | {"CLEAR MAPPING" , clearGamepadMenu}, |
471 | {"RESET TO DEFAULTS" , resetGamepadMenu}, |
472 | {"BACK" , showOptionsMenu, .back = true}, |
473 | }; |
474 | |
475 | initGamepadButtons(main); |
476 | |
477 | studio_menu_init(main->menu, GamepadMenu, COUNT_OF(GamepadMenu), |
478 | main->gamepads.key < 0 ? KeyMappingStart : main->gamepads.key + KeyMappingStart, |
479 | COUNT_OF(OptionMenu) - 3, showOptionsMenu, main); |
480 | |
481 | main->gamepads.key = -1; |
482 | } |
483 | |
484 | static void (void* data, s32 pos) |
485 | { |
486 | StudioMainMenu* main = data; |
487 | main->gamepads.index = 0; |
488 | ZEROMEM(main->gamepads.mapping); |
489 | tic_sys_default_mapping(&main->gamepads.mapping); |
490 | initGamepadMenu(main); |
491 | } |
492 | |