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
28typedef struct
29{
30 tic_mapping mapping;
31 s32 index;
32 s32 key;
33} Gamepads;
34
35struct 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
52static void showMainMenu(void* data, s32 pos);
53
54StudioMainMenu* studio_mainmenu_init(Menu *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
72static void initGamepadMenu(StudioMainMenu* menu);
73
74bool 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
91static s32 optionFullscreenGet(void* data)
92{
93 return tic_sys_fullscreen_get() ? 1 : 0;
94}
95
96static void optionFullscreenSet(void* data, s32 pos)
97{
98 StudioMainMenu* main = data;
99 tic_sys_fullscreen_set(main->options->fullscreen = (pos == 1));
100}
101
102static const char OffValue[] = "OFF";
103static const char OnValue[] = "ON";
104
105static MenuOption FullscreenOption =
106{
107 OPTION_VALUES({OffValue, OnValue}),
108 optionFullscreenGet,
109 optionFullscreenSet,
110};
111
112#if defined(CRT_SHADER_SUPPORT)
113static s32 optionCrtMonitorGet(void* data)
114{
115 StudioMainMenu* main = data;
116 return main->options->crt ? 1 : 0;
117}
118
119static void optionCrtMonitorSet(void* data, s32 pos)
120{
121 StudioMainMenu* main = data;
122 main->options->crt = pos == 1;
123}
124
125static MenuOption CrtMonitorOption =
126{
127 OPTION_VALUES({OffValue, OnValue}),
128 optionCrtMonitorGet,
129 optionCrtMonitorSet,
130};
131
132#endif
133
134static s32 optionVSyncGet(void* data)
135{
136 StudioMainMenu* main = data;
137 return main->options->vsync ? 1 : 0;
138}
139
140static void optionVSyncSet(void* data, s32 pos)
141{
142 StudioMainMenu* main = data;
143 main->options->vsync = pos == 1;
144}
145
146static MenuOption VSyncOption =
147{
148 OPTION_VALUES({OffValue, OnValue}),
149 optionVSyncGet,
150 optionVSyncSet,
151};
152
153static s32 optionVolumeGet(void* data)
154{
155 StudioMainMenu* main = data;
156 return main->options->volume;
157}
158
159static void optionVolumeSet(void* data, s32 pos)
160{
161 StudioMainMenu* main = data;
162 main->options->volume = pos;
163}
164
165static 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
180static s32 optionDevModeGet(void* data)
181{
182 StudioMainMenu* main = data;
183 return main->options->devmode ? 1 : 0;
184}
185
186static void optionDevModeSet(void* data, s32 pos)
187{
188 StudioMainMenu* main = data;
189 main->options->devmode = pos == 1;
190}
191
192static MenuOption DevModeOption =
193{
194 OPTION_VALUES({OffValue, OnValue}),
195 optionDevModeGet,
196 optionDevModeSet,
197};
198
199#endif
200
201static void showGamepadMenu(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
210static const MenuItem OptionMenu[] =
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
226static void showOptionsMenu(void* data, s32 pos);
227static 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
235static void freeItems(StudioMainMenu* menu)
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
248void studio_mainmenu_free(StudioMainMenu* menu)
249{
250 freeItems(menu);
251 FREE(menu);
252}
253
254static void initGameMenu(StudioMainMenu* main)
255{
256 tic_mem* tic = main->tic;
257
258 freeItems(main);
259
260 char* menu = 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
286static void showGameMenu(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
292static inline s32 mainMenuStart(StudioMainMenu* menu)
293{
294 return menu->count ? 0 : 1;
295}
296
297static void onResumeGame(void* data, s32 pos)
298{
299 StudioMainMenu* main = data;
300 resumeGame(main->studio);
301}
302
303static 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
310static void onExitStudio(void* data, s32 pos)
311{
312 StudioMainMenu* main = data;
313 exitStudio(main->studio);
314}
315
316static void onExitGame(void* data, s32 pos)
317{
318 StudioMainMenu* main = data;
319 exitGame(main->studio);
320}
321
322static 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
335static 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
345static void showOptionsMenuPos(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
353static void showOptionsMenu(void* data, s32 pos)
354{
355 showOptionsMenuPos(data, COUNT_OF(OptionMenu) - 4);
356}
357
358static void saveGamepadMenu(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
366static void resetGamepadMenu(void* data, s32 pos);
367
368static char MappingItems[TIC_BUTTONS][sizeof "RIGHT - RIGHT"];
369
370static const char* const ButtonLabels[] =
371{
372 "UP",
373 "DOWN",
374 "LEFT",
375 "RIGHT",
376 "A",
377 "B",
378 "X",
379 "Y",
380};
381
382enum{KeyMappingStart = 2};
383
384static 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 AssignKeyMenu[] =
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
404static void initGamepadButtons(StudioMainMenu* menu)
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
425static s32 optionGamepadGet(void* data)
426{
427 StudioMainMenu* main = data;
428 return main->gamepads.index;
429}
430
431static void optionGamepadSet(void* data, s32 pos)
432{
433 StudioMainMenu* main = data;
434 main->gamepads.index = pos;
435 initGamepadButtons(main);
436}
437
438static MenuOption GamepadOption =
439{
440 OPTION_VALUES({"1", "2", "3", "4"}),
441 optionGamepadGet,
442 optionGamepadSet,
443};
444
445static void clearGamepadMenu(void* data, s32 pos)
446{
447 StudioMainMenu* main = data;
448 ZEROMEM(main->gamepads.mapping);
449 initGamepadMenu(main);
450}
451
452static void initGamepadMenu(StudioMainMenu* main)
453{
454 static const MenuItem GamepadMenu[] =
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
484static void resetGamepadMenu(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