|
- #include <stdlib.h>
- #include <string.h>
-
- #include <dirent.h>
-
- #include "menu.h"
- #include "file.h"
- #include "timer.h"
-
-
- static int get_input(nes_Input_Reader* reader,
- nes_input* input, int *last) {
- int status = nes_input_update(reader, input);
- int new_buttons = 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(nes_Input_Reader* reader,
- nes_input* input) {
- int buttons = input->controllers[0].buttons;
- int status = 0;
- for ( ;
- 0 == status;
- status = get_input(reader, input, &buttons) ) {
- time_sleep(US_PER_S / 60);
- }
- return status;
- }
-
- static int wait_for_input_quiet(nes_Input_Reader* reader,
- nes_input* input) {
- int status = 0;
- while ( input_Result_Quit != status &&
- input->controllers[0].buttons) {
- time_sleep(US_PER_S / 60);
- status = nes_input_update(reader, input);
- }
- 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, nes_Renderer* rend,
- nes_Input_Reader* input, nes* sys) {
- 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 != sys->cart.mapper,
- x, rend, files);
-
- int buttons = wait_for_input(input, &sys->input);
- int special = (buttons >> 8);
-
- if ( input_Result_Quit == special ||
- input_Result_Refresh == 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, nes_Renderer* rend,
- nes_Input_Reader* input, nes* sys,
- const char* cur_filename) {
- char* cart_filename = NULL;
-
- DIR* dir = opendir("rom");
-
- if (NULL == dir) {
- nes_draw_last_frame(rend, NULL != sys->cart.mapper);
- nes_draw_text(
- rend,
- "No ROMS found!\nPress any key to exit",
- 10, 21, color_error
- );
- nes_draw_done(rend);
- wait_for_input(input, &sys->input);
-
- } else {
- file_list files = {0};
- make_file_list(dir, &files);
- closedir(dir);
-
- menu_state menu = {0};
- if (NULL != state) menu = *state;
-
- if (NULL != cur_filename) {
- // Add 4 to skip past "rom/"
- int current = find_file(&files, cur_filename + 4);
- if (current >= 0) menu.cursor = current;
- }
-
- // Don't let window refreshes interrupt us.
- int status = input_Result_Refresh;
- while (input_Result_Refresh == status) {
- status = run_menu(&menu, &files, 20,
- rend, input, sys);
- }
-
- 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, nes_Renderer* rend,
- nes_Input_Reader* input, nes* sys) {
- static char* items[] = {
- "Resume",
- "Save",
- "Restore",
- "Reset",
- "Select ROM",
- "Toggle Fullscreen",
- "Exit",
- };
- static int choices[] = {
- input_Result_OK,
- input_Result_Save,
- input_Result_Load,
- input_Result_Reset,
- input_Result_Menu,
- input_Result_View,
- input_Result_Quit,
- };
- static 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,
- rend, input, sys);
-
- if (input_Result_Menu == status) {
- status = input_Result_Cancel;
- }
-
- if (0 == status) status = choices[menu.cursor];
-
- wait_for_input_quiet(input, &sys->input);
-
- if (NULL != state) *state = menu;
-
- return status;
- }
-
- int modal_popup(const char* message, nes_Renderer* rend,
- nes_Input_Reader* input, nes* sys) {
- int w = 0;
- int h = 0;
-
- nes_text_size(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(rend, NULL != sys->cart.mapper);
- nes_draw_text(rend, message, x, y, color_error);
- nes_draw_done(rend);
-
- return wait_for_input(input, &sys->input);
- }
|