#include #include #include #include "menu.h" #include "file.h" #include "timer.h" static int get_input(nese_Components* comp, int *last) { int status = nes_input_update(comp->reader, comp); int new_buttons = comp->sys->input.controllers[0].buttons; if (0 == status) { status = (~*last & new_buttons); } else { status = (status << 8); } *last = new_buttons; return status; } static int wait_for_input(nese_Components* comp) { int buttons = comp->sys->input.controllers[0].buttons; int status = 0; for ( ; 0 == status; status = get_input(comp, &buttons) ) { time_sleep(US_PER_S / 60); } return status; } static int wait_for_input_quiet(nese_Components* comp) { int status = 0; while ( input_Result_Quit != status && comp->sys->input.controllers[0].buttons) { time_sleep(US_PER_S / 60); status = nes_input_update(comp->reader, comp); } return ( input_Result_Quit == status ? input_Result_Quit : 0); } static int count_files(DIR* dir) { int count = 0; struct dirent* de = NULL; while (NULL != (de = readdir(dir))) { if ('.' != de->d_name[0]) ++count; } rewinddir(dir); return count; } typedef struct { int count; char** files; } file_list; int cmp_files(const void* _a, const void* _b) { const char* a = *(const char**)_a; const char* b = *(const char**)_b; int diff = 0; for (char ca = 1, cb = 1; ca && cb && 0 == diff; ++a, ++b) { // Cut extensions; replace underscore with space ca = (*a == '_' ? ' ' : (*a == '.' ? '\0' : *a)); cb = (*b == '_' ? ' ' : (*b == '.' ? '\0' : *b)); diff = (ca - cb); } return diff; } static void make_file_list(DIR* dir, file_list* files) { files->count = count_files(dir); files->files = calloc(files->count, sizeof(char*)); struct dirent* de = NULL; int i_file = 0; while (NULL != (de = readdir(dir))) { if ('.' != de->d_name[0]) { files->files[i_file] = strdup(de->d_name); ++i_file; } } qsort(files->files, files->count, sizeof(char*), cmp_files); } static void free_file_list(file_list* files) { for (int i = 0; i < files->count; ++i) { free(files->files[i]); } free(files->files); } static int find_file(file_list* files, const char* filename) { int i = (files->count - 1); for ( ; i >= 0 && (0 != strcmp(files->files[i], filename)); --i); return i; } static void fix_filename(char* dst, int n, const char* src) { for ( int i = 0; i < n && *src && '.' != *src; ++i, ++dst, ++src) { *dst = ('_' == *src ? ' ' : *src); } *dst = '\0'; } static inline int n_visible(void) { return ((nes_ppu_render_h - 20) / 11); } static const uint32_t menu_colors[6] = { color_red, color_orange, color_yellow, color_green, color_blue, color_purple, }; static void show_menu(const menu_state* menu, int dim, int x, nes_Renderer* rend, const file_list* files) { nes_draw_last_frame(rend, dim); int bottom = menu->top + n_visible() - 1; int max = n_visible(); if (max > files->count) max = files->count; int y = (nes_ppu_render_h - (max * 11)) / 2; for ( int n = menu->top; n < files->count && n <= bottom; ++n, y += 11 ) { char filename[100]; fix_filename(filename, sizeof(filename) - 1, files->files[n]); nes_draw_text( rend, ( (menu->top == n && 0 != menu->top) || (bottom == n && files->count - 1 > bottom) ) ? "..." : filename, x, y, (menu->cursor == n) ? color_white : menu_colors[n % 6] ); if (menu->cursor == n) { nes_draw_text(rend, ">", x - 10, y, color_white); } } nes_draw_done(rend); } static int run_menu(menu_state* state, const file_list* files, int x, nese_Components* comp, const cart_info* cart) { menu_state menu = {0}; if (NULL != state) { menu = *state; if (menu.cursor < 0) { menu.cursor = 0; } else if (menu.cursor >= files->count) { menu.cursor = files->count - 1; } } int status = 0; while (0 == status) { // Scrolling (do this first to ensure menu is valid) const int visible = n_visible() - 1; menu.top = menu.cursor - (visible / 2); if (menu.top <= 0) { // We use <= so we don't readjust the top from 0 menu.top = 0; } else if (menu.top + visible >= files->count) { menu.top = (files->count - 1) - visible; } show_menu(&menu, NULL != cart->file, x, comp->rend, files); int buttons = wait_for_input(comp); int special = (buttons >> 8); if (input_Result_Quit == special) { status = special; } else if ( input_Result_Menu == special || (buttons & (1 << Button_B))) { status = input_Result_Cancel; } else if (buttons & ( (1 << Button_A) | (1 << Button_Start) )) { // Select break; } else if (buttons & (1 << Button_Up)) { if (menu.cursor > 0) --menu.cursor; } else if (buttons & ( (1 << Button_Down) | (1 << Button_Select) )) { if (menu.cursor < (files->count - 1)) { ++menu.cursor; } else if (buttons & (1 << Button_Select)) { // Wrap around on Select menu.cursor = 0; } } } if (NULL != state) *state = menu; return status; } char* run_main_menu(menu_state* state, nese_Components* comp, const cart_info* cart) { char* cart_filename = NULL; DIR* dir = opendir("rom"); if (NULL == dir) { modal_popup( "No ROMS found!\nPress any key to exit", comp, cart ); } else { file_list files = {0}; make_file_list(dir, &files); closedir(dir); menu_state menu = {0}; if (NULL != state) menu = *state; if (NULL != cart->filename) { // Add 4 to skip past "rom/" int current = find_file(&files, cart->filename + 4); if (current >= 0) menu.cursor = current; } // Don't let window refreshes interrupt us. int status = run_menu(&menu, &files, 20, comp, cart); if (input_Result_Quit == status) { cart_filename = (char*)-1; } else if (input_Result_Cancel == status) { cart_filename = NULL; } else if ( menu.cursor >= 0 && menu.cursor < files.count) { char filename[1024]; snprintf(filename, sizeof(filename) - 1, "%s/%s", "rom", files.files[menu.cursor]); cart_filename = strdup(filename); } free_file_list(&files); if (NULL != state) *state = menu; } return cart_filename; } int run_game_menu(menu_state* state, nese_Components* comp, const nese_State* nese) { char* items[] = { "Resume", "Save", "Restore", "Reset", "Select ROM", #ifndef STANDALONE (nese->flags & (1 << State_Bit_Fullscreen)) ? "Windowed" : "Fullscreen", #endif (nese->flags & (1 << State_Bit_Integer_Scale)) ? "Force Aspect" : "Integer Scaling", "Exit", }; static const int choices[] = { input_Result_OK, input_Result_Save, input_Result_Load, input_Result_Reset, input_Result_Menu, #ifndef STANDALONE input_Result_View, #endif input_Result_Scale, input_Result_Quit, }; const file_list options = { .files = items, .count = (sizeof(items) / sizeof(*items)), }; menu_state menu = {0}; if (NULL != state) menu = *state; int status = run_menu(&menu, &options, 100, comp, &nese->cart); if (input_Result_Menu == status) { status = input_Result_Cancel; } if (0 == status) status = choices[menu.cursor]; wait_for_input_quiet(comp); if (NULL != state) *state = menu; return status; } int modal_popup(const char* message, nese_Components* comp, const cart_info* cart) { int w = 0; int h = 0; nes_text_size(comp->rend, message, &w, &h); int x = ((int)nes_ppu_render_w - w) / 2; int y = ((int)nes_ppu_render_h - h) / 2; if (x < 5) x = 5; if (y < 5) y = 5; nes_draw_last_frame(comp->rend, NULL != cart->file); nes_draw_text(comp->rend, message, x, y, color_error); nes_draw_done(comp->rend); return wait_for_input(comp); }