diff --git a/include/app/app.h b/include/app/app.h index 8cec047..1f951f2 100644 --- a/include/app/app.h +++ b/include/app/app.h @@ -30,41 +30,41 @@ struct ImGuiIO; enum texture_filter_kind { - TEXTURE_FILTER_MIN = 0, - TEXTURE_FILTER_NEAREST = 0, TEXTURE_FILTER_LINEAR = 1, + TEXTURE_FILTER_LEN, + TEXTURE_FILTER_MIN = 0, TEXTURE_FILTER_MAX = 1, }; -enum pixel_color_effect_kind { - PIXEL_COLOR_EFFECT_MIN = 0, - - PIXEL_COLOR_EFFECT_NONE = 0, - PIXEL_COLOR_EFFECT_COLOR_CORRECTION = 1, - PIXEL_COLOR_EFFECT_GREY_SCALE = 2, +enum pixel_color_filter_kind { + PIXEL_COLOR_FILTER_NONE = 0, + PIXEL_COLOR_FILTER_COLOR_CORRECTION = 1, + PIXEL_COLOR_FILTER_GREY_SCALE = 2, - PIXEL_COLOR_EFFECT_MAX = 2, + PIXEL_COLOR_FILTER_LEN, + PIXEL_COLOR_FILTER_MIN = 0, + PIXEL_COLOR_FILTER_MAX = 2, }; -enum pixel_scaler_effect_kind { - PIXEL_SCALER_EFFECT_MIN = 0, +enum pixel_scaling_filter_kind { + PIXEL_SCALING_FILTER_NONE = 0, + PIXEL_SCALING_FILTER_LCD_GRID = 1, + PIXEL_SCALING_FILTER_LCD_GRID_WITH_RGB_STRIPES = 2, - PIXEL_SCALER_EFFECT_NONE = 0, - PIXEL_SCALER_EFFECT_LCD_GRID = 1, - PIXEL_SCALER_EFFECT_LCD_GRID_WITH_RGB_STRIPES = 2, - - PIXEL_SCALER_EFFECT_MAX = 2, + PIXEL_SCALING_FILTER_LEN, + PIXEL_SCALING_FILTER_MIN = 0, + PIXEL_SCALING_FILTER_MAX = 2, }; enum aspect_ratio { - ASPECT_RATIO_MIN = 0, - ASPECT_RATIO_RESIZE = 0, ASPECT_RATIO_BORDERS = 1, ASPECT_RATIO_STRETCH = 2, + ASPECT_RATIO_LEN, + ASPECT_RATIO_MIN = 0, ASPECT_RATIO_MAX = 2, }; @@ -113,6 +113,15 @@ enum ui_notification_kind { UI_NOTIFICATION_ERROR, }; +enum menu_kind { + MENU_EMULATION, + MENU_VIDEO, + MENU_AUDIO, + MENU_BINDINGS, + + MENU_MAX, +}; + struct ui_notification { enum ui_notification_kind kind; char *msg; @@ -201,7 +210,7 @@ struct app { GLuint game_texture; GLuint pixel_color_texture; - GLuint pixel_scaler_texture; + GLuint pixel_scaling_texture; GLuint fbo; GLuint vao; GLuint vbo; @@ -212,11 +221,9 @@ struct app { GLuint program_lcd_grid_with_rgb_stripes; GLuint pixel_color_program; - bool use_pixel_color_program; - GLuint pixel_scaler_program; - size_t pixel_scaler_size; - bool use_pixel_scaler_program; + GLuint pixel_scaling_program; + size_t pixel_scaling_size; } gfx; struct { @@ -240,8 +247,8 @@ struct app { enum aspect_ratio aspect_ratio; bool vsync; enum texture_filter_kind texture_filter; - enum pixel_color_effect_kind pixel_color_effect; - enum pixel_scaler_effect_kind pixel_scaler_effect; + enum pixel_color_filter_kind pixel_color_filter; + enum pixel_scaling_filter_kind pixel_scaling_filter; } video; struct { @@ -314,11 +321,14 @@ struct app { struct { bool open; - bool visible; - SDL_Keycode *keyboard_target; - SDL_GameControllerButton *controller_target; - } keybindings_editor; + uint32_t menu; + + struct { + SDL_Keycode *keyboard_target; + SDL_GameControllerButton *controller_target; + } keybindings_editor; + } settings; struct ui_notification *notifications; } ui; @@ -388,9 +398,6 @@ extern char const *SHADER_VERTEX_COMMON; /* app/windows/game.c */ void app_win_game(struct app *app); -/* app/windows/keybinds.c */ -void app_win_keybinds_editor(struct app *app); - /* app/windows/menubar.c */ void app_win_menubar(struct app *app); @@ -398,6 +405,9 @@ void app_win_menubar(struct app *app); void app_new_notification(struct app *app, enum ui_notification_kind, char const *msg, ...); void app_win_notifications(struct app *app); +/* app/windows/settings.c */ +void app_win_settings(struct app *app); + /* args.c */ void app_args_parse(struct app *app, int argc, char * const argv[]); diff --git a/include/gba/memory.h b/include/gba/memory.h index a7f3610..4f95f5c 100644 --- a/include/gba/memory.h +++ b/include/gba/memory.h @@ -129,7 +129,6 @@ enum backup_storage_types { BACKUP_LEN = BACKUP_MAX + 1, }; - static char const * const backup_storage_names[] = { "None", "EEPROM 4k", diff --git a/source/app/config.c b/source/app/config.c index 8f44ff2..096750d 100644 --- a/source/app/config.c +++ b/source/app/config.c @@ -120,14 +120,14 @@ app_config_load( app->video.texture_filter = max(TEXTURE_FILTER_MIN, min(app->video.texture_filter, TEXTURE_FILTER_MAX)); } - if (mjson_get_number(data, data_len, "$.video.pixel_color_effect", &d)) { - app->video.pixel_color_effect = (int)d; - app->video.pixel_color_effect = max(PIXEL_COLOR_EFFECT_MIN, min(app->video.pixel_color_effect, PIXEL_COLOR_EFFECT_MAX)); + if (mjson_get_number(data, data_len, "$.video.pixel_color_filter", &d)) { + app->video.pixel_color_filter = (int)d; + app->video.pixel_color_filter = max(PIXEL_COLOR_FILTER_MIN, min(app->video.pixel_color_filter, PIXEL_COLOR_FILTER_MAX)); } - if (mjson_get_number(data, data_len, "$.video.pixel_scaler_effect", &d)) { - app->video.pixel_scaler_effect = (int)d; - app->video.pixel_scaler_effect = max(PIXEL_SCALER_EFFECT_MIN, min(app->video.pixel_scaler_effect, PIXEL_SCALER_EFFECT_MAX)); + if (mjson_get_number(data, data_len, "$.video.pixel_scaling_filter", &d)) { + app->video.pixel_scaling_filter = (int)d; + app->video.pixel_scaling_filter = max(PIXEL_SCALING_FILTER_MIN, min(app->video.pixel_scaling_filter, PIXEL_SCALING_FILTER_MAX)); } } @@ -236,8 +236,8 @@ app_config_save( "aspect_ratio": %d, "vsync": %B, "texture_filter": %d, - "pixel_color_effect": %d, - "pixel_scaler_effect": %d + "pixel_color_filter": %d, + "pixel_scaling_filter": %d }, // Audio @@ -263,8 +263,8 @@ app_config_save( (int)app->video.aspect_ratio, (int)app->video.vsync, (int)app->video.texture_filter, - (int)app->video.pixel_color_effect, - (int)app->video.pixel_scaler_effect, + (int)app->video.pixel_color_filter, + (int)app->video.pixel_scaling_filter, (int)app->audio.mute, app->audio.level ); diff --git a/source/app/main.c b/source/app/main.c index 51d0584..4c17a2b 100644 --- a/source/app/main.c +++ b/source/app/main.c @@ -68,7 +68,8 @@ main( app.emulation.rtc.autodetect = true; app.emulation.rtc.enabled = true; app.file.bios_path = strdup("./bios.bin"); - app.video.pixel_color_effect = PIXEL_COLOR_EFFECT_COLOR_CORRECTION; + app.video.pixel_color_filter = PIXEL_COLOR_FILTER_COLOR_CORRECTION; + app.video.pixel_scaling_filter = PIXEL_SCALING_FILTER_LCD_GRID; app.video.vsync = false; app.video.display_size = 3; app.video.aspect_ratio = ASPECT_RATIO_RESIZE; diff --git a/source/app/meson.build b/source/app/meson.build index b874874..7dd97da 100644 --- a/source/app/meson.build +++ b/source/app/meson.build @@ -71,9 +71,9 @@ libapp = static_library( 'shaders/frag-lcd-grid.c', 'shaders/vertex-common.c', 'windows/game.c', - 'windows/keybinds.c', 'windows/menubar.c', 'windows/notif.c', + 'windows/settings.c', 'args.c', 'config.c', 'emulator.c', diff --git a/source/app/sdl/event.c b/source/app/sdl/event.c index 4965f71..9c45cd7 100644 --- a/source/app/sdl/event.c +++ b/source/app/sdl/event.c @@ -122,23 +122,25 @@ app_sdl_handle_events( break; } - /* Handle the special case where we are creating new keybindings. */ - if (app->ui.keybindings_editor.visible) { + /* + ** Ignore keys if the settings are open except the special case where we are creating new bindings. + */ + if (app->ui.settings.open) { if (event.type == SDL_KEYDOWN) { // The `Escape` key is used to clear a bind. if (event.key.keysym.sym == SDLK_ESCAPE) { - if (app->ui.keybindings_editor.keyboard_target) { - *app->ui.keybindings_editor.keyboard_target = SDLK_UNKNOWN; - app->ui.keybindings_editor.keyboard_target = NULL; + if (app->ui.settings.keybindings_editor.keyboard_target) { + *app->ui.settings.keybindings_editor.keyboard_target = SDLK_UNKNOWN; + app->ui.settings.keybindings_editor.keyboard_target = NULL; } - if (app->ui.keybindings_editor.controller_target) { - *app->ui.keybindings_editor.controller_target = SDL_CONTROLLER_BUTTON_INVALID; - app->ui.keybindings_editor.controller_target = NULL; + if (app->ui.settings.keybindings_editor.controller_target) { + *app->ui.settings.keybindings_editor.controller_target = SDL_CONTROLLER_BUTTON_INVALID; + app->ui.settings.keybindings_editor.controller_target = NULL; } - } else if (app->ui.keybindings_editor.keyboard_target) { + } else if (app->ui.settings.keybindings_editor.keyboard_target) { app_bindings_keyboard_clear(app, event.key.keysym.sym); - *app->ui.keybindings_editor.keyboard_target = event.key.keysym.sym; - app->ui.keybindings_editor.keyboard_target = NULL; + *app->ui.settings.keybindings_editor.keyboard_target = event.key.keysym.sym; + app->ui.settings.keybindings_editor.keyboard_target = NULL; } } break ; @@ -162,12 +164,14 @@ app_sdl_handle_events( case SDL_CONTROLLERBUTTONDOWN: { size_t i; - /* Handle the special case where we are creating new keybindings. */ - if (app->ui.keybindings_editor.visible) { - if (event.type == SDL_CONTROLLERBUTTONDOWN && app->ui.keybindings_editor.controller_target) { + /* + ** Ignore buttons if the settings are open except the special case where we are creating new bindings. + */ + if (app->ui.settings.open) { + if (event.type == SDL_CONTROLLERBUTTONDOWN && app->ui.settings.keybindings_editor.controller_target) { app_bindings_controller_clear(app, event.cbutton.button); - *app->ui.keybindings_editor.controller_target = event.cbutton.button; - app->ui.keybindings_editor.controller_target = NULL; + *app->ui.settings.keybindings_editor.controller_target = event.cbutton.button; + app->ui.settings.keybindings_editor.controller_target = NULL; } break ; } @@ -189,8 +193,8 @@ app_sdl_handle_events( bool state_a; bool state_b; - /* Disable the joysticks if the keybindings editor is visible */ - if (app->ui.keybindings_editor.visible) { + /* Disable the joysticks if the settings are visible */ + if (app->ui.settings.open) { break; } diff --git a/source/app/sdl/video.c b/source/app/sdl/video.c index dd20145..9b8333e 100644 --- a/source/app/sdl/video.c +++ b/source/app/sdl/video.c @@ -154,7 +154,7 @@ app_sdl_video_init( /* Create the OpenGL objects required to build the pipeline */ glGenTextures(1, &app->gfx.game_texture); glGenTextures(1, &app->gfx.pixel_color_texture); - glGenTextures(1, &app->gfx.pixel_scaler_texture); + glGenTextures(1, &app->gfx.pixel_scaling_texture); glGenFramebuffers(1, &app->gfx.fbo); glGenVertexArrays(1, &app->gfx.vao); glGenBuffers(1, &app->gfx.vbo); @@ -220,20 +220,17 @@ app_sdl_video_rebuild_pipeline( NULL ); - switch (app->video.pixel_color_effect) { - case PIXEL_COLOR_EFFECT_COLOR_CORRECTION: { + switch (app->video.pixel_color_filter) { + case PIXEL_COLOR_FILTER_COLOR_CORRECTION: { app->gfx.pixel_color_program = app->gfx.program_color_correction; - app->gfx.use_pixel_color_program = true; break; }; - case PIXEL_COLOR_EFFECT_GREY_SCALE: { + case PIXEL_COLOR_FILTER_GREY_SCALE: { app->gfx.pixel_color_program = app->gfx.program_grey_scale; - app->gfx.use_pixel_color_program = true; break; }; default: { app->gfx.pixel_color_program = 0; - app->gfx.use_pixel_color_program = false; break; }; } @@ -256,30 +253,27 @@ app_sdl_video_rebuild_pipeline( NULL ); - switch (app->video.pixel_scaler_effect) { - case PIXEL_SCALER_EFFECT_LCD_GRID: { - app->gfx.pixel_scaler_program = app->gfx.program_lcd_grid; - app->gfx.pixel_scaler_size = 3; - app->gfx.use_pixel_scaler_program = true; + switch (app->video.pixel_scaling_filter) { + case PIXEL_SCALING_FILTER_LCD_GRID: { + app->gfx.pixel_scaling_program = app->gfx.program_lcd_grid; + app->gfx.pixel_scaling_size = 3; break; }; - case PIXEL_SCALER_EFFECT_LCD_GRID_WITH_RGB_STRIPES: { - app->gfx.pixel_scaler_program = app->gfx.program_lcd_grid_with_rgb_stripes; - app->gfx.pixel_scaler_size = 3; - app->gfx.use_pixel_scaler_program = true; + case PIXEL_SCALING_FILTER_LCD_GRID_WITH_RGB_STRIPES: { + app->gfx.pixel_scaling_program = app->gfx.program_lcd_grid_with_rgb_stripes; + app->gfx.pixel_scaling_size = 3; break; }; default: { - app->gfx.pixel_scaler_program = 0; - app->gfx.pixel_scaler_size = 1; - app->gfx.use_pixel_scaler_program = false; + app->gfx.pixel_scaling_program = 0; + app->gfx.pixel_scaling_size = 1; break; }; } - // Setup the pixel scaler texture + // Setup the pixel scaling texture glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, app->gfx.pixel_scaler_texture); + glBindTexture(GL_TEXTURE_2D, app->gfx.pixel_scaling_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texture_filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texture_filter); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); @@ -287,8 +281,8 @@ app_sdl_video_rebuild_pipeline( GL_TEXTURE_2D, 0, GL_RGBA, - GBA_SCREEN_WIDTH * app->gfx.pixel_scaler_size, - GBA_SCREEN_HEIGHT * app->gfx.pixel_scaler_size, + GBA_SCREEN_WIDTH * app->gfx.pixel_scaling_size, + GBA_SCREEN_HEIGHT * app->gfx.pixel_scaling_size, 0, GL_RGBA, GL_UNSIGNED_BYTE, @@ -434,7 +428,7 @@ app_sdl_video_cleanup( glDeleteFramebuffers(1, &app->gfx.fbo); glDeleteTextures(1, &app->gfx.game_texture); glDeleteTextures(1, &app->gfx.pixel_color_texture); - glDeleteTextures(1, &app->gfx.pixel_scaler_texture); + glDeleteTextures(1, &app->gfx.pixel_scaling_texture); SDL_GL_DeleteContext(app->gfx.gl_context); // Close the Wingowd @@ -458,7 +452,9 @@ app_sdl_video_render_frame( app_win_game(app); } - app_win_keybinds_editor(app); + if (app->ui.settings.open) { + app_win_settings(app); + } app_win_notifications(app); diff --git a/source/app/windows/game.c b/source/app/windows/game.c index 5df2f83..2362b6a 100644 --- a/source/app/windows/game.c +++ b/source/app/windows/game.c @@ -67,7 +67,7 @@ app_win_game( glBindFramebuffer(GL_FRAMEBUFFER, app->gfx.fbo); // Apply the Pixel Color Effect - if (app->gfx.use_pixel_color_program) { + if (app->gfx.pixel_color_program != 0) { in_texture = out_texture; out_texture = app->gfx.pixel_color_texture; @@ -85,20 +85,20 @@ app_win_game( glDrawArrays(GL_TRIANGLES, 0, 6); } - // Apply the Pixel Scaler Effect - if (app->gfx.use_pixel_scaler_program) { + // Apply the Pixel Scaling Effect + if (app->gfx.pixel_scaling_program != 0) { in_texture = out_texture; - out_texture = app->gfx.pixel_scaler_texture; + out_texture = app->gfx.pixel_scaling_texture; // Set the viewport - glViewport(0, 0, GBA_SCREEN_WIDTH * app->gfx.pixel_scaler_size, GBA_SCREEN_HEIGHT * app->gfx.pixel_scaler_size); + glViewport(0, 0, GBA_SCREEN_WIDTH * app->gfx.pixel_scaling_size, GBA_SCREEN_HEIGHT * app->gfx.pixel_scaling_size); // Set the input and output texture glBindTexture(GL_TEXTURE_2D, in_texture); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, out_texture, 0); // Set the shader to use - glUseProgram(app->gfx.pixel_scaler_program); + glUseProgram(app->gfx.pixel_scaling_program); // Draw glDrawArrays(GL_TRIANGLES, 0, 6); diff --git a/source/app/windows/keybinds.c b/source/app/windows/keybinds.c deleted file mode 100644 index 7f1d8a3..0000000 --- a/source/app/windows/keybinds.c +++ /dev/null @@ -1,193 +0,0 @@ -/******************************************************************************\ -** -** This file is part of the Hades GBA Emulator, and is made available under -** the terms of the GNU General Public License version 2. -** -** Copyright (C) 2021-2024 - The Hades Authors -** -\******************************************************************************/ - -#include -#include "hades.h" -#include "app/app.h" - -char const * const binds_pretty_name[] = { - [BIND_GBA_A] = "A", - [BIND_GBA_B] = "B", - [BIND_GBA_L] = "L", - [BIND_GBA_R] = "R", - [BIND_GBA_UP] = "Up", - [BIND_GBA_DOWN] = "Down", - [BIND_GBA_LEFT] = "Left", - [BIND_GBA_RIGHT] = "Right", - [BIND_GBA_START] = "Start", - [BIND_GBA_SELECT] = "Select", - - [BIND_EMULATOR_SPEED_X1] = "Speed x1", - [BIND_EMULATOR_SPEED_X2] = "Speed x2", - [BIND_EMULATOR_SPEED_X3] = "Speed x3", - [BIND_EMULATOR_SPEED_X4] = "Speed x4", - [BIND_EMULATOR_SPEED_X5] = "Speed x5", - [BIND_EMULATOR_SPEED_MAX] = "Speed Max", - [BIND_EMULATOR_SPEED_MAX_HOLD] = "Speed Max (Hold)", - [BIND_EMULATOR_SCREENSHOT] = "Screenshot", - [BIND_EMULATOR_QUICKSAVE] = "Quicksave", - [BIND_EMULATOR_QUICKLOAD] = "Quickload", - [BIND_EMULATOR_PAUSE] = "Pause", - [BIND_EMULATOR_RESET] = "Reset", -}; - -char const * const binds_slug[] = { - [BIND_GBA_A] = "a", - [BIND_GBA_B] = "b", - [BIND_GBA_L] = "l", - [BIND_GBA_R] = "r", - [BIND_GBA_UP] = "up", - [BIND_GBA_DOWN] = "down", - [BIND_GBA_LEFT] = "left", - [BIND_GBA_RIGHT] = "right", - [BIND_GBA_START] = "start", - [BIND_GBA_SELECT] = "select", - - [BIND_EMULATOR_SPEED_X1] = "speed_x1", - [BIND_EMULATOR_SPEED_X2] = "speed_x2", - [BIND_EMULATOR_SPEED_X3] = "speed_x3", - [BIND_EMULATOR_SPEED_X4] = "speed_x4", - [BIND_EMULATOR_SPEED_X5] = "speed_x5", - [BIND_EMULATOR_SPEED_MAX] = "speed_max", - [BIND_EMULATOR_SPEED_MAX_HOLD] = "speed_max_hold", - [BIND_EMULATOR_SCREENSHOT] = "screenshot", - [BIND_EMULATOR_QUICKSAVE] = "quicksave", - [BIND_EMULATOR_QUICKLOAD] = "quickload", - [BIND_EMULATOR_PAUSE] = "pause", - [BIND_EMULATOR_RESET] = "reset", -}; - -void -app_win_keybinds_editor( - struct app *app -) { - if (app->ui.keybindings_editor.open) { - app->ui.keybindings_editor.open = false; - igOpenPopup_Str("Keybindings Editor", ImGuiPopupFlags_None); - } - - app->ui.keybindings_editor.visible = false; - - // Always center the modal - igSetNextWindowPos( - (ImVec2){.x = app->ui.ioptr->DisplaySize.x * 0.5f, .y = app->ui.ioptr->DisplaySize.y * 0.5f}, - ImGuiCond_Always, - (ImVec2){.x = 0.5f, .y = 0.5f} - ); - - if (igBeginPopupModal( - "Keybindings Editor", - NULL, - ImGuiWindowFlags_Popup - | ImGuiWindowFlags_Modal - | ImGuiWindowFlags_NoResize - | ImGuiWindowFlags_NoMove - | ImGuiWindowFlags_NoTitleBar - | ImGuiWindowFlags_AlwaysAutoResize - | ImGuiWindowFlags_NoNavInputs - | ImGuiWindowFlags_NoNavFocus - )) { - size_t bind; - - app->ui.keybindings_editor.visible = true; - - for (bind = BIND_MIN; bind < BIND_MAX; ++bind) { - size_t j; - - - if (bind == BIND_GBA_MIN || bind == BIND_EMULATOR_MIN) { - igSpacing(); - igSpacing(); - - if (bind == BIND_GBA_MIN) { - igText(" GBA"); - igSameLine(0, igGetFontSize() * 13.5f); - } else if (bind == BIND_EMULATOR_MIN) { - igText(" Hades"); - igSameLine(0, igGetFontSize() * 12.5f); - } - - igText("Keyboard"); - igSameLine(0, igGetFontSize() * 10.f); - igText("Controller"); - - - igSpacing(); - igSeparator(); - igSpacing(); - } - - igText(" %-18s ", binds_pretty_name[bind]); - - for (j = 0; j < 4; ++j) { - SDL_GameControllerButton *controller_target; - SDL_Keycode *keyboard_target; - char const *key_name; - char label[32]; - - key_name = NULL; - keyboard_target = NULL; - controller_target = NULL; - - switch (j) { - case 0: { - igSameLine(0, igGetFontSize() * 0.5f); - key_name = SDL_GetKeyName(app->binds.keyboard[bind]); - keyboard_target = &app->binds.keyboard[bind]; - break; - }; - case 1: { - igSameLine(0, igGetFontSize() * 0.5f); - key_name = SDL_GetKeyName(app->binds.keyboard_alt[bind]); - keyboard_target = &app->binds.keyboard_alt[bind]; - break; - }; - case 2: { - igSameLine(0, igGetFontSize() * 2.f); - key_name = SDL_GameControllerGetStringForButton(app->binds.controller[bind]); - controller_target = &app->binds.controller[bind]; - break; - }; - case 3: { - igSameLine(0, igGetFontSize() * 0.5f); - key_name = SDL_GameControllerGetStringForButton(app->binds.controller_alt[bind]); - controller_target = &app->binds.controller_alt[bind]; - break; - }; - } - - if ( - (keyboard_target && keyboard_target == app->ui.keybindings_editor.keyboard_target) - || (controller_target && controller_target == app->ui.keybindings_editor.controller_target) - ) { - snprintf(label, sizeof(label), ">> %s <<##%zu", key_name ? key_name : " ", bind * 10 + j); - } else { - snprintf(label, sizeof(label), "%s##%zu", key_name ? key_name : "", bind * 10 + j); - } - - if (igButton(label, (ImVec2){.x = igGetFontSize() * 6.f, .y = igGetFontSize() * 1.5f})) { - app->ui.keybindings_editor.keyboard_target = keyboard_target; - app->ui.keybindings_editor.controller_target = controller_target; - } - } - - igSameLine(0, igGetFontSize() * 0.5f); - igText(" "); - } - - igSpacing(); - igSpacing(); - - if (igButton("Close", (ImVec2){.x = igGetFontSize() * 4.f, .y = igGetFontSize() * 1.5f})) { - igCloseCurrentPopup(); - } - - igEndPopup(); - } -} diff --git a/source/app/windows/menubar.c b/source/app/windows/menubar.c index 4913fde..da62090 100644 --- a/source/app/windows/menubar.c +++ b/source/app/windows/menubar.c @@ -84,8 +84,9 @@ app_win_menubar_file( igSeparator(); - if (igMenuItem_Bool("Keybindings", NULL, false, true)) { - app->ui.keybindings_editor.open = true; + if (igMenuItem_Bool("Settings", NULL, false, true)) { + app->ui.settings.open = true; + app->ui.settings.menu = 0; } igEndMenu(); @@ -98,9 +99,6 @@ app_win_menubar_emulation( struct app *app ) { if (igBeginMenu("Emulation", true)) { - if (igMenuItem_Bool("Skip BIOS", NULL, app->emulation.skip_bios, true)) { - app->emulation.skip_bios ^= 1; - } if (igBeginMenu("Speed", app->emulation.is_started)) { uint32_t x; @@ -183,42 +181,6 @@ app_win_menubar_emulation( igSeparator(); - if (igBeginMenu("Backup Storage", !app->emulation.is_started)) { - uint32_t x; - - if (igMenuItem_Bool("Auto-detect", NULL, app->emulation.backup_storage.autodetect, true)) { - app->emulation.backup_storage.autodetect ^= 1; - } - - igSeparator(); - - for (x = BACKUP_MIN; x < BACKUP_LEN; ++x) { - if (igMenuItem_Bool(backup_storage_names[x], NULL, !app->emulation.backup_storage.autodetect && app->emulation.backup_storage.type == x, true)) { - app->emulation.backup_storage.type = x; - app->emulation.backup_storage.autodetect = false; - } - } - - igEndMenu(); - } - - /* Backup storage & GPIO */ - if (igBeginMenu("Devices", !app->emulation.is_started)) { - - igText("RTC"); - igSeparator(); - - if (igMenuItem_Bool("Auto-detect", NULL, app->emulation.rtc.autodetect, true)) { - app->emulation.rtc.autodetect ^= 1; - } - if (igMenuItem_Bool("Enable", NULL, app->emulation.rtc.enabled, !app->emulation.rtc.autodetect)) { - app->emulation.rtc.enabled ^= 1; - } - igEndMenu(); - } - - igSeparator(); - if (igMenuItem_Bool("Pause", NULL, !app->emulation.is_running, app->emulation.is_started)) { if (app->emulation.is_running) { app_emulator_pause(app); @@ -236,6 +198,13 @@ app_win_menubar_emulation( app_emulator_run(app); } + igSeparator(); + + if (igMenuItem_Bool("Emulation Settings", NULL, false, true)) { + app->ui.settings.open = true; + app->ui.settings.menu = MENU_EMULATION; + } + igEndMenu(); } } @@ -282,108 +251,52 @@ app_win_menubar_video( igEndMenu(); } - /* Aspect Ratio */ - if (igBeginMenu("Aspect Ratio", true)) { - if (igMenuItem_Bool( - "Auto resize", - NULL, - app->video.aspect_ratio == ASPECT_RATIO_RESIZE, - true - )) { - app->video.aspect_ratio = ASPECT_RATIO_RESIZE; - app->ui.win.resize = true; - app->ui.win.resize_with_ratio = true; - app->ui.win.resize_ratio = min(app->ui.game.width / ((float)GBA_SCREEN_WIDTH * app->ui.scale), app->ui.game.height / ((float)GBA_SCREEN_HEIGHT * app->ui.scale)); - } - - if (igMenuItem_Bool( - "Black borders", - NULL, - app->video.aspect_ratio == ASPECT_RATIO_BORDERS, - true - )) { - app->video.aspect_ratio = ASPECT_RATIO_BORDERS; - } - - if (igMenuItem_Bool( - "Stretch", - NULL, - app->video.aspect_ratio == ASPECT_RATIO_STRETCH, - true - )) { - app->video.aspect_ratio = ASPECT_RATIO_STRETCH; - } - - igEndMenu(); - } - igSeparator(); - /* Texture Filter */ - if (igBeginMenu("Texture Filter", true)) { - if (igMenuItem_Bool("Nearest", NULL, app->video.texture_filter == TEXTURE_FILTER_NEAREST, true)) { - app->video.texture_filter = TEXTURE_FILTER_NEAREST; - app_sdl_video_rebuild_pipeline(app); - } - - if (igMenuItem_Bool("Linear", NULL, app->video.texture_filter == TEXTURE_FILTER_LINEAR, true)) { - app->video.texture_filter = TEXTURE_FILTER_LINEAR; - app_sdl_video_rebuild_pipeline(app); - } - - igEndMenu(); - } - /* Pixel Color Effect */ if (igBeginMenu("Color Effect", true)) { - if (igMenuItem_Bool("None", NULL, app->video.pixel_color_effect == PIXEL_COLOR_EFFECT_NONE, true)) { - app->video.pixel_color_effect = PIXEL_COLOR_EFFECT_NONE; + if (igMenuItem_Bool("None", NULL, app->video.pixel_color_filter == PIXEL_COLOR_FILTER_NONE, true)) { + app->video.pixel_color_filter = PIXEL_COLOR_FILTER_NONE; app_sdl_video_rebuild_pipeline(app); } igSeparator(); - if (igMenuItem_Bool("Color Correction", NULL, app->video.pixel_color_effect == PIXEL_COLOR_EFFECT_COLOR_CORRECTION, true)) { - app->video.pixel_color_effect = PIXEL_COLOR_EFFECT_COLOR_CORRECTION; + if (igMenuItem_Bool("Color Correction", NULL, app->video.pixel_color_filter == PIXEL_COLOR_FILTER_COLOR_CORRECTION, true)) { + app->video.pixel_color_filter = PIXEL_COLOR_FILTER_COLOR_CORRECTION; app_sdl_video_rebuild_pipeline(app); } - if (igMenuItem_Bool("Grey Scale", NULL, app->video.pixel_color_effect == PIXEL_COLOR_EFFECT_GREY_SCALE, true)) { - app->video.pixel_color_effect = PIXEL_COLOR_EFFECT_GREY_SCALE; + if (igMenuItem_Bool("Grey Scale", NULL, app->video.pixel_color_filter == PIXEL_COLOR_FILTER_GREY_SCALE, true)) { + app->video.pixel_color_filter = PIXEL_COLOR_FILTER_GREY_SCALE; app_sdl_video_rebuild_pipeline(app); } igEndMenu(); } - /* Pixel Scaler Effect */ - if (igBeginMenu("Scaler Effect", true)) { - if (igMenuItem_Bool("None", NULL, app->video.pixel_scaler_effect == PIXEL_SCALER_EFFECT_NONE, true)) { - app->video.pixel_scaler_effect = PIXEL_SCALER_EFFECT_NONE; + /* Pixel Scaling Effect */ + if (igBeginMenu("Scaling Effect", true)) { + if (igMenuItem_Bool("None", NULL, app->video.pixel_scaling_filter == PIXEL_SCALING_FILTER_NONE, true)) { + app->video.pixel_scaling_filter = PIXEL_SCALING_FILTER_NONE; app_sdl_video_rebuild_pipeline(app); } igSeparator(); - if (igMenuItem_Bool("LCD Grid /w RGB Stripes", NULL, app->video.pixel_scaler_effect == PIXEL_SCALER_EFFECT_LCD_GRID_WITH_RGB_STRIPES, true)) { - app->video.pixel_scaler_effect = PIXEL_SCALER_EFFECT_LCD_GRID_WITH_RGB_STRIPES; + if (igMenuItem_Bool("LCD Grid /w RGB Stripes", NULL, app->video.pixel_scaling_filter == PIXEL_SCALING_FILTER_LCD_GRID_WITH_RGB_STRIPES, true)) { + app->video.pixel_scaling_filter = PIXEL_SCALING_FILTER_LCD_GRID_WITH_RGB_STRIPES; app_sdl_video_rebuild_pipeline(app); } - if (igMenuItem_Bool("LCD Grid", NULL, app->video.pixel_scaler_effect == PIXEL_SCALER_EFFECT_LCD_GRID, true)) { - app->video.pixel_scaler_effect = PIXEL_SCALER_EFFECT_LCD_GRID; + if (igMenuItem_Bool("LCD Grid", NULL, app->video.pixel_scaling_filter == PIXEL_SCALING_FILTER_LCD_GRID, true)) { + app->video.pixel_scaling_filter = PIXEL_SCALING_FILTER_LCD_GRID; app_sdl_video_rebuild_pipeline(app); } igEndMenu(); } - /* VSync */ - if (igMenuItem_Bool("VSync", NULL, app->video.vsync, true)) { - app->video.vsync ^= 1; - SDL_GL_SetSwapInterval(app->video.vsync); - } - igSeparator(); /* Take a screenshot */ @@ -392,6 +305,13 @@ app_win_menubar_video( app_emulator_screenshot(app); } + igSeparator(); + + if (igMenuItem_Bool("Video Settings", NULL, false, true)) { + app->ui.settings.open = true; + app->ui.settings.menu = MENU_VIDEO; + } + igEndMenu(); } } @@ -402,8 +322,6 @@ app_win_menubar_audio( struct app *app ) { if (igBeginMenu("Audio", true)) { - float percent; - /* VSync */ if (igMenuItem_Bool("Mute", NULL, app->audio.mute, true)) { app->audio.mute ^= 1; @@ -411,17 +329,10 @@ app_win_menubar_audio( igSeparator(); - igText("Sound Level:"); - - igSpacing(); - - igSetNextItemWidth(100.f * app->ui.scale); - - percent = app->audio.level * 100.f; - igSliderFloat("##slider_sound_level", &percent, 0.0f, 100.0f, "%.0f%%", ImGuiSliderFlags_None); - app->audio.level = max(0.0f, min(percent / 100.f, 1.f)); - - igSpacing(); + if (igMenuItem_Bool("Audio Settings", NULL, false, true)) { + app->ui.settings.open = true; + app->ui.settings.menu = MENU_AUDIO; + } igEndMenu(); } diff --git a/source/app/windows/settings.c b/source/app/windows/settings.c new file mode 100644 index 0000000..7579ce5 --- /dev/null +++ b/source/app/windows/settings.c @@ -0,0 +1,592 @@ +/******************************************************************************\ +** +** This file is part of the Hades GBA Emulator, and is made available under +** the terms of the GNU General Public License version 2. +** +** Copyright (C) 2021-2024 - The Hades Authors +** +\******************************************************************************/ + +#include +#include +#include "hades.h" +#include "app/app.h" + +static char const *menu_names[MENU_MAX] = { + [MENU_EMULATION] = "Emulation", + [MENU_VIDEO] = "Video", + [MENU_AUDIO] = "Audio", + [MENU_BINDINGS] = "Bindings", +}; + +static char const *texture_filters_names[TEXTURE_FILTER_LEN] = { + [TEXTURE_FILTER_NEAREST] = "Nearest", + [TEXTURE_FILTER_LINEAR] = "Linear", +}; + +static char const *pixel_color_filters_names[PIXEL_COLOR_FILTER_LEN] = { + [PIXEL_COLOR_FILTER_NONE] = "None", + [PIXEL_COLOR_FILTER_COLOR_CORRECTION] = "Color correction", + [PIXEL_COLOR_FILTER_GREY_SCALE] = "Grey scale", +}; + +static char const *pixel_scaling_filters_names[PIXEL_SCALING_FILTER_LEN] = { + [PIXEL_SCALING_FILTER_NONE] = "None", + [PIXEL_SCALING_FILTER_LCD_GRID] = "LCD Grid", + [PIXEL_SCALING_FILTER_LCD_GRID_WITH_RGB_STRIPES] = "LCD Grid /w RGB Stripes", +}; + +static char const *aspect_ratio_names[ASPECT_RATIO_LEN] = { + [ASPECT_RATIO_RESIZE] = "Auto-Resize", + [ASPECT_RATIO_BORDERS] = "Black Borders", + [ASPECT_RATIO_STRETCH] = "Stretch", +}; + +static char const * const speed_names[] = { + "x1 (60 fps)", + "x2 (120 fps)", + "x3 (180 fps)", + "x4 (240 fps)", + "x5 (300 fps)", +}; + +static char const * const display_size_names[] = { + "x1", + "x2", + "x3", + "x4", + "x5", +}; + +char const * const binds_pretty_name[] = { + [BIND_GBA_A] = "A", + [BIND_GBA_B] = "B", + [BIND_GBA_L] = "L", + [BIND_GBA_R] = "R", + [BIND_GBA_UP] = "Up", + [BIND_GBA_DOWN] = "Down", + [BIND_GBA_LEFT] = "Left", + [BIND_GBA_RIGHT] = "Right", + [BIND_GBA_START] = "Start", + [BIND_GBA_SELECT] = "Select", + + [BIND_EMULATOR_SPEED_X1] = "Speed x1", + [BIND_EMULATOR_SPEED_X2] = "Speed x2", + [BIND_EMULATOR_SPEED_X3] = "Speed x3", + [BIND_EMULATOR_SPEED_X4] = "Speed x4", + [BIND_EMULATOR_SPEED_X5] = "Speed x5", + [BIND_EMULATOR_SPEED_MAX] = "Speed Max", + [BIND_EMULATOR_SPEED_MAX_HOLD] = "Speed Max (Hold)", + [BIND_EMULATOR_SCREENSHOT] = "Screenshot", + [BIND_EMULATOR_QUICKSAVE] = "Quicksave", + [BIND_EMULATOR_QUICKLOAD] = "Quickload", + [BIND_EMULATOR_PAUSE] = "Pause", + [BIND_EMULATOR_RESET] = "Reset", +}; + +char const * const binds_slug[] = { + [BIND_GBA_A] = "a", + [BIND_GBA_B] = "b", + [BIND_GBA_L] = "l", + [BIND_GBA_R] = "r", + [BIND_GBA_UP] = "up", + [BIND_GBA_DOWN] = "down", + [BIND_GBA_LEFT] = "left", + [BIND_GBA_RIGHT] = "right", + [BIND_GBA_START] = "start", + [BIND_GBA_SELECT] = "select", + + [BIND_EMULATOR_SPEED_X1] = "speed_x1", + [BIND_EMULATOR_SPEED_X2] = "speed_x2", + [BIND_EMULATOR_SPEED_X3] = "speed_x3", + [BIND_EMULATOR_SPEED_X4] = "speed_x4", + [BIND_EMULATOR_SPEED_X5] = "speed_x5", + [BIND_EMULATOR_SPEED_MAX] = "speed_max", + [BIND_EMULATOR_SPEED_MAX_HOLD] = "speed_max_hold", + [BIND_EMULATOR_SCREENSHOT] = "screenshot", + [BIND_EMULATOR_QUICKSAVE] = "quicksave", + [BIND_EMULATOR_QUICKLOAD] = "quickload", + [BIND_EMULATOR_PAUSE] = "pause", + [BIND_EMULATOR_RESET] = "reset", +}; + +static +void +app_win_settings_emulation( + struct app *app +) { + ImGuiViewport *vp; + int32_t speed; + + vp = igGetMainViewport(); + speed = app->emulation.speed ? app->emulation.speed - 1 : 0; + + igTextWrapped("Emulation Settings"); + igSpacing(); + igSeparator(); + igSpacing(); + + if (igBeginTabBar("##EmulationSettings", ImGuiTabBarFlags_None)) { + if (igBeginTabItem("BIOS", NULL, ImGuiTabItemFlags_None)) { + if (igBeginTable("##EmulationSettingsBIOS", 2, ImGuiTableFlags_None, (ImVec2){ .x = 0.f, .y = 0.f }, 0.f)) { + igTableSetupColumn("##EmulationSettingsBIOSLabel", ImGuiTableColumnFlags_WidthFixed, vp->WorkSize.x / 5.f, 0); + igTableSetupColumn("##EmulationSettingsBIOSValue", ImGuiTableColumnFlags_WidthStretch, 0.f, 0); + + // BIOS Path + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("BIOS"); + + igTableNextColumn(); + igBeginDisabled(true); + igInputText("##BiosPath", app->file.bios_path, strlen(app->file.bios_path), ImGuiInputTextFlags_ReadOnly, NULL, NULL); + igEndDisabled(); + igSameLine(0.0f, -1.0f); + if (igButton("Choose", (ImVec2){ 50.f, 0.f})) { + nfdresult_t result; + nfdchar_t *path; + + result = NFD_OpenDialog( + &path, + (nfdfilteritem_t[1]){(nfdfilteritem_t){ .name = "BIOS file", .spec = "bin,bios,raw"}}, + 1, + NULL + ); + + if (result == NFD_OKAY) { + free(app->file.bios_path); + app->file.bios_path = strdup(path); + NFD_FreePath(path); + } + } + + // Skip BIOS + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("Skip BIOS Intro"); + + igTableNextColumn(); + igCheckbox("##SkipBIOS", &app->emulation.skip_bios); + + igEndTable(); + } + igEndTabItem(); + } + if (igBeginTabItem("Speed", NULL, ImGuiTabItemFlags_None)) { + if (igBeginTable("##EmulationSettingsSpeed", 2, ImGuiTableFlags_None, (ImVec2){ .x = 0.f, .y = 0.f }, 0.f)) { + igTableSetupColumn("##EmulationSettingsSpeedLabel", ImGuiTableColumnFlags_WidthFixed, vp->WorkSize.x / 5.f, 0); + igTableSetupColumn("##EmulationSettingsSpeedValue", ImGuiTableColumnFlags_WidthStretch, 0.f, 0); + + // Unbounded Speed + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("Unbounded Speed"); + + igTableNextColumn(); + if (igCheckbox("##UnboundedSpeed", &app->emulation.unbounded)) { + app_emulator_speed(app, app->emulation.unbounded ? 0 : app->emulation.speed); + } + + // Speed + igBeginDisabled(app->emulation.unbounded); + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("Speed"); + + igTableNextColumn(); + if (igCombo_Str_arr("##Speed", &speed, speed_names, array_length(speed_names), 0)) { + app->emulation.speed = speed + 1; + app->emulation.unbounded = false; + app_emulator_speed(app, app->emulation.speed); + } + + igEndDisabled(); + + igEndTable(); + } + igEndTabItem(); + } + if (igBeginTabItem("Save", NULL, ImGuiTabItemFlags_None)) { + if (igBeginTable("##EmulationSettingsSave", 2, ImGuiTableFlags_None, (ImVec2){ .x = 0.f, .y = 0.f }, 0.f)) { + igTableSetupColumn("##EmulationSettingsSaveLabel", ImGuiTableColumnFlags_WidthFixed, vp->WorkSize.x / 5.f, 0); + igTableSetupColumn("##EmulationSettingsSaveValue", ImGuiTableColumnFlags_WidthStretch, 0.f, 0); + + // Unbounded Speed + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("Backup Storage Type Auto-Detect"); + + igTableNextColumn(); + igCheckbox("##BackupStorageTypeAutoDetect", &app->emulation.backup_storage.autodetect); + + // Backup Storage Type + igBeginDisabled(app->emulation.backup_storage.autodetect); + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("Backup Storage Type"); + + igTableNextColumn(); + igCombo_Str_arr("##BackupStorageType", (int *)&app->emulation.backup_storage.type, backup_storage_names, array_length(backup_storage_names), 0); + igEndDisabled(); + + igEndTable(); + } + igEndTabItem(); + } + if (igBeginTabItem("Devices", NULL, ImGuiTabItemFlags_None)) { + if (igBeginTable("##EmulationSettingsDevice", 2, ImGuiTableFlags_None, (ImVec2){ .x = 0.f, .y = 0.f }, 0.f)) { + igTableSetupColumn("##EmulationSettingsDeviceLabel", ImGuiTableColumnFlags_WidthFixed, vp->WorkSize.x / 5.f, 0); + igTableSetupColumn("##EmulationSettingsDeviceValue", ImGuiTableColumnFlags_WidthStretch, 0.f, 0); + + // RTC Auto-Detect + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("RTC Auto-Detect"); + + igTableNextColumn(); + igCheckbox("##RTCAutoDetect", &app->emulation.rtc.autodetect); + + // RTC Enable + igBeginDisabled(app->emulation.rtc.autodetect); + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("RTC Enable"); + + igTableNextColumn(); + igCheckbox("##RTCEnable", &app->emulation.rtc.enabled); + igEndDisabled(); + + igEndTable(); + } + igEndTabItem(); + } + igEndTabBar(); + } +} + +static +void +app_win_settings_video( + struct app *app +) { + int32_t display_size; + ImGuiViewport *vp; + uint32_t i; + + vp = igGetMainViewport(); + + igTextWrapped("Video Settings"); + igSpacing(); + igSeparator(); + igSpacing(); + + if (igBeginTabBar("##VideoSettings", ImGuiTabBarFlags_None)) { + if (igBeginTabItem("Display", NULL, ImGuiTabItemFlags_None)) { + if (igBeginTable("##VideoSettingsDisplay", 2, ImGuiTableFlags_None, (ImVec2){ .x = 0.f, .y = 0.f }, 0.f)) { + igTableSetupColumn("##VideoSettingsDisplayLabel", ImGuiTableColumnFlags_WidthFixed, vp->WorkSize.x / 5.f, 0); + igTableSetupColumn("##VideoSettingsDisplayValue", ImGuiTableColumnFlags_WidthStretch, 0.f, 0); + + // VSync + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("VSync"); + + igTableNextColumn(); + if (igCheckbox("##VSync", &app->video.vsync)) { + SDL_GL_SetSwapInterval(app->video.vsync); + } + + // Display Size + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("Display Size"); + + igTableNextColumn(); + + display_size = -1; + for (i = 1; i < array_length(display_size_names) + 1; ++i) { + if (vp->WorkSize.x == GBA_SCREEN_WIDTH * i * app->ui.scale && vp->WorkSize.y == GBA_SCREEN_HEIGHT * i * app->ui.scale) { + display_size = i; + break; + } + } + + if (igBeginCombo("##DisplaySize", display_size > 0 ? display_size_names[display_size - 1] : "", ImGuiComboFlags_None)) { + for (i = 1; i < array_length(display_size_names) + 1; ++i) { + bool is_selected; + + is_selected = (display_size == i); + if (igSelectable_Bool(display_size_names[i - 1], is_selected, ImGuiSelectableFlags_None, (ImVec2){ 0.f, 0.f })) { + app->video.display_size = i; + app->ui.win.resize = true; + app->ui.win.resize_with_ratio = false; + } + + if (is_selected) { + igSetItemDefaultFocus(); + } + } + igEndCombo(); + } + + // Aspect Ratio + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("Aspect Ratio"); + + igTableNextColumn(); + if (igCombo_Str_arr("##AspectRatio", (int *)&app->video.aspect_ratio, aspect_ratio_names, ASPECT_RATIO_LEN, 0)) { + // Force a resize of the window if the "auto-resize" option is selected + if (app->video.aspect_ratio == ASPECT_RATIO_RESIZE) { + app->ui.win.resize = true; + app->ui.win.resize_with_ratio = true; + app->ui.win.resize_ratio = min(app->ui.game.width / ((float)GBA_SCREEN_WIDTH * app->ui.scale), app->ui.game.height / ((float)GBA_SCREEN_HEIGHT * app->ui.scale)); + } + } + + igEndTable(); + } + igEndTabItem(); + } + if (igBeginTabItem("Filters", NULL, ImGuiTabItemFlags_None)) { + if (igBeginTable("##VideoSettingsFilters", 2, ImGuiTableFlags_None, (ImVec2){ .x = 0.f, .y = 0.f }, 0.f)) { + igTableSetupColumn("##VideoSettingsFiltersLabel", ImGuiTableColumnFlags_WidthFixed, vp->WorkSize.x / 5.f, 0); + igTableSetupColumn("##VideoSettingsFiltersValue", ImGuiTableColumnFlags_WidthStretch, 0.f, 0); + + // Texture Filter + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("Texture Filter"); + + igTableNextColumn(); + if (igCombo_Str_arr("##TextureFilters", (int *)&app->video.texture_filter, texture_filters_names, TEXTURE_FILTER_LEN, 0)) { + app_sdl_video_rebuild_pipeline(app); + } + + // Color Filter + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("Color Filter"); + + igTableNextColumn(); + if (igCombo_Str_arr("##ColorFilter", (int *)&app->video.pixel_color_filter, pixel_color_filters_names, PIXEL_COLOR_FILTER_LEN, 0)) { + app_sdl_video_rebuild_pipeline(app); + } + + // Scaling Filter + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("Scaling Filter"); + + igTableNextColumn(); + if (igCombo_Str_arr("##ScalingFilter", (int *)&app->video.pixel_scaling_filter, pixel_scaling_filters_names, PIXEL_SCALING_FILTER_LEN, 0)) { + app_sdl_video_rebuild_pipeline(app); + } + + igEndTable(); + } + igEndTabItem(); + } + igEndTabBar(); + } +} + +static +void +app_win_settings_audio( + struct app *app +) { + ImGuiViewport *vp; + float level; + + vp = igGetMainViewport(); + level = app->audio.level * 100.f; + + igTextWrapped("Audio Settings"); + igSpacing(); + igSeparator(); + igSpacing(); + + if (igBeginTable("##AudioSettings", 2, ImGuiTableFlags_None, (ImVec2){ .x = 0.f, .y = 0.f }, 0.f)) { + igTableSetupColumn("##AudioSettingsLabel", ImGuiTableColumnFlags_WidthFixed, vp->WorkSize.x / 5.f, 0); + igTableSetupColumn("##AudioSettingsValue", ImGuiTableColumnFlags_WidthStretch, 0.f, 0); + + // Mute + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("Mute"); + + igTableNextColumn(); + igCheckbox("##Mute", &app->audio.mute); + + // Audio level + igBeginDisabled(app->audio.mute); + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + igTableNextColumn(); + igTextWrapped("Audio Level"); + + igTableNextColumn(); + if (igSliderFloat("##SoundLevel", &level, 0.0f, 100.0f, "%.0f%%", ImGuiSliderFlags_None)) { + app->audio.level = max(0.0f, min(level / 100.f, 1.f)); + } + igEndDisabled(); + + igEndTable(); + } +} + +static +void +app_win_settings_bindings( + struct app *app +) { + size_t bind; + + igTextWrapped("Bindings"); + igSpacing(); + igSeparator(); + igSpacing(); + + if (igBeginTabBar("##BindingsSettings", ImGuiTabBarFlags_None)) { + if (igBeginTabItem("Keyboard", NULL, ImGuiTabItemFlags_None)) { + if (igBeginTable("##BindingsSettingsKeyboard", 3, ImGuiTableFlags_None, (ImVec2){ .x = 0.f, .y = 0.f }, 0.f)) { + igTableSetupColumn("##BindingsSettingsKeyboardLabel", ImGuiTableColumnFlags_WidthStretch, 1.f, 0); + igTableSetupColumn("##BindingsSettingsKeyboardBindMain", ImGuiTableColumnFlags_WidthStretch, 1.f, 0); + igTableSetupColumn("##BindingsSettingsKeyboardBindAlt", ImGuiTableColumnFlags_WidthStretch, 1.f, 0); + + for (bind = BIND_MIN; bind < BIND_MAX; ++bind) { + size_t j; + + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + + igTableNextColumn(); + igTextWrapped(binds_pretty_name[bind]); + + for (j = 0; j < 2; ++j) { + SDL_Keycode *keycode; + char const *name; + char label[128]; + + keycode = (j == 0) ? &app->binds.keyboard[bind] : &app->binds.keyboard_alt[bind]; + name = SDL_GetKeyName(*keycode); + + if (keycode == app->ui.settings.keybindings_editor.keyboard_target) { + snprintf(label, sizeof(label), ">> %s <<##BindingsSettingsKeyboard%zu", name ? name : " ", bind * 10 + j); + } else { + snprintf(label, sizeof(label), "%s##BindingsSettingsKeyboard%zu", name ? name : "", bind * 10 + j); + } + + igTableNextColumn(); + if (igButton(label, (ImVec2){ -1.f, 0.f })) { + app->ui.settings.keybindings_editor.keyboard_target = keycode; + app->ui.settings.keybindings_editor.controller_target = NULL; + } + } + } + igEndTable(); + } + igEndTabItem(); + } + if (igBeginTabItem("Controller", NULL, ImGuiTabItemFlags_None)) { + if (igBeginTable("##BindingsSettingsController", 3, ImGuiTableFlags_None, (ImVec2){ .x = 0.f, .y = 0.f }, 0.f)) { + igTableSetupColumn("##BindingsSettingsControllerLabel", ImGuiTableColumnFlags_WidthStretch, 1.f, 0); + igTableSetupColumn("##BindingsSettingsControllerBindMain", ImGuiTableColumnFlags_WidthStretch, 1.f, 0); + igTableSetupColumn("##BindingsSettingsControllerBindAlt", ImGuiTableColumnFlags_WidthStretch, 1.f, 0); + + for (bind = BIND_MIN; bind < BIND_MAX; ++bind) { + size_t j; + + igTableNextRow(ImGuiTableRowFlags_None, 0.f); + + igTableNextColumn(); + igTextWrapped(binds_pretty_name[bind]); + + for (j = 0; j < 2; ++j) { + SDL_GameControllerButton *button; + char const *name; + char label[128]; + + button = (j == 0) ? &app->binds.controller[bind] : &app->binds.controller_alt[bind]; + name = SDL_GameControllerGetStringForButton(*button); + + if (button == app->ui.settings.keybindings_editor.controller_target) { + snprintf(label, sizeof(label), ">> %s <<##BindingsSettingsController%zu", name ? name : " ", bind * 10 + j); + } else { + snprintf(label, sizeof(label), "%s##BindingsSettingsController%zu", name ? name : "", bind * 10 + j); + } + + igTableNextColumn(); + if (igButton(label, (ImVec2){ -1.f, 0.f })) { + app->ui.settings.keybindings_editor.controller_target = button; + app->ui.settings.keybindings_editor.keyboard_target = NULL; + } + } + } + igEndTable(); + } + igEndTabItem(); + } + igEndTabBar(); + } +} + +static void (*menu_callbacks[MENU_MAX])(struct app *) = { + [MENU_EMULATION] = &app_win_settings_emulation, + [MENU_VIDEO] = &app_win_settings_video, + [MENU_AUDIO] = &app_win_settings_audio, + [MENU_BINDINGS] = &app_win_settings_bindings, +}; + +void +app_win_settings( + struct app *app +) { + ImGuiViewport *vp; + + vp = igGetMainViewport(); + + igSetNextWindowPos(vp->WorkPos, ImGuiCond_Always, (ImVec2){0.f, 0.f}); + igSetNextWindowSize(vp->WorkSize, ImGuiCond_Always); + + if (igBegin( + "Settings", + NULL, + ImGuiWindowFlags_None + | ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_NoResize + | ImGuiWindowFlags_AlwaysAutoResize + | ImGuiWindowFlags_NoTitleBar + | ImGuiWindowFlags_NoNavInputs + | ImGuiWindowFlags_NoNavFocus + )) { + uint32_t i; + + if (igBeginChild_Str("##SettingsMenu", (ImVec2){ vp->WorkSize.x / 4.f, 0.f}, ImGuiChildFlags_Border , ImGuiWindowFlags_None)) { + for (i = 0; i < MENU_MAX; ++i) { + if (igSelectable_Bool(menu_names[i], app->ui.settings.menu == i, ImGuiSelectableFlags_None, (ImVec2){ 0.f, 0.f})) { + app->ui.settings.menu = i; + } + } + igEndChild(); + } + + igSameLine(0.0f, -1.0f); + + igBeginGroup(); + + if (igBeginChild_Str("##SettingsVariables", (ImVec2){ 0.f, -igGetFrameHeightWithSpacing()}, ImGuiChildFlags_Border, ImGuiWindowFlags_None)) { + if (menu_callbacks[app->ui.settings.menu]) { + menu_callbacks[app->ui.settings.menu](app); + } + igEndChild(); + } + + if (igButton("Close", (ImVec2){ 0.f, 0.f})) { + app->ui.settings.open = false; + } + + igEndGroup(); + + igEnd(); + } +}