From 3cfd1313c659e2f20b1bbc84f6084f0c7060ad87 Mon Sep 17 00:00:00 2001 From: Shaun Inman Date: Thu, 26 Jan 2023 20:42:28 -0500 Subject: [PATCH] first incomplete pass at options layout is done, need to polish up the frontend and emulator options and sort out the mess of input overrides --- skeleton/.system/res/assets@2x.png | Bin 4748 -> 5947 bytes src/common/api.c | 101 +- src/common/api.h | 16 + src/minarch/main.c | 1141 +++++++++++++++++++---- src/minarch/makefile | 6 +- src/minarch/overrides.h | 23 + src/minarch/overrides/fceumm.h | 28 + src/minarch/overrides/gambatte.h | 27 + src/minarch/overrides/gpsp.h | 24 + src/minarch/overrides/pcsx_rearmed.h | 22 + src/minarch/overrides/picodrive.h | 24 + src/minarch/overrides/pokemini.h | 23 + src/minarch/overrides/snes9x2005_plus.h | 20 + src/minui/main.c | 2 +- 14 files changed, 1278 insertions(+), 179 deletions(-) create mode 100644 src/minarch/overrides.h create mode 100644 src/minarch/overrides/fceumm.h create mode 100644 src/minarch/overrides/gambatte.h create mode 100644 src/minarch/overrides/gpsp.h create mode 100644 src/minarch/overrides/pcsx_rearmed.h create mode 100644 src/minarch/overrides/picodrive.h create mode 100644 src/minarch/overrides/pokemini.h create mode 100644 src/minarch/overrides/snes9x2005_plus.h diff --git a/skeleton/.system/res/assets@2x.png b/skeleton/.system/res/assets@2x.png index f75523f8b0c3a39ad686ecd7d5fdc3e5dd899cf9..cf950c154c331b24a61e51d875e37e99fe52cac2 100644 GIT binary patch literal 5947 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 aqXYJ~l35m-a4rA-#&uwZT9tPlBL4@H9ggn+ literal 4748 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 Rjq$w%urReXd1>UC@E=HJ^Fjas diff --git a/src/common/api.c b/src/common/api.c index 901097c..f62c4e4 100644 --- a/src/common/api.c +++ b/src/common/api.c @@ -126,6 +126,7 @@ static SDL_Rect asset_rects[] = { [ASSET_WHITE_PILL] = (SDL_Rect){SCALE4( 1, 1,30,30)}, [ASSET_BLACK_PILL] = (SDL_Rect){SCALE4(33, 1,30,30)}, [ASSET_DARK_GRAY_PILL] = (SDL_Rect){SCALE4(65, 1,30,30)}, + [ASSET_OPTION] = (SDL_Rect){SCALE4(97, 1,20,20)}, [ASSET_BUTTON] = (SDL_Rect){SCALE4( 1,33,20,20)}, [ASSET_PAGE_BG] = (SDL_Rect){SCALE4(64,33,15,15)}, [ASSET_STATE_BG] = (SDL_Rect){SCALE4(23,54, 8, 8)}, @@ -144,6 +145,9 @@ static SDL_Rect asset_rects[] = { [ASSET_BATTERY_FILL] = (SDL_Rect){SCALE4(81,33,12, 6)}, [ASSET_BATTERY_FILL_LOW]= (SDL_Rect){SCALE4( 1,55,12, 6)}, [ASSET_BATTERY_BOLT] = (SDL_Rect){SCALE4(81,41,12, 6)}, + + [ASSET_SCROLL_UP] = (SDL_Rect){SCALE4(97,23,24, 6)}, + [ASSET_SCROLL_DOWN] = (SDL_Rect){SCALE4(97,31,24, 6)}, }; static uint32_t asset_rgbs[ASSET_COLORS]; GFX_Fonts font; @@ -155,7 +159,7 @@ SDL_Surface* GFX_init(int mode) { SDL_ShowCursor(0); TTF_Init(); - gfx.vsync = 1; + gfx.vsync = VSYNC_LENIENT; gfx.mode = mode; // we're drawing to the (triple-buffered) framebuffer directly @@ -208,6 +212,7 @@ SDL_Surface* GFX_init(int mode) { asset_rgbs[ASSET_WHITE_PILL] = RGB_WHITE; asset_rgbs[ASSET_BLACK_PILL] = RGB_BLACK; asset_rgbs[ASSET_DARK_GRAY_PILL]= RGB_DARK_GRAY; + asset_rgbs[ASSET_OPTION] = RGB_DARK_GRAY; asset_rgbs[ASSET_BUTTON] = RGB_WHITE; asset_rgbs[ASSET_PAGE_BG] = RGB_WHITE; asset_rgbs[ASSET_STATE_BG] = RGB_WHITE; @@ -229,6 +234,9 @@ SDL_Surface* GFX_init(int mode) { return gfx.screen; } +void GFX_setMode(int mode) { + gfx.mode = mode; +} void GFX_quit(void) { TTF_CloseFont(font.large); TTF_CloseFont(font.medium); @@ -270,9 +278,10 @@ void GFX_startFrame(void) { void GFX_flip(SDL_Surface* screen) { static int ticks = 0; ticks += 1; - if (gfx.vsync) { + if (gfx.vsync!=VSYNC_OFF) { + // this limiting condition helps SuperFX chip games #define FRAME_BUDGET 17 // 60fps - if (frame_start==0 || SDL_GetTicks()-frame_start=GFX_BUFFER_COUNT) gfx.buffer -= GFX_BUFFER_COUNT; screen->pixels = gfx.map + (gfx.buffer * gfx.buffer_size); } +void GFX_sync(void) { + #define FRAME_BUDGET 17 // ~60fps + if (gfx.vsync!=VSYNC_OFF) { + // this limiting condition helps SuperFX chip games + if (gfx.vsync==VSYNC_STRICT || frame_start==0 || SDL_GetTicks()-frame_startMAX_TEXT_LINES) break; // TODO: bail + lines[count++] = tmp+1; + } + *h = count * leading; + + int mw = 0; + char line[256]; + for (int i=0; imw) mw = lw; + } + } + *w = mw; +} +void GFX_blitText(TTF_Font* font, char* str, int leading, SDL_Color color, SDL_Surface* dst, SDL_Rect* dst_rect) { + if (dst_rect==NULL) dst_rect = &(SDL_Rect){0,0,SCREEN_WIDTH,SCREEN_HEIGHT}; + + char* lines[MAX_TEXT_LINES]; + int count = 0; + + char* tmp; + lines[count++] = str; + while ((tmp=strchr(lines[count-1], '\n'))!=NULL) { + if (count+1>MAX_TEXT_LINES) break; // TODO: bail + lines[count++] = tmp+1; + } + int x = dst_rect->x; + int y = dst_rect->y; + + SDL_Surface* text; + char line[256]; + for (int i=0; iw-text->w)/2),y+(i*leading)}); + SDL_FreeSurface(text); + } + } +} + /////////////////////////////// // based on picoarch's audio diff --git a/src/common/api.h b/src/common/api.h index 1aa9f51..cdec57c 100644 --- a/src/common/api.h +++ b/src/common/api.h @@ -30,6 +30,7 @@ enum { ASSET_WHITE_PILL, ASSET_BLACK_PILL, ASSET_DARK_GRAY_PILL, + ASSET_OPTION, ASSET_BUTTON, ASSET_PAGE_BG, ASSET_STATE_BG, @@ -50,6 +51,9 @@ enum { ASSET_BATTERY_FILL, ASSET_BATTERY_FILL_LOW, ASSET_BATTERY_BOLT, + + ASSET_SCROLL_UP, + ASSET_SCROLL_DOWN, }; typedef struct GFX_Fonts { @@ -66,12 +70,20 @@ enum { }; SDL_Surface* GFX_init(int mode); +void GFX_setMode(int mode); void GFX_clear(SDL_Surface* screen); void GFX_clearAll(void); void GFX_startFrame(void); void GFX_flip(SDL_Surface* screen); +void GFX_sync(void); // call this to maintain 60fps when not calling GFX_flip() this frame void GFX_quit(void); +enum { + VSYNC_OFF = 0, + VSYNC_LENIENT, // default + VSYNC_STRICT, +}; + int GFX_getVsync(void); void GFX_setVsync(int vsync); @@ -90,6 +102,9 @@ void GFX_blitMessage(char* msg, SDL_Surface* dst, SDL_Rect* dst_rect); int GFX_blitHardwareGroup(SDL_Surface* dst, int show_setting); int GFX_blitButtonGroup(char** hints, SDL_Surface* dst, int align_right); +void GFX_sizeText(TTF_Font* font, char* str, int leading, int* w, int* h); +void GFX_blitText(TTF_Font* font, char* str, int leading, SDL_Color color, SDL_Surface* dst, SDL_Rect* dst_rect); + /////////////////////////////// typedef struct SND_Frame { @@ -104,6 +119,7 @@ void SND_quit(void); /////////////////////////////// enum { + BTN_ID_NONE = -1, BTN_ID_UP, BTN_ID_DOWN, BTN_ID_LEFT, diff --git a/src/minarch/main.c b/src/minarch/main.c index 9dc5bd9..197b8a6 100644 --- a/src/minarch/main.c +++ b/src/minarch/main.c @@ -20,6 +20,30 @@ #include "api.h" #include "scaler_neon.h" +/////////////////////////////////////// + +#include "overrides.h" +#include "overrides/fceumm.h" +#include "overrides/gambatte.h" +#include "overrides/gpsp.h" +#include "overrides/pcsx_rearmed.h" +#include "overrides/picodrive.h" +#include "overrides/pokemini.h" +#include "overrides/snes9x2005_plus.h" + +static CoreOverrides* overrides[] = { + &fceumm_overrides, + &gambatte_overrides, + &gpsp_overrides, + &pcsx_rearmed_overrides, + &picodrive_overrides, + &pokemini_overrides, + &snes9x2005_plus_overrides, + NULL, +}; + +/////////////////////////////////////// + static SDL_Surface* screen; static int quit; static int show_menu; @@ -115,6 +139,8 @@ static struct Core { const char name[128]; // eg. gambatte const char version[128]; // eg. Gambatte (v0.5.0-netlink 7e02df6) + CoreOverrides* overrides; + const char config_dir[MAX_PATH]; // eg. /mnt/sdcard/.userdata/rg35xx/GB-gambatte const char saves_dir[MAX_PATH]; // eg. /mnt/sdcard/Saves/GB const char bios_dir[MAX_PATH]; // eg. /mnt/sdcard/Bios/GB @@ -287,20 +313,6 @@ static void State_resume(void) { /////////////////////////////// -typedef struct OptionOverride { - char* core; - char* key; - char* value; -} OptionOverride; - -static OptionOverride option_overrides[] = { - {"gpsp", "gpsp_save_method", "libretro"}, - {"gambatte", "gambatte_gb_colorization", "internal"}, - {"gambatte", "gambatte_gb_internal_palette", "TWB64 - Pack 1"}, - {"gambatte", "gambatte_gb_palette_twb64_1", "TWB64 038 - Pokemon mini Ver."}, - {NULL,NULL,NULL}, -}; - typedef struct Option { char* key; char* name; // desc @@ -309,17 +321,22 @@ typedef struct Option { int default_value; int value; int count; + int visible; char** values; char** labels; } Option; - static struct { int count; int changed; Option* items; } options; -static int Option_getValueIndex(Option* item, const char* value) { +static Option* frontend_options = (Option[]){ + // TODO: + {NULL} +}; + +static int Option_getValueIndex(Option* item, const char* value) { if (!value) return 0; for (int i=0; icount; i++) { if (!strcmp(item->values[i], value)) return i; @@ -330,10 +347,13 @@ static void Option_setValue(Option* item, const char* value) { // TODO: store previous value? item->value = Option_getValueIndex(item, value); } + static void Options_init(const struct retro_core_option_definition *defs) { int count; for (count=0; defs[count].key; count++); + // TODO: add frontend options to this? so the can use the same override method? eg. minarch_* + options.count = count; if (count) { options.items = calloc(count, sizeof(Option)); @@ -357,12 +377,15 @@ static void Options_init(const struct retro_core_option_definition *defs) { strcpy(item->desc, def->info); } + item->visible = 1; + for (count=0; def->values[count].value; count++); item->count = count; - item->values = calloc(count, sizeof(char*)); - item->labels = calloc(count, sizeof(char*)); + item->values = calloc(count+1, sizeof(char*)); + item->labels = calloc(count+1, sizeof(char*)); + printf("%s (%s)\n", item->name, item->key); for (int j=0; jvalues[j].value; const char* label = def->values[j].label; @@ -379,23 +402,28 @@ static void Options_init(const struct retro_core_option_definition *defs) { else { item->labels[j] = item->values[j]; } + printf("\t%s\n", item->labels[j]); } const char* default_value = def->default_value; - for (int k=0; option_overrides[k].core; k++) { - OptionOverride* override = &option_overrides[k]; - if (!strcmp(override->key, item->key)) { - default_value = override->value; - break; + if (core.overrides && core.overrides->option_overrides) { + for (int k=0; core.overrides->option_overrides[k].key; k++) { + OptionOverride* override = &core.overrides->option_overrides[k]; + if (!strcmp(override->key, item->key)) { + default_value = override->value; + break; + } } } item->value = Option_getValueIndex(item, default_value); item->default_value = item->value; - // printf("SET %s to %s (%i)\n", item->key, default_value, item->value); fflush(stdout); + // printf("(%s:%i)\n", item->labels[item->value], item->value); + printf("%s %s\n",item->name, item->labels[item->value]); + // if (item->desc) printf("\t%s\n", item->desc); } } - // fflush(stdout); + fflush(stdout); } static void Options_vars(const struct retro_variable *vars) { int count; @@ -425,14 +453,15 @@ static void Options_vars(const struct retro_variable *vars) { tmp += 2; } - char* opt = tmp; + item->visible = 1; + char* opt = tmp; for (count=0; (tmp=strchr(tmp, '|')); tmp++, count++); count += 1; // last entry after final '|' item->count = count; - item->values = calloc(count, sizeof(char*)); - item->labels = calloc(count, sizeof(char*)); + item->values = calloc(count+1, sizeof(char*)); + item->labels = calloc(count+1, sizeof(char*)); tmp = opt; int j; @@ -448,11 +477,13 @@ static void Options_vars(const struct retro_variable *vars) { // no native default_value support for retro vars const char* default_value = NULL; - for (int k=0; option_overrides[k].core; k++) { - OptionOverride* override = &option_overrides[k]; - if (!strcmp(override->key, item->key)) { - default_value = override->value; - break; + if (core.overrides && core.overrides->option_overrides) { + for (int k=0; core.overrides->option_overrides[k].key; k++) { + OptionOverride* override = &core.overrides->option_overrides[k]; + if (!strcmp(override->key, item->key)) { + default_value = override->value; + break; + } } } @@ -498,14 +529,19 @@ static Option* Options_getOption(const char* key) { } static char* Options_getOptionValue(const char* key) { Option* item = Options_getOption(key); - if (item) { - // printf("GET %s (%i)\n", item->key, item->value); fflush(stdout); - return item->values[item->value]; - } + if (item) return item->values[item->value]; else LOG_warn("unknown option %s \n", key); return NULL; } -static void Options_setOption(const char* key, const char* value) { +static void Options_setOptionRawValue(const char* key, int value) { + Option* item = Options_getOption(key); + if (item) { + item->value = value; + options.changed = 1; + } + else printf("unknown option %s \n", key); fflush(stdout); +} +static void Options_setOptionValue(const char* key, const char* value) { Option* item = Options_getOption(key); if (item) { Option_setValue(item, value); @@ -513,6 +549,11 @@ static void Options_setOption(const char* key, const char* value) { } else printf("unknown option %s \n", key); fflush(stdout); } +static void Options_setOptionVisibility(const char* key, int visible) { + Option* item = Options_getOption(key); + if (item) item->visible = visible; + else printf("unknown option %s \n", key); fflush(stdout); +} /////////////////////////////// @@ -520,70 +561,44 @@ static void Menu_beforeSleep(void); static void Menu_afterSleep(void); #define RETRO_BUTTON_COUNT 14 -typedef struct InputOverride { - char* core; - int key; - int value; -} InputOverride; - -// TODO: where do I apply these? -// TODO: when loading options from cfg file? -static InputOverride input_overrides[] = { - {"gambatte", RETRO_DEVICE_ID_JOYPAD_Y, BTN_NONE}, // disable turbo - {"gambatte", RETRO_DEVICE_ID_JOYPAD_X, BTN_NONE}, // disable turbo - {"gambatte", RETRO_DEVICE_ID_JOYPAD_L, BTN_NONE}, // disable palette swapping - {"gambatte", RETRO_DEVICE_ID_JOYPAD_R, BTN_NONE}, // disable palette swapping - - {"gpsp", RETRO_DEVICE_ID_JOYPAD_Y, BTN_NONE}, // disable turbo - {"gpsp", RETRO_DEVICE_ID_JOYPAD_X, BTN_NONE}, // disable turbo - - {"fceumm", RETRO_DEVICE_ID_JOYPAD_Y, BTN_NONE}, // disable turbo - {"fceumm", RETRO_DEVICE_ID_JOYPAD_X, BTN_NONE}, // disable turbo - {"fceumm", RETRO_DEVICE_ID_JOYPAD_L, BTN_NONE}, // disable extras - {"fceumm", RETRO_DEVICE_ID_JOYPAD_R, BTN_NONE}, // disable extras - {"fceumm", RETRO_DEVICE_ID_JOYPAD_L2, BTN_NONE}, // disable extras - {"fceumm", RETRO_DEVICE_ID_JOYPAD_R2, BTN_NONE}, // disable extras - - {"pokemini", RETRO_DEVICE_ID_JOYPAD_X, BTN_NONE}, // disable turbo - - {NULL,0,0}, +static ButtonMapping default_button_mapping[] = { + {"UP", RETRO_DEVICE_ID_JOYPAD_UP, BTN_ID_UP}, + {"DOWN", RETRO_DEVICE_ID_JOYPAD_DOWN, BTN_ID_DOWN}, + {"LEFT", RETRO_DEVICE_ID_JOYPAD_LEFT, BTN_ID_LEFT}, + {"RIGHT", RETRO_DEVICE_ID_JOYPAD_RIGHT, BTN_ID_RIGHT}, + {"A BUTTON", RETRO_DEVICE_ID_JOYPAD_A, BTN_ID_A}, + {"B BUTTON", RETRO_DEVICE_ID_JOYPAD_B, BTN_ID_B}, + {"X BUTTON", RETRO_DEVICE_ID_JOYPAD_X, BTN_ID_X}, + {"Y BUTTON", RETRO_DEVICE_ID_JOYPAD_Y, BTN_ID_Y}, + {"START", RETRO_DEVICE_ID_JOYPAD_START, BTN_ID_START}, + {"SELECT", RETRO_DEVICE_ID_JOYPAD_SELECT, BTN_ID_SELECT}, + {"L1 BUTTON", RETRO_DEVICE_ID_JOYPAD_L, BTN_ID_L1}, + {"R1 BUTTON", RETRO_DEVICE_ID_JOYPAD_R, BTN_ID_R1}, + {"L2 BUTTON", RETRO_DEVICE_ID_JOYPAD_L2, BTN_ID_L2}, + {"R2 BUTTON", RETRO_DEVICE_ID_JOYPAD_R2, BTN_ID_R2}, + {NULL,0,0} }; - -static const char* core_button_names[RETRO_BUTTON_COUNT]; // core-provided +static ButtonMapping* button_mapping; // either default_button_mapping or core.overrides->button_mapping static const char* device_button_names[RETRO_BUTTON_COUNT] = { - [RETRO_DEVICE_ID_JOYPAD_UP] = "UP", - [RETRO_DEVICE_ID_JOYPAD_DOWN] = "DOWN", - [RETRO_DEVICE_ID_JOYPAD_LEFT] = "LEFT", - [RETRO_DEVICE_ID_JOYPAD_RIGHT] = "RIGHT", - [RETRO_DEVICE_ID_JOYPAD_SELECT] = "SELECT", - [RETRO_DEVICE_ID_JOYPAD_START] = "START", - [RETRO_DEVICE_ID_JOYPAD_Y] = "Y", - [RETRO_DEVICE_ID_JOYPAD_X] = "X", - [RETRO_DEVICE_ID_JOYPAD_B] = "B", - [RETRO_DEVICE_ID_JOYPAD_A] = "A", - [RETRO_DEVICE_ID_JOYPAD_L] = "L1", - [RETRO_DEVICE_ID_JOYPAD_R] = "R1", - [RETRO_DEVICE_ID_JOYPAD_L2] = "L2", - [RETRO_DEVICE_ID_JOYPAD_R2] = "R2", -}; -static uint32_t button_map_default[RETRO_BUTTON_COUNT] = { - [RETRO_DEVICE_ID_JOYPAD_B] = BTN_B, - [RETRO_DEVICE_ID_JOYPAD_Y] = BTN_Y, - [RETRO_DEVICE_ID_JOYPAD_SELECT] = BTN_SELECT, - [RETRO_DEVICE_ID_JOYPAD_START] = BTN_START, - [RETRO_DEVICE_ID_JOYPAD_UP] = BTN_UP, - [RETRO_DEVICE_ID_JOYPAD_DOWN] = BTN_DOWN, - [RETRO_DEVICE_ID_JOYPAD_LEFT] = BTN_LEFT, - [RETRO_DEVICE_ID_JOYPAD_RIGHT] = BTN_RIGHT, - [RETRO_DEVICE_ID_JOYPAD_A] = BTN_A, - [RETRO_DEVICE_ID_JOYPAD_X] = BTN_X, - [RETRO_DEVICE_ID_JOYPAD_L] = BTN_L1, - [RETRO_DEVICE_ID_JOYPAD_R] = BTN_R1, - [RETRO_DEVICE_ID_JOYPAD_L2] = BTN_L2, - [RETRO_DEVICE_ID_JOYPAD_R2] = BTN_R2, + [BTN_ID_UP] = "UP", + [BTN_ID_DOWN] = "DOWN", + [BTN_ID_LEFT] = "LEFT", + [BTN_ID_RIGHT] = "RIGHT", + [BTN_ID_SELECT] = "SELECT", + [BTN_ID_START] = "START", + [BTN_ID_Y] = "Y", + [BTN_ID_X] = "X", + [BTN_ID_B] = "B", + [BTN_ID_A] = "A", + [BTN_ID_L1] = "L1", + [BTN_ID_R1] = "R1", + [BTN_ID_L2] = "L2", + [BTN_ID_R2] = "R2", }; +static const char* core_button_names[RETRO_BUTTON_COUNT]; static uint32_t buttons = 0; // RETRO_DEVICE_ID_JOYPAD_* buttons static int ignore_menu = 0; +static int show_debug = 0; static void input_poll_callback(void) { PAD_poll(); @@ -598,6 +613,9 @@ static void input_poll_callback(void) { if (PAD_isPressed(BTN_MENU) && (PAD_isPressed(BTN_VOL_UP) || PAD_isPressed(BTN_VOL_DN))) { ignore_menu = 1; } + if ((PAD_isPressed(BTN_L2) && PAD_justPressed(BTN_R2)) || PAD_isPressed(BTN_R2) && PAD_justPressed(BTN_L2)) { + show_debug = !show_debug; + } if (!ignore_menu && PAD_justReleased(BTN_MENU)) { show_menu = 1; @@ -606,10 +624,10 @@ static void input_poll_callback(void) { // TODO: support remapping buttons = 0; - for (int i=0; ibutton_mapping ? core.overrides->button_mapping : default_button_mapping; const struct retro_input_descriptor *vars = (const struct retro_input_descriptor *)data; if (vars) { - for (int i=0; vars[i].description; i++) { - const struct retro_input_descriptor* var = &vars[i]; - - // TODO: can I break or will these come in out of order? - if (var->port || var->device!=1 || var->id>=RETRO_BUTTON_COUNT) continue; - - core_button_names[var->id] = var->description; - // printf("%s: %s\n", var->description, device_button_names[var->id]); - } - // TODO: is this guaranteed to be called? puts("---------------------------------"); - - // apply overrides - for (int k=0; input_overrides[k].core; k++) { - InputOverride* override = &input_overrides[k]; - if (!strcmp(override->core, core.name)) { - button_map_default[override->key] = override->value; - } + + // identify buttons available in this core + int present[RETRO_BUTTON_COUNT]; + memset(&present, 0, RETRO_BUTTON_COUNT * sizeof(int)); + for (int i=0; vars[i].description; i++) { + const struct retro_input_descriptor* var = &vars[i]; + present[var->id] = 1; + core_button_names[var->id] = var->description; } - // TODO: with or without button_sort_order the sort order is broken - for (int i=0; i\n", default_button_mapping[i].name, (local==BTN_ID_NONE ? "NONE" : device_button_names[local])); } + + for (int i=0; button_mapping[i].name; i++) { + int retro = button_mapping[i].retro; + // null mappings that aren't available in this core + if (!present[retro]) { + button_mapping[i].name = NULL; + continue; + } + int local = button_mapping[i].local; + LOG_info("%s: <%s>\n", button_mapping[i].name, (local==BTN_ID_NONE ? "NONE" : device_button_names[local])); + } + puts("---------------------------------"); return false; @@ -800,17 +828,10 @@ static bool environment_callback(unsigned cmd, void *data) { // copied from pico } break; } - // TODO: not used by gambatte case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY: { /* 55 */ // puts("RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY"); - - const struct retro_core_option_display *display = - (const struct retro_core_option_display *)data; - - // TODO: set visibitility of option - // if (display) - // printf("visible: %i (%s)\n", display->visible, display->key); - // options_set_visible(display->key, display->visible); + const struct retro_core_option_display *display = (const struct retro_core_option_display *)data; + if (display) Options_setOptionVisibility(display->key, display->visible); break; } case RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION: { /* 57 */ @@ -864,13 +885,12 @@ static bool environment_callback(unsigned cmd, void *data) { // copied from pico // TODO: RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE 64 // TODO: RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK 69 // TODO: used by gambatte for L/R palette switching (seems like it needs to return true even if data is NULL to indicate support) - // TODO: these should be overridden to be disabled by default because ick case RETRO_ENVIRONMENT_SET_VARIABLE: { // puts("RETRO_ENVIRONMENT_SET_VARIABLE"); const struct retro_variable *var = (const struct retro_variable *)data; if (var && var->key) { - Options_setOption(var->key, var->value); + Options_setOptionValue(var->key, var->value); break; } @@ -1023,6 +1043,25 @@ static void scale1x(void* __restrict src, void* __restrict dst, uint32_t w, uint src_row += src_stride; } +} +static void scale1x_scanline(void* __restrict src, void* __restrict dst, uint32_t w, uint32_t h, uint32_t pitch, uint32_t dst_pitch) { + // pitch of src image not src buffer! + // eg. gb has a 160 pixel wide image but + // gambatte uses a 256 pixel wide buffer + // (only matters when using memcpy) + int src_pitch = w * SCREEN_BPP; + int src_stride = 2 * pitch / SCREEN_BPP; + int dst_stride = 2 * dst_pitch / SCREEN_BPP; + int cpy_pitch = MIN(src_pitch, dst_pitch); + + uint16_t* restrict src_row = (uint16_t*)src; + uint16_t* restrict dst_row = (uint16_t*)dst; + for (int y=0; y> 10) & 0x3E; + uint16_t g = (l1 >> 5) & 0x3F; + uint16_t b = (l1 << 1) & 0x3E; + uint16_t luma = (r * 218) + (g * 732) + (b * 74); + luma = (luma >> 10) + ((luma >> 9) & 1); // 0-63 + d = luma > 24; + } + + uint16_t s = *psrc16; + + while (dx < 0) { + *pdst16++ = d ? l1 : s; + dx += w; + + l2 = l1; + l1 = s; + d = 0; + } + + dx -= renderer.dst_w; + psrc16++; + } + } + + dst += dst_pitch; + dy += h; + + if (dy >= 0) { + dy -= renderer.dst_h; + src += pitch; + lines--; + } + row += 1; + } +} static SDL_Surface* scaler_surface; static void selectScaler(int width, int height, int pitch) { @@ -1486,26 +1617,36 @@ static void selectScaler(int width, int height, int pitch) { if (use_nearest) renderer.scaler = scaleNN_text; + // renderer.scaler = scaleNN_text_scanline; // renderer.scaler = scaleNN; // better for Tekken 3, Colin McRae Rally 2 else { sprintf(scaler_name, "%ix", scale); - switch (scale) { - // eggs-optimized scalers - case 4: renderer.scaler = scale4x_n16; break; - case 3: renderer.scaler = scale3x_n16; break; - case 2: renderer.scaler = scale2x_n16; break; - default: renderer.scaler = scale1x_n16; break; - - // my lesser scalers :sweat_smile: - // case 4: renderer.scaler = scale4x; break; - // case 3: renderer.scaler = scale3x; break; - // case 3: renderer.scaler = scale3x_dmg; break; - // case 3: renderer.scaler = scale3x_lcd; break; - // case 3: renderer.scaler = scale3x_scanline; break; - // case 2: renderer.scaler = scale2x; break; - // case 2: renderer.scaler = scale2x_lcd; break; - // case 2: renderer.scaler = scale2x_scanline; break; - // default: renderer.scaler = scale1x; break; + if (0) { + switch (scale) { + case 4: renderer.scaler = scale4x_scanline; break; + case 3: renderer.scaler = scale3x_scanline; break; + case 2: renderer.scaler = scale2x_scanline; break; + default: renderer.scaler = scale1x_scanline; break; + } + } + else { + switch (scale) { + case 4: renderer.scaler = scale4x_n16; break; + case 3: renderer.scaler = scale3x_n16; break; + case 2: renderer.scaler = scale2x_n16; break; + default: renderer.scaler = scale1x_n16; break; + + // my lesser scalers :sweat_smile: + // case 4: renderer.scaler = scale4x; break; + // case 3: renderer.scaler = scale3x; break; + // case 3: renderer.scaler = scale3x_dmg; break; + // case 3: renderer.scaler = scale3x_lcd; break; + // case 3: renderer.scaler = scale3x_scanline; break; + // case 2: renderer.scaler = scale2x; break; + // case 2: renderer.scaler = scale2x_lcd; break; + // case 2: renderer.scaler = scale2x_scanline; break; + // default: renderer.scaler = scale1x; break; + } } } @@ -1570,7 +1711,7 @@ static void video_refresh_callback(const void *data, unsigned width, unsigned he if (frame>=fps) frame -= fps; } - if (1) { + if (show_debug) { int x = 0; int y = SCREEN_HEIGHT - DIGIT_HEIGHT; @@ -1694,7 +1835,13 @@ void Core_open(const char* core_path, const char* tag_name) { sprintf((char*)core.version, "%s (%s)", info.library_name, info.library_version); strcpy((char*)core.tag, tag_name); - LOG_info("% %s (%s)\n", core.name, core.version, core.tag); + LOG_info("core: %s version: %s tag: %s\n", core.name, core.version, core.tag); + + for (int i=0; overrides[i]; i++) { + if (!strcmp(overrides[i]->core_name, core.name)) { + core.overrides = overrides[i]; + } + } sprintf((char*)core.config_dir, SDCARD_PATH "/.userdata/" PLATFORM "/%s-%s", core.tag, core.name); sprintf((char*)core.saves_dir, SDCARD_PATH "/Saves/%s", core.tag); @@ -1776,7 +1923,7 @@ enum { STATUS_QUIT = 30 }; -static struct Menu { +static struct { int initialized; SDL_Surface* overlay; char* items[MENU_ITEM_COUNT]; @@ -1786,7 +1933,7 @@ static struct Menu { [ITEM_CONT] = "Continue", [ITEM_SAVE] = "Save", [ITEM_LOAD] = "Load", - [ITEM_OPTS] = "Reset", + [ITEM_OPTS] = "Options", [ITEM_QUIT] = "Quit", } }; @@ -1842,6 +1989,658 @@ void Menu_beforeSleep(void) { void Menu_afterSleep(void) { unlink(AUTO_RESUME_PATH); } + +typedef struct MenuList MenuList; +typedef struct MenuItem MenuItem; +enum { + MENU_CALLBACK_NOP, + MENU_CALLBACK_EXIT, + MENU_CALLBACK_NEXT_ITEM, +}; +typedef int(*MenuList_callback_t)(MenuList* list, int i); +typedef struct MenuItem { + char* name; + char* desc; + char** values; + char* key; // optional, used by options + int value; + MenuList* submenu; + MenuList_callback_t on_confirm; + MenuList_callback_t on_change; +} MenuItem; + +enum { + MENU_LIST, // eg. save and main menu + MENU_VAR, // eg. frontend + MENU_FIXED, // eg. emulator + MENU_INPUT, // eg. renders like but MENU_VAR but handles input differently +}; +typedef struct MenuList { + int type; + int max_width; // cached on first draw + char* desc; + MenuItem* items; + MenuList_callback_t on_confirm; + MenuList_callback_t on_change; +} MenuList; + +void Menu_detail(MenuItem* item) { + // TODO: name +} + +#define OPTION_PADDING 8 +#define MAX_VISIBLE_OPTIONS 7 +int Menu_options(MenuList* list) { + MenuItem* items = list->items; + int type = list->type; + + int dirty = 1; + int show_options = 1; + + int count; + for (count=0; items[count].name; count++); + int selected = 0; + int start = 0; + int end = MIN(count,MAX_VISIBLE_OPTIONS); + int visible_rows = end; + + while (show_options) { + GFX_startFrame(); + uint32_t frame_start = SDL_GetTicks(); + + PAD_poll(); + + if (PAD_justRepeated(BTN_UP)) { + selected -= 1; + if (selected<0) { + selected = count - 1; + start = MAX(0,count - MAX_VISIBLE_OPTIONS); + end = count; + } + else if (selected=count) { + selected = 0; + start = 0; + end = visible_rows; + } + else if (selected>=end) { + start += 1; + end += 1; + } + dirty = 1; + } + else if (type!=MENU_INPUT) { + if (PAD_justPressed(BTN_LEFT)) { + MenuItem* item = &items[selected]; + if (item->value>0) item->value -= 1; + else { + int j; + for (j=0; item->values[j]; j++); + item->value = j - 1; + } + + if (item->on_change) item->on_change(list, selected); + else if (list->on_change) list->on_change(list, selected); + + dirty = 1; + } + else if (PAD_justPressed(BTN_RIGHT)) { + MenuItem* item = &items[selected]; + if (item->values[item->value+1]) item->value += 1; + else item->value = 0; + + if (item->on_change) item->on_change(list, selected); + else if (list->on_change) list->on_change(list, selected); + + dirty = 1; + } + } + + if (PAD_justPressed(BTN_B)) { + show_options = 0; + } + else if (PAD_justPressed(BTN_A)) { + MenuItem* item = &items[selected]; + int result = 0; + if (item->on_confirm) result = item->on_confirm(list, selected); // item-specific action, eg. Save for all games + else if (item->submenu) result = Menu_options(item->submenu); // drill down, eg. main options menu + else if (list->on_confirm) result = list->on_confirm(list, selected); // list-specific action, eg. show item detail view or input binding + if (result==MENU_CALLBACK_EXIT) show_options = 0; + else { + if (result==MENU_CALLBACK_NEXT_ITEM) { + // copied from PAD_justRepeated(BTN_DOWN) above + selected += 1; + if (selected>=count) { + selected = 0; + start = 0; + end = visible_rows; + } + else if (selected>=end) { + start += 1; + end += 1; + } + } + dirty = 1; + } + } + else if (type==MENU_INPUT) { + if (PAD_justPressed(BTN_X)) { + MenuItem* item = &items[selected]; + item->value = 0; + + if (item->on_change) item->on_change(list, selected); + else if (list->on_change) list->on_change(list, selected); + + // copied from PAD_justRepeated(BTN_DOWN) above + selected += 1; + if (selected>=count) { + selected = 0; + start = 0; + end = visible_rows; + } + else if (selected>=end) { + start += 1; + end += 1; + } + dirty = 1; + } + } + + if (dirty) { + dirty = 0; + + GFX_clear(screen); + GFX_blitHardwareGroup(screen, 0); + + char* desc = NULL; + SDL_Surface* text; + + if (type==MENU_LIST) { + int mw = list->max_width; + if (!mw) { + // get the width of the widest item + for (int i=0; iname, &w, NULL); + w += SCALE1(OPTION_PADDING*2); + if (w>mw) mw = w; + } + // cache the result + list->max_width = mw = MIN(mw, SCREEN_WIDTH - SCALE1(PADDING *2)); + } + + int ox = (SCREEN_WIDTH - mw) / 2; + int oy = SCALE1(PADDING + PILL_SIZE); + int selected_row = selected - start; + for (int i=start,j=0; iname, &w, NULL); + w += SCALE1(OPTION_PADDING*2); + + GFX_blitPill(ASSET_BUTTON, screen, &(SDL_Rect){ + ox, + oy+SCALE1(j*BUTTON_SIZE), + w, + SCALE1(BUTTON_SIZE) + }); + text_color = COLOR_BLACK; + + if (item->desc) desc = item->desc; + } + text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); + SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ + ox+SCALE1(OPTION_PADDING), + oy+SCALE1((j*BUTTON_SIZE)+1) + }); + SDL_FreeSurface(text); + } + } + else if (type==MENU_FIXED) { + // NOTE: no need to calculate max width + int mw = SCREEN_WIDTH - SCALE1(PADDING*2); + int lw,rw; + lw = rw = mw / 2; + int ox,oy; + ox = oy = SCALE1(PADDING); + oy += SCALE1(PILL_SIZE); + + int selected_row = selected - start; + for (int i=start,j=0; iname, &w, NULL); + w += SCALE1(OPTION_PADDING*2); + GFX_blitPill(ASSET_BUTTON, screen, &(SDL_Rect){ + ox, + oy+SCALE1(j*BUTTON_SIZE), + w, + SCALE1(BUTTON_SIZE) + }); + text_color = COLOR_BLACK; + + if (item->desc) desc = item->desc; + } + text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); + SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ + ox+SCALE1(OPTION_PADDING), + oy+SCALE1((j*BUTTON_SIZE)+1) + }); + SDL_FreeSurface(text); + + if (item->value>=0) { + text = TTF_RenderUTF8_Blended(font.tiny, item->values[item->value], COLOR_WHITE); // always white + SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ + ox + mw - text->w - SCALE1(OPTION_PADDING), + oy+SCALE1((j*BUTTON_SIZE)+3) + }); + SDL_FreeSurface(text); + } + } + } + else if (type==MENU_VAR || type==MENU_INPUT) { + int mw = list->max_width; + if (!mw) { + // get the width of the widest row + int mrw = 0; + for (int i=0; iname, &lw, NULL); + + // every value list in an input table is the same + // so only calculate rw for the first item... + if (!mrw || type!=MENU_INPUT) { + for (int j=0; item->values[j]; j++) { + TTF_SizeUTF8(font.tiny, item->values[j], &rw, NULL); + if (lw+rw>w) w = lw+rw; + if (rw>mrw) mrw = rw; + } + } + else { + w = lw + mrw; + } + w += SCALE1(OPTION_PADDING*4); + if (w>mw) mw = w; + } + fflush(stdout); + // cache the result + list->max_width = mw = MIN(mw, SCREEN_WIDTH - SCALE1(PADDING *2)); + } + + int ox = (SCREEN_WIDTH - mw) / 2; + int oy = SCALE1(PADDING + PILL_SIZE); + int selected_row = selected - start; + for (int i=start,j=0; iname, &w, NULL); + w += SCALE1(OPTION_PADDING*2); + GFX_blitPill(ASSET_BUTTON, screen, &(SDL_Rect){ + ox, + oy+SCALE1(j*BUTTON_SIZE), + w, + SCALE1(BUTTON_SIZE) + }); + text_color = COLOR_BLACK; + + if (item->desc) desc = item->desc; + } + text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); + SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ + ox+SCALE1(OPTION_PADDING), + oy+SCALE1((j*BUTTON_SIZE)+1) + }); + SDL_FreeSurface(text); + + if (item->value>=0) { + text = TTF_RenderUTF8_Blended(font.tiny, item->values[item->value], COLOR_WHITE); // always white + SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ + ox + mw - text->w - SCALE1(OPTION_PADDING), + oy+SCALE1((j*BUTTON_SIZE)+3) + }); + SDL_FreeSurface(text); + } + } + } + + if (count>MAX_VISIBLE_OPTIONS) { + #define SCROLL_WIDTH 24 + #define SCROLL_HEIGHT 4 + int ox = (SCREEN_WIDTH - SCALE1(SCROLL_WIDTH))/2; + int oy = SCALE1((PILL_SIZE - SCROLL_HEIGHT) / 2); + if (start>0) GFX_blitAsset(ASSET_SCROLL_UP, NULL, screen, &(SDL_Rect){ox, SCALE1(PADDING) + oy}); + if (enddesc) desc = list->desc; + + if (desc) { + int w,h; + GFX_sizeText(font.tiny, desc, SCALE1(12), &w,&h); + GFX_blitText(font.tiny, desc, SCALE1(12), COLOR_WHITE, screen, &(SDL_Rect){ + (SCREEN_WIDTH - w) / 2, + SCREEN_HEIGHT - SCALE1(PADDING) - h, + w,h + }); + } + + GFX_flip(screen); + } + else GFX_sync(); + } + + GFX_clearAll(); + GFX_flip(screen); + + return 0; +} + +// TODO: set in makefile +#define BUILD_DATE "2023.01.25" +#define COMMIT_HASH "d3adb33f" + +int options_save_confirm(MenuList* list, int i) { + char* message; + switch (i) { + case 0: { + // TODO: + message = "Saved for all games."; + break; + } + case 1: { + // TODO: + message = "Saved for this game."; + break; + } + default: { + // TODO: + message = "Restored defaults."; + break; + } + } + + uint32_t message_start = SDL_GetTicks(); + int ready = 0; + int dirty = 1; + while (1) { + GFX_startFrame(); + PAD_poll(); + + if (!ready && SDL_GetTicks()-message_start>=1000) ready = dirty = 1; + + if (ready && (PAD_justPressed(BTN_A) || PAD_justPressed(BTN_B))) break; + + if (dirty) { + dirty = 0; + GFX_clear(screen); + GFX_blitMessage(message, screen, NULL); + if (ready) GFX_blitButtonGroup((char*[]){ "A","OKAY", NULL }, screen, 1); + GFX_flip(screen); + } + else GFX_sync(); + } + return MENU_CALLBACK_EXIT; +} +int options_shortcuts_bind_confirm(MenuList* list, int i) { + MenuItem* item = &list->items[i]; + int bound = 0; + while (!bound) { + GFX_startFrame(); + PAD_poll(); + + // NOTE: off by one because of the initial NONE value + for (int id=0; id<=RETRO_BUTTON_COUNT; id++) { + if (PAD_justPressed(1 << id-1)) { + fflush(stdout); + item->value = id; + if (PAD_isPressed(BTN_MENU)) { + item->value += RETRO_BUTTON_COUNT; + } + bound = 1; + break; + } + } + GFX_sync(); + } + return MENU_CALLBACK_NEXT_ITEM; +} +int options_controls_bind_confirm(MenuList* list, int i) { + MenuItem* item = &list->items[i]; + int bound = 0; + while (!bound) { + GFX_startFrame(); + PAD_poll(); + + // NOTE: off by one because of the initial NONE value + for (int id=0; id<=RETRO_BUTTON_COUNT; id++) { + if (PAD_justPressed(1 << id-1)) { + fflush(stdout); + item->value = id; + bound = 1; + break; + } + } + GFX_sync(); + } + return MENU_CALLBACK_NEXT_ITEM; +} + +// NOTE: these must be in BTN_ID_ order also off by 1 because of NONE (which is -1 in BTN_ID_ land) +static char* button_labels[] = { + "NONE", // displayed by default + "UP", + "DOWN", + "LEFT", + "RIGHT", + "A", + "B", + "X", + "Y", + "START", + "SELECT", + "L1", + "R1", + "L2", + "R2", + NULL, +}; +static char* shortcut_labels[] = { + "NONE", // displayed by default + "UP", + "DOWN", + "LEFT", + "RIGHT", + "A", + "B", + "X", + "Y", + "START", + "SELECT", + "L1", + "R1", + "L2", + "R2", + "MENU+UP", + "MENU+DOWN", + "MENU+LEFT", + "MENU+RIGHT", + "MENU+A", + "MENU+B", + "MENU+X", + "MENU+Y", + "MENU+START", + "MENU+SELECT", + "MENU+L1", + "MENU+R1", + "MENU+L2", + "MENU+R2", + NULL, +}; +static char* onoff_labels[] = { + "OFF", + "ON", + NULL +}; +static char* tearing_labels[] = { + "OFF", + "LENIENT", + "STRICT", + NULL +}; +static char* max_ff_labels[] = { + "NONE", + "2X", + "3X", + "4X", + "5X", + "6X", + "7X", + "8X", + NULL, +}; + +static MenuList options_frontend_menu = { + .type = MENU_VAR, + .items = (MenuItem[]){ + {"Scanlines/Grid", .values=onoff_labels, .desc="Simulate scanlines (or a pixel grid at odd scales). Darkens\nthe overall image by about 50%. Reduces CPU load."}, + {"Optimize Text", .values=onoff_labels,.value=1, .desc="Prioritize a consistent stroke width when upscaling single\npixel lines using nearest neighbor scaler. Increases CPU load."}, + {"Prevent Tearing", .values=tearing_labels,.value=1, .desc="Wait for vsync before drawing the next frame. Lenient\nonly waits when within frame budget. Strict always waits\nand may cause audio stutter or crackling in some games."}, + {"Debug HUD", .values=onoff_labels,.desc="Show frames per second, cpu load,\nresolution, and scaler information."}, + {"Max FF Speed", .values=max_ff_labels,.value=3,.desc="Fast forward will not exceed the selected speed\n(but may be less than depending on game and emulator)."}, + {NULL}, + } +}; + +static int options_emulator_change(MenuList* list, int i) { + MenuItem* item = &list->items[i]; + Option* option = Options_getOption(item->key); + LOG_info("%s (%s) changed from `%s` (%s) to `%s` (%s)\n", item->name, item->key, + item->values[option->value], option->values[option->value], + item->values[item->value], option->values[item->value] + ); + Options_setOptionRawValue(item->key, item->value); +} + +static MenuList options_emulator_menu = { + .type = MENU_FIXED, + .on_change = options_emulator_change, + .items = NULL, +}; +static int options_emulator_confirm(MenuList* list, int i) { + if (options_emulator_menu.items==NULL) { + // TODO: where do I free this? + options_emulator_menu.items = calloc(options.count+1, sizeof(MenuItem)); + for (int j=0; jname = option->name; + item->key = option->key; + item->value = option->value; + // TODO: these need to be uppercased so we'll want to copy instead of reference + item->values = option->labels; + } + } + Menu_options(&options_emulator_menu); + return MENU_CALLBACK_NOP; +} + +// TODO: generated by core overrides? +static MenuList options_controls_menu = { + .type = MENU_INPUT, + .desc = "Press A to set and X to clear.", + .on_confirm = options_controls_bind_confirm, + .items = (MenuItem[]){ + {"UP",.values=button_labels}, + {"DOWN",.values=button_labels}, + {"LEFT",.values=button_labels}, + {"RIGHT",.values=button_labels}, + {"SELECT",.values=button_labels}, + {"START",.values=button_labels}, + {"A BUTTON",.values=button_labels}, + {"B BUTTON",.values=button_labels}, + {"X BUTTON",.values=button_labels}, + {"Y BUTTON",.values=button_labels}, + {"L BUTTON",.values=button_labels}, + {"R BUTTON",.values=button_labels}, + {NULL} + } +}; +static MenuList options_shortcuts_menu = { + .type = MENU_INPUT, + .desc = "Press A to set and X to clear.\nSupports single button and MENU+button.", + .on_confirm = options_shortcuts_bind_confirm, + .items = (MenuItem[]){ + {"Save State",.values=shortcut_labels}, + {"Load State",.values=shortcut_labels}, + {"Reset Game",.values=shortcut_labels}, + {"Toggle FF",.values=shortcut_labels}, + {"Hold FF",.values=shortcut_labels}, + {NULL} + } +}; +static MenuList options_save_menu = { + .type = MENU_LIST, + .on_confirm = options_save_confirm, + .items = (MenuItem[]){ + {"Save for all games"}, + {"Save for this game"}, + {"Restore defaults"}, + {NULL}, + } +}; + +static MenuList options_menu = { + .type = MENU_LIST, + .items = (MenuItem[]){ + {"Frontend", "MinUI / " BUILD_DATE " / " COMMIT_HASH,.submenu=&options_frontend_menu}, // TODO: build submenu from frontend options + {"Emulator",.on_confirm=options_emulator_confirm}, // TODO: build submenu from core options + {"Controls",.submenu=&options_controls_menu}, // TODO: build submenu from core buttons using callback then open the submenu manually + {"Shortcuts",.submenu=&options_shortcuts_menu}, + {"Save Changes",.submenu=&options_save_menu,}, + {NULL}, + } +}; + void Menu_loop(void) { POW_enableAutosleep(); PAD_reset(); @@ -2031,9 +2830,10 @@ void Menu_loop(void) { } break; case ITEM_OPTS: - Core_reset(); // TODO: tmp? - status = STATUS_OPTS; - show_menu = 0; + GFX_setMode(MODE_MAIN); + Menu_options(&options_menu); + GFX_setMode(MODE_MENU); + dirty = 1; break; case ITEM_QUIT: status = STATUS_QUIT; @@ -2069,10 +2869,10 @@ void Menu_loop(void) { SDL_BlitSurface(text, &(SDL_Rect){ 0, 0, - max_width-SCALE1(12*2), + max_width-SCALE1(BUTTON_PADDING*2), text->h }, screen, &(SDL_Rect){ - SCALE1(PADDING+12), + SCALE1(PADDING+BUTTON_PADDING), SCALE1(PADDING+4) }); SDL_FreeSurface(text); @@ -2081,8 +2881,6 @@ void Menu_loop(void) { GFX_blitButtonGroup((char*[]){ "B","BACK", "A","OKAY", NULL }, screen, 1); // list - TTF_SizeUTF8(font.large, menu.items[ITEM_CONT], &ow, NULL); - ow += SCALE1(12*2); oy = 35; for (int i=0; iw, + SCREEN_WIDTH - SCALE1(PADDING + BUTTON_PADDING) - text->w, SCALE1(oy + PADDING + 4) }); SDL_FreeSurface(text); } + TTF_SizeUTF8(font.large, item, &ow, NULL); + ow += SCALE1(BUTTON_PADDING*2); + // pill GFX_blitPill(ASSET_WHITE_PILL, screen, &(SDL_Rect){ SCALE1(PADDING), @@ -2113,14 +2914,12 @@ void Menu_loop(void) { SCALE1(PILL_SIZE) }); text_color = COLOR_BLACK; - - // TODO: draw arrow? } else { // shadow text = TTF_RenderUTF8_Blended(font.large, item, COLOR_BLACK); SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ - SCALE1(2 + PADDING + 12), + SCALE1(2 + PADDING + BUTTON_PADDING), SCALE1(1 + PADDING + oy + (i * PILL_SIZE) + 4) }); SDL_FreeSurface(text); @@ -2129,7 +2928,7 @@ void Menu_loop(void) { // text text = TTF_RenderUTF8_Blended(font.large, item, text_color); SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ - SCALE1(PADDING + 12), + SCALE1(PADDING + BUTTON_PADDING), SCALE1(oy + PADDING + (i * PILL_SIZE) + 4) }); SDL_FreeSurface(text); @@ -2187,12 +2986,7 @@ void Menu_loop(void) { GFX_flip(screen); dirty = 0; } - else { - // slow down to 60fps - uint32_t frame_duration = SDL_GetTicks() - frame_start; - #define kTargetFrameDuration 17 - if (frame_durationstart = (start<0) ? 0 : start; - top->end = total; + top->end = total; } else if (selectedstart) { top->start -= 1;