diff --git a/skeleton/.system/res/assets@2x.png b/skeleton/.system/res/assets@2x.png index f75523f..cf950c1 100644 Binary files a/skeleton/.system/res/assets@2x.png and b/skeleton/.system/res/assets@2x.png differ 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;