diff --git a/src/linux/port.c b/src/linux/port.c index 355fb0a..381a851 100644 --- a/src/linux/port.c +++ b/src/linux/port.c @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -97,6 +98,62 @@ typedef struct { } platform_data; +/* Directories */ + +static 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 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; +} + +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; +} + + /* Input */ static const int sdl_action_keycodes[Action_Max] = { @@ -175,13 +232,6 @@ static nese_Action process_events(nes* sys) { return action; } -nese_Action nese_update_input(void* plat_data, nes_Input* input) { - platform_data* plat = (platform_data*)plat_data; - nese_Action action = process_events(plat->sys); - if (NULL != input) *input = plat->sys->core.memory.input; - return action; -} - /* * Time / Video - Should be maximally reusable across platforms @@ -385,6 +435,59 @@ int nese_get_audio_frequency(void*) { // TODO: Audio functions +/* Menus */ + +nese_Action nese_update_input(void* plat_data, nes_Input* input) { + time_sleep_until(time_now() + (NS_PER_S / 60)); + platform_data* plat = (platform_data*)plat_data; + nese_Action action = process_events(plat->sys); + if (NULL != input) *input = plat->sys->core.memory.input; + return action; +} + +static nese_Action run_rom_menu(platform_data* plat, Menu_State* state, + char* filename, int sz_filename) { + nese_Action action = Action_Cancel; + + DIR* dir = opendir("rom"); + + if (NULL == dir) { + modal_popup(plat, "No ROMS found!\nPress any key to exit", + color_error); + + } else { + File_List files = {0}; + make_file_list(dir, &files); + closedir(dir); + + Menu_State menu = {0}; + if (NULL != state) menu = *state; + + // Add 4 to skip past "rom/" + int current = find_file(&files, filename + 4); + if (current >= 0) menu.cursor = current; + + action = run_menu(plat, &menu, &files, 20); + + if (Action_OK == action) { + if ( menu.cursor >= 0 && + menu.cursor < files.count) { + snprintf(filename, sz_filename - 1, + "%s/%s", "rom", files.files[menu.cursor]); + } else { + action = Action_Cancel; + } + } + + free_file_list(&files); + + if (NULL != state) *state = menu; + } + + return action; +} + + /* Platform Data */ static int plat_init(platform_data* plat) { @@ -414,6 +517,7 @@ static nes sys = {0}; int main(int argc, char* argv[]) { int status = 0; + char filename[1024] = {0}; // This should be tiny enough to keep on the stack. platform_data plat = { @@ -425,22 +529,27 @@ int main(int argc, char* argv[]) { } if (0 == status) { - if (argc <= 1) { - modal_popup(&plat, "No ROM file provided.", color_error); - status = -1; + if (1 < argc) { + strncpy(filename, argv[1], sizeof(filename) - 1); + filename[sizeof(filename) - 1] = '\0'; + } - } else { - status = load_cart(&plat.cart, argv[1], &sys); - if (0 != status) { - char message[128]; - snprintf(message, sizeof(message), "Failed to load %s", argv[1]); - modal_popup(&plat, message, color_error); + while (NULL == sys.cart_header && 0 == status) { + if (filename[0]) { + status = load_cart(&plat.cart, filename, &sys); + if (0 != status) { + char message[1039]; + snprintf(message, sizeof(message), + "Failed to load %s", filename); + modal_popup(&plat, message, color_error); + } } - } - if (NULL == sys.cart_header) { - // TODO: ROM selection menu - status = -1; + if (NULL == sys.cart_header) { + nese_Action action = run_rom_menu(&plat, NULL, + filename, sizeof(filename)); + status = (Action_OK == action) ? 0 : -1; + } } // This shall invoke menus as needed diff --git a/src/menu.c b/src/menu.c index 5767cdc..a187cbe 100644 --- a/src/menu.c +++ b/src/menu.c @@ -2,6 +2,7 @@ #include "nes.h" #include "action.h" #include "port.h" +#include "draw.h" static nese_Action wait_for_input(void* plat, uint8_t* new_buttons) { @@ -22,7 +23,7 @@ static nese_Action wait_for_input(void* plat, uint8_t* new_buttons) { } -int modal_popup(void* plat, const char* message, uint32_t color) { +nese_Action modal_popup(void* plat, const char* message, uint32_t color) { int w = 0; int h = 0; @@ -45,3 +46,119 @@ int modal_popup(void* plat, const char* message, uint32_t color) { return action; } + +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(void* plat, const Menu_State* menu, + const File_List* files, int x) { + nese_draw_begin(plat); + + 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]); + nese_draw_text( + plat, + ( (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) { + nese_draw_text(plat, ">", x - 10, y, color_white); + } + } + + nese_draw_finish(plat); +} + +int run_menu(void* plat, Menu_State* state, const File_List* files, int x) { + 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; + } + } + + nese_Action action = Action_OK; + while (Action_OK == action) { + // 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(plat, &menu, files, x); + + uint8_t buttons = 0; + action = wait_for_input(plat, &buttons); + + if (Action_Menu == action) { + action = Action_Cancel; + + } else if (Action_OK == action) { + if (buttons & (1 << Button_B)) { + action = Action_Cancel; + break; + + } 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; + } + } + + } else if (Action_Quit != action && Action_Cancel != action) { + // Ignore anything that isn't "Menu", "Cancel" or "Quit" + action = Action_OK; + } + } + + if (NULL != state) *state = menu; + + return action; +} diff --git a/src/menu.h b/src/menu.h index d2be6d7..6972394 100644 --- a/src/menu.h +++ b/src/menu.h @@ -2,6 +2,7 @@ #define NESE_MENU_H_ #include "cartinfo.h" +#include "action.h" typedef struct { @@ -9,12 +10,14 @@ typedef struct { int top; } Menu_State; +typedef struct { + int count; + char** files; +} File_List; -int run_rom_menu(void*, Menu_State*, const Cart_Info*, char* file, int sz_file); - -int run_game_menu(void*, Menu_State*); +nese_Action run_menu(void*, Menu_State* state, const File_List* files, int x); -int modal_popup(void*, const char* message, uint32_t color); +nese_Action modal_popup(void*, const char* message, uint32_t color); #endif // NESE_MENU_H_