| 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 |  |