diff options
author | Michael Pavone <pavone@retrodev.com> | 2018-03-25 12:01:49 -0700 |
---|---|---|
committer | Michael Pavone <pavone@retrodev.com> | 2018-03-25 12:01:49 -0700 |
commit | a5d5c017589945008024f111c06894f5724d6c3b (patch) | |
tree | a06db423e81d53aee66cfa0b0ca366bd57d71dc8 /nuklear_ui/blastem_nuklear.c | |
parent | 15af9462392967b6adf7ba6ff4f7ff778cf10eb3 (diff) | |
parent | b6feecb6c12566e9ea826cb67701008f9e423eea (diff) |
Merged nuklear_ui into default
Diffstat (limited to 'nuklear_ui/blastem_nuklear.c')
-rw-r--r-- | nuklear_ui/blastem_nuklear.c | 964 |
1 files changed, 964 insertions, 0 deletions
diff --git a/nuklear_ui/blastem_nuklear.c b/nuklear_ui/blastem_nuklear.c new file mode 100644 index 0000000..59d5a3b --- /dev/null +++ b/nuklear_ui/blastem_nuklear.c @@ -0,0 +1,964 @@ +#define NK_IMPLEMENTATION +#define NK_SDL_GLES2_IMPLEMENTATION + +#include <stdlib.h> +#include "blastem_nuklear.h" +#include "font.h" +#include "../render.h" +#include "../render_sdl.h" +#include "../util.h" +#include "../paths.h" +#include "../saves.h" +#include "../blastem.h" +#include "../config.h" +#include "../io.h" + +static struct nk_context *context; + +typedef void (*view_fun)(struct nk_context *); +static view_fun current_view; +static view_fun *previous_views; +static uint32_t view_storage; +static uint32_t num_prev; + +static void push_view(view_fun new_view) +{ + if (num_prev == view_storage) { + view_storage = view_storage ? 2*view_storage : 2; + previous_views = realloc(previous_views, view_storage*sizeof(view_fun)); + } + previous_views[num_prev++] = current_view; + current_view = new_view; +} + +static void pop_view() +{ + if (num_prev) { + current_view = previous_views[--num_prev]; + } +} + +static void clear_view_stack() +{ + num_prev = 0; +} + +void view_play(struct nk_context *context) +{ + +} + +void view_file_browser(struct nk_context *context, uint8_t normal_open) +{ + static char *current_path; + static dir_entry *entries; + static size_t num_entries; + static uint32_t selected_entry; + static char **ext_list; + static uint32_t num_exts; + static uint8_t got_ext_list; + if (!current_path) { + get_initial_browse_path(¤t_path); + } + if (!entries) { + entries = get_dir_list(current_path, &num_entries); + if (entries) { + sort_dir_list(entries, num_entries); + } + } + if (!got_ext_list) { + ext_list = get_extension_list(config, &num_exts); + got_ext_list = 1; + } + uint32_t width = render_width(); + uint32_t height = render_height(); + if (nk_begin(context, "Load ROM", nk_rect(0, 0, width, height), 0)) { + nk_layout_row_static(context, height - 100, width - 60, 1); + if (nk_group_begin(context, "Select ROM", NK_WINDOW_BORDER | NK_WINDOW_TITLE)) { + nk_layout_row_static(context, 28, width-100, 1); + for (uint32_t i = 0; i < num_entries; i++) + { + if (entries[i].name[0] == '.' && entries[i].name[1] != '.') { + continue; + } + if (num_exts && !entries[i].is_dir && !path_matches_extensions(entries[i].name, ext_list, num_exts)) { + continue; + } + int selected = i == selected_entry; + nk_selectable_label(context, entries[i].name, NK_TEXT_ALIGN_LEFT, &selected); + if (selected) { + selected_entry = i; + } + } + nk_group_end(context); + } + nk_layout_row_static(context, 52, width > 600 ? 300 : width / 2, 2); + if (nk_button_label(context, "Back")) { + pop_view(); + } + if (nk_button_label(context, "Open")) { + char *full_path = path_append(current_path, entries[selected_entry].name); + if (entries[selected_entry].is_dir) { + free(current_path); + current_path = full_path; + free_dir_list(entries, num_entries); + entries = NULL; + } else { + if(normal_open) { + if (current_system) { + current_system->next_rom = full_path; + current_system->request_exit(current_system); + } else { + init_system_with_media(full_path, SYSTEM_UNKNOWN); + free(full_path); + } + } else { + lockon_media(full_path); + free(full_path); + } + clear_view_stack(); + current_view = view_play; + } + } + nk_end(context); + } +} + +void view_load(struct nk_context *context) +{ + view_file_browser(context, 1); +} + +void view_lock_on(struct nk_context *context) +{ + view_file_browser(context, 0); +} + +void view_about(struct nk_context *context) +{ + const char *lines[] = { + "BlastEm v0.6.0", + "Copyright 2012-2017 Michael Pavone", + "", + "BlastEm is a high performance open source", + "(GPLv3) Genesis/Megadrive emulator", + }; + const uint32_t NUM_LINES = sizeof(lines)/sizeof(*lines); + const char *thanks[] = { + "Nemesis: Documentatino and test ROMs", + "Charles MacDonald: Documentation", + "Eke-Eke: Documentation", + "Bart Trzynadlowski: Documentation", + "KanedaFR: Hosting the best Sega forum", + "Titan: Awesome demos and documentation", + "micky: Testing", + "Sasha: Testing", + "lol-frank: Testing", + "Sik: Testing", + "Tim Lawrence : Testing", + "ComradeOj: Testing", + "Vladikcomper: Testing" + }; + const uint32_t NUM_THANKS = sizeof(thanks)/sizeof(*thanks); + uint32_t width = render_width(); + uint32_t height = render_height(); + if (nk_begin(context, "About", nk_rect(0, 0, width, height), 0)) { + nk_layout_row_static(context, 30, width-40, 1); + for (uint32_t i = 0; i < NUM_LINES; i++) + { + nk_label(context, lines[i], NK_TEXT_LEFT); + } + nk_layout_row_static(context, height - 80 - 34*NUM_LINES, width-40, 1); + if (nk_group_begin(context, "Special Thanks", NK_WINDOW_TITLE)) { + nk_layout_row_static(context, 30, width - 80, 1); + for (uint32_t i = 0; i < NUM_THANKS; i++) + { + nk_label(context, thanks[i], NK_TEXT_LEFT); + } + nk_group_end(context); + } + nk_layout_row_static(context, 52, width/3, 1); + if (nk_button_label(context, "Back")) { + pop_view(); + } + nk_end(context); + } +} + +typedef struct { + const char *title; + view_fun next_view; +} menu_item; + +static save_slot_info *slots; +static uint32_t num_slots, selected_slot; + +void view_choose_state(struct nk_context *context, uint8_t is_load) +{ + uint32_t width = render_width(); + uint32_t height = render_height(); + if (nk_begin(context, "Slot Picker", nk_rect(0, 0, width, height), 0)) { + nk_layout_row_static(context, height - 100, width - 60, 1); + if (nk_group_begin(context, "Select Save Slot", NK_WINDOW_BORDER | NK_WINDOW_TITLE)) { + nk_layout_row_static(context, 28, width-100, 1); + if (!slots) { + slots = get_slot_info(current_system, &num_slots); + } + for (uint32_t i = 0; i < num_slots; i++) + { + int selected = i == selected_slot; + nk_selectable_label(context, slots[i].desc, NK_TEXT_ALIGN_LEFT, &selected); + if (selected && (slots[i].modification_time || !is_load)) { + selected_slot = i; + } + } + nk_group_end(context); + } + nk_layout_row_static(context, 52, width > 600 ? 300 : width / 2, 2); + if (nk_button_label(context, "Back")) { + pop_view(); + } + if (is_load) { + if (nk_button_label(context, "Load")) { + current_system->load_state(current_system, selected_slot); + current_view = view_play; + } + } else { + if (nk_button_label(context, "Save")) { + current_system->save_state = selected_slot + 1; + current_view = view_play; + } + } + nk_end(context); + } +} + +void view_save_state(struct nk_context *context) +{ + view_choose_state(context, 0); +} + +void view_load_state(struct nk_context *context) +{ + view_choose_state(context, 1); +} + +static void menu(struct nk_context *context, uint32_t num_entries, const menu_item *items) +{ + const uint32_t button_height = 52; + const uint32_t ideal_button_width = 300; + const uint32_t button_space = 6; + + uint32_t width = render_width(); + uint32_t height = render_height(); + uint32_t top = height/2 - (button_height * num_entries)/2; + uint32_t button_width = width > ideal_button_width ? ideal_button_width : width; + uint32_t left = width/2 - button_width/2; + + nk_layout_space_begin(context, NK_STATIC, top + button_height * num_entries, num_entries); + for (uint32_t i = 0; i < num_entries; i++) + { + nk_layout_space_push(context, nk_rect(left, top + i * button_height, button_width, button_height-button_space)); + if (nk_button_label(context, items[i].title)) { + push_view(items[i].next_view); + if (!current_view) { + exit(0); + } + if (current_view == view_save_state || current_view == view_load_state) { + free_slot_info(slots); + slots = NULL; + } + } + } + nk_layout_space_end(context); +} + +void binding_loop(char *key, tern_val val, uint8_t valtype, void *data) +{ + if (valtype != TVAL_PTR) { + return; + } + tern_node **binding_lookup = data; + *binding_lookup = tern_insert_ptr(*binding_lookup, val.ptrval, strdup(key)); +} + +static int32_t keycode; +static const char *set_binding; +char *set_label; +void binding_group(struct nk_context *context, char *name, const char **binds, const char **bind_names, uint32_t num_binds, tern_node *binding_lookup) +{ + nk_layout_row_static(context, 34*num_binds+60, render_width() - 80, 1); + if (nk_group_begin(context, name, NK_WINDOW_TITLE)) { + nk_layout_row_static(context, 30, render_width()/2 - 80, 2); + + for (int i = 0; i < num_binds; i++) + { + char *label_alloc = bind_names ? NULL : path_extension(binds[i]); + const char *label = label_alloc; + if (!label) { + label = bind_names ? bind_names[i] : binds[i]; + } + nk_label(context, label, NK_TEXT_LEFT); + if (nk_button_label(context, tern_find_ptr_default(binding_lookup, binds[i], "Not Set"))) { + set_binding = binds[i]; + set_label = strdup(label); + keycode = 0; + } + if (label_alloc) { + free(label_alloc); + } + } + nk_group_end(context); + } +} + +static char *get_key_name(int32_t keycode) +{ + char *name = NULL; + if (keycode > ' ' && keycode < 0x80) { + //key corresponds to a printable non-whitespace character + name = malloc(2); + name[0] = keycode; + name[1] = 0; + } else { + switch (keycode) + { + case RENDERKEY_UP: name = "up"; break; + case RENDERKEY_DOWN: name = "down"; break; + case RENDERKEY_LEFT: name = "left"; break; + case RENDERKEY_RIGHT: name = "right"; break; + case '\r': name = "enter"; break; + case ' ': name = "space"; break; + case '\t': name = "tab"; break; + case '\b': name = "backspace"; break; + case RENDERKEY_ESC: name = "esc"; break; + case RENDERKEY_DEL: name = "delete"; break; + case RENDERKEY_LSHIFT: name = "lshift"; break; + case RENDERKEY_RSHIFT: name = "rshift"; break; + case RENDERKEY_LCTRL: name = "lctrl"; break; + case RENDERKEY_RCTRL: name = "rctrl"; break; + case RENDERKEY_LALT: name = "lalt"; break; + case RENDERKEY_RALT: name = "ralt"; break; + case RENDERKEY_HOME: name = "home"; break; + case RENDERKEY_END: name = "end"; break; + case RENDERKEY_PAGEUP: name = "pageup"; break; + case RENDERKEY_PAGEDOWN: name = "pagedown"; break; + case RENDERKEY_F1: name = "f1"; break; + case RENDERKEY_F2: name = "f2"; break; + case RENDERKEY_F3: name = "f3"; break; + case RENDERKEY_F4: name = "f4"; break; + case RENDERKEY_F5: name = "f5"; break; + case RENDERKEY_F6: name = "f6"; break; + case RENDERKEY_F7: name = "f7"; break; + case RENDERKEY_F8: name = "f8"; break; + case RENDERKEY_F9: name = "f9"; break; + case RENDERKEY_F10: name = "f10"; break; + case RENDERKEY_F11: name = "f11"; break; + case RENDERKEY_F12: name = "f12"; break; + case RENDERKEY_SELECT: name = "select"; break; + case RENDERKEY_PLAY: name = "play"; break; + case RENDERKEY_SEARCH: name = "search"; break; + case RENDERKEY_BACK: name = "back"; break; + } + if (name) { + name = strdup(name); + } + } + return name; +} + +void view_key_bindings(struct nk_context *context) +{ + const char *controller1_binds[] = { + "gamepads.1.up", "gamepads.1.down", "gamepads.1.left", "gamepads.1.right", + "gamepads.1.a", "gamepads.1.b", "gamepads.1.c", + "gamepads.1.x", "gamepads.1.y", "gamepads.1.z", + "gamepads.1.start", "gamepads.1.mode" + }; + const char *controller2_binds[] = { + "gamepads.2.up", "gamepads.2.down", "gamepads.2.left", "gamepads.2.right", + "gamepads.2.a", "gamepads.2.b", "gamepads.2.c", + "gamepads.2.x", "gamepads.2.y", "gamepads.2.z", + "gamepads.2.start", "gamepads.2.mode" + }; + const char *general_binds[] = { + "ui.exit", "ui.save_state", "ui.toggle_fullscreen", "ui.soft_reset", "ui.reload", + "ui.screenshot", "ui.sms_pause", "ui.toggle_keyboard_cpatured", "ui.release_mouse" + }; + const char *general_names[] = { + "Show Menu", "Quick Save", "Toggle Fullscreen", "Soft Reset", "Reload Media", + "Internal Screenshot", "SMS Pause", "Capture Keyboard", "Release Mouse" + }; + const char *speed_binds[] = { + "ui.next_speed", "ui.prev_speed", + "ui.set_speed.0", "ui.set_speed.1", "ui.set_speed.2" ,"ui.set_speed.3", "ui.set_speed.4", + "ui.set_speed.5", "ui.set_speed.6", "ui.set_speed.7" ,"ui.set_speed.8", "ui.set_speed.9", + }; + const char *speed_names[] = { + "Next", "Previous", + "Default Speed", "Set Speed 1", "Set Speed 2", "Set Speed 3", "Set Speed 4", + "Set Speed 5", "Set Speed 6", "Set Speed 7", "Set Speed 8", "Set Speed 9" + }; + const char *debug_binds[] = { + "ui.enter_debugger", "ui.vdp_debug_mode", "ui.vdp_debug_pal" + }; + const char *debug_names[] = { + "Enter Debugger", "VDP Debug Mode", "Debug Palette" + }; + const uint32_t NUM_C1_BINDS = sizeof(controller1_binds)/sizeof(*controller1_binds); + const uint32_t NUM_C2_BINDS = sizeof(controller2_binds)/sizeof(*controller2_binds); + const uint32_t NUM_SPEED_BINDS = sizeof(speed_binds)/sizeof(*speed_binds); + const uint32_t NUM_GEN_BINDS = sizeof(general_binds)/sizeof(*general_binds); + const uint32_t NUM_DBG_BINDS = sizeof(debug_binds)/sizeof(*debug_binds); + static tern_node *binding_lookup; + if (!binding_lookup) { + tern_node *bindings = tern_find_path(config, "bindings\0keys\0", TVAL_NODE).ptrval; + if (bindings) { + tern_foreach(bindings, binding_loop, &binding_lookup); + } + } + uint32_t width = render_width(); + uint32_t height = render_height(); + if (nk_begin(context, "Keyboard Bindings", nk_rect(0, 0, width, height), 0)) { + binding_group(context, "Controller 1", controller1_binds, NULL, NUM_C1_BINDS, binding_lookup); + binding_group(context, "Controller 2", controller2_binds, NULL, NUM_C2_BINDS, binding_lookup); + binding_group(context, "General", general_binds, general_names, NUM_GEN_BINDS, binding_lookup); + binding_group(context, "Speed Control", speed_binds, speed_names, NUM_SPEED_BINDS, binding_lookup); + binding_group(context, "Debug", debug_binds, debug_names, NUM_DBG_BINDS, binding_lookup); + + nk_end(context); + } + if (set_binding && nk_begin(context, "Set Binding", nk_rect(width/4, height/4, width/2/*width*3/4*/, height/2), NK_WINDOW_TITLE | NK_WINDOW_BORDER)) { + nk_layout_row_static(context, 30, width/2-30, 1); + nk_label(context, "Press new key for", NK_TEXT_CENTERED); + nk_label(context, set_label, NK_TEXT_CENTERED); + if (nk_button_label(context, "Cancel")) { + free(set_label); + set_binding = set_label = NULL; + } else if (keycode) { + char *name = get_key_name(keycode); + if (name) { + uint32_t prefix_len = strlen("bindings") + strlen("keys") + 2; + char * old = tern_find_ptr(binding_lookup, set_binding); + if (old) { + uint32_t suffix_len = strlen(old) + 1; + char *old_path = malloc(prefix_len + suffix_len + 1); + memcpy(old_path, "bindings\0keys\0", prefix_len); + memcpy(old_path + prefix_len, old, suffix_len); + old_path[prefix_len + suffix_len] = 0; + tern_val old_val; + if (tern_delete_path(&config, old_path, &old_val) == TVAL_PTR) { + free(old_val.ptrval); + } + } + uint32_t suffix_len = strlen(name) + 1; + char *path = malloc(prefix_len + suffix_len + 1); + memcpy(path, "bindings\0keys\0", prefix_len); + memcpy(path + prefix_len, name, suffix_len); + path[prefix_len + suffix_len] = 0; + + config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(set_binding)}, TVAL_PTR); + free(path); + free(name); + tern_free(binding_lookup); + binding_lookup = NULL; + } + free(set_label); + set_binding = set_label = NULL; + } + nk_end(context); + } +} +void view_controllers(struct nk_context *context) +{ + +} + +void settings_toggle(struct nk_context *context, char *label, char *path, uint8_t def) +{ + uint8_t curval = !strcmp("on", tern_find_path_default(config, path, (tern_val){.ptrval = def ? "on": "off"}, TVAL_PTR).ptrval); + nk_label(context, label, NK_TEXT_LEFT); + uint8_t newval = nk_check_label(context, "", curval); + if (newval != curval) { + config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(newval ? "on" : "off")}, TVAL_PTR); + } +} + +void settings_int_input(struct nk_context *context, char *label, char *path, char *def) +{ + char buffer[12]; + nk_label(context, label, NK_TEXT_LEFT); + uint32_t curval; + char *curstr = tern_find_path_default(config, path, (tern_val){.ptrval = def}, TVAL_PTR).ptrval; + uint32_t len = strlen(curstr); + if (len > 11) { + len = 11; + } + memcpy(buffer, curstr, len); + nk_edit_string(context, NK_EDIT_SIMPLE, buffer, &len, sizeof(buffer)-1, nk_filter_decimal); + buffer[len] = 0; + if (strcmp(buffer, curstr)) { + config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(buffer)}, TVAL_PTR); + } +} + +void settings_int_property(struct nk_context *context, char *label, char *name, char *path, int def, int min, int max) +{ + char *curstr = tern_find_path(config, path, TVAL_PTR).ptrval; + int curval = curstr ? atoi(curstr) : def; + nk_label(context, label, NK_TEXT_LEFT); + int val = curval; + nk_property_int(context, name, min, &val, max, 1, 1.0f); + if (val != curval) { + char buffer[12]; + sprintf(buffer, "%d", val); + config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(buffer)}, TVAL_PTR); + } +} + +typedef struct { + char *fragment; + char *vertex; +} shader_prog; + +shader_prog *get_shader_progs(dir_entry *entries, size_t num_entries, shader_prog *progs, uint32_t *num_existing, uint32_t *storage) +{ + uint32_t num_progs = *num_existing; + uint32_t prog_storage = *storage; + uint32_t starting = num_progs; + + for (uint32_t i = 0; i < num_entries; i++) { + if (entries[i].is_dir) { + continue; + } + char *no_ext = basename_no_extension(entries[i].name); + uint32_t len = strlen(no_ext); + if (no_ext[len-1] == 'f' && no_ext[len-2] == '.') { + uint8_t dupe = 0;; + for (uint32_t j = 0; j < starting; j++) { + if (!strcmp(entries[i].name, progs[j].fragment)) { + dupe = 1; + break; + } + } + if (!dupe) { + if (num_progs == prog_storage) { + prog_storage = prog_storage ? prog_storage*2 : 4; + progs = realloc(progs, sizeof(progs) * prog_storage); + } + progs[num_progs].vertex = NULL; + progs[num_progs++].fragment = strdup(entries[i].name); + } + } + free(no_ext); + } + + for (uint32_t i = 0; i < num_entries; i++) { + if (entries[i].is_dir) { + continue; + } + char *no_ext = basename_no_extension(entries[i].name); + uint32_t len = strlen(no_ext); + if (no_ext[len-1] == 'v' && no_ext[len-2] == '.') { + for (uint32_t j = 0; j < num_progs; j++) { + if (!strncmp(no_ext, progs[j].fragment, len-1) && progs[j].fragment[len-1] == 'f' && progs[j].fragment[len] == '.') { + progs[j].vertex = strdup(entries[i].name); + } + } + } + free(no_ext); + } + free_dir_list(entries, num_entries); + *num_existing = num_progs; + *storage = prog_storage; + return progs; +} + +shader_prog *get_shader_list(uint32_t *num_out) +{ + char *shader_dir = path_append(get_config_dir(), "shaders"); + size_t num_entries; + dir_entry *entries = get_dir_list(shader_dir, &num_entries); + free(shader_dir); + shader_prog *progs; + uint32_t num_progs = 0, prog_storage; + if (num_entries) { + progs = calloc(num_entries, sizeof(shader_prog)); + prog_storage = num_entries; + progs = get_shader_progs(entries, num_entries, progs, &num_progs, &prog_storage); + } else { + progs = NULL; + prog_storage = 0; + } + shader_dir = path_append(get_exe_dir(), "shaders"); + entries = get_dir_list(shader_dir, &num_entries); + progs = get_shader_progs(entries, num_entries, progs, &num_progs, &prog_storage); + *num_out = num_progs; + return progs; +} + +void view_video_settings(struct nk_context *context) +{ + static shader_prog *progs; + static char **prog_names; + static uint32_t num_progs; + static uint32_t selected_prog; + if(!progs) { + progs = get_shader_list(&num_progs); + prog_names = calloc(num_progs, sizeof(char*)); + for (uint32_t i = 0; i < num_progs; i++) + { + prog_names[i] = basename_no_extension(progs[i].fragment);; + uint32_t len = strlen(prog_names[i]); + if (len > 2) { + prog_names[i][len-2] = 0; + } + if (!progs[i].vertex) { + progs[i].vertex = strdup("default.v.glsl"); + } + if (!strcmp( + progs[i].fragment, + tern_find_path_default(config, "video\0fragment_shader\0", (tern_val){.ptrval = "default.f.glsl"}, TVAL_PTR).ptrval + )) { + selected_prog = i; + } + } + } + uint32_t width = render_width(); + uint32_t height = render_height(); + if (nk_begin(context, "Video Settings", nk_rect(0, 0, width, height), 0)) { + nk_layout_row_static(context, 30, width > 300 ? 300 : width, 2); + settings_toggle(context, "Fullscreen", "video\0fullscreen\0", 0); + settings_toggle(context, "Open GL", "video\0gl\0", 1); + settings_toggle(context, "Scanlines", "video\0scanlines\0", 0); + settings_int_input(context, "Windowed Width", "video\0width\0", "640"); + nk_label(context, "Shader", NK_TEXT_LEFT); + uint32_t next_selected = nk_combo(context, (const char **)prog_names, num_progs, selected_prog, 30, nk_vec2(300, 300)); + if (next_selected != selected_prog) { + selected_prog = next_selected; + config = tern_insert_path(config, "video\0fragment_shader\0", (tern_val){.ptrval = strdup(progs[next_selected].fragment)}, TVAL_PTR); + config = tern_insert_path(config, "video\0vertex_shader\0", (tern_val){.ptrval = strdup(progs[next_selected].vertex)}, TVAL_PTR); + } + settings_int_property(context, "NTSC Overscan", "Top", "video\0ntsc\0overscan\0top\0", 2, 0, 32); + settings_int_property(context, "", "Bottom", "video\0ntsc\0overscan\0bottom\0", 17, 0, 32); + settings_int_property(context, "", "Left", "video\0ntsc\0overscan\0left\0", 13, 0, 32); + settings_int_property(context, "", "Right", "video\0ntsc\0overscan\0right\0", 14, 0, 32); + settings_int_property(context, "PAL Overscan", "Top", "video\0pal\0overscan\0top\0", 2, 0, 32); + settings_int_property(context, "", "Bottom", "video\0pal\0overscan\0bottom\0", 17, 0, 32); + settings_int_property(context, "", "Left", "video\0pal\0overscan\0left\0", 13, 0, 32); + settings_int_property(context, "", "Right", "video\0pal\0overscan\0right\0", 14, 0, 32); + + if (nk_button_label(context, "Back")) { + pop_view(); + } + nk_end(context); + } +} + +int32_t find_match(const char **options, uint32_t num_options, char *path, char *def) +{ + char *setting = tern_find_path_default(config, path, (tern_val){.ptrval = def}, TVAL_PTR).ptrval; + int32_t selected = -1; + for (uint32_t i = 0; i < num_options; i++) + { + if (!strcmp(setting, options[i])) { + selected = i; + break; + } + } + if (selected == -1) { + for (uint32_t i = 0; i < num_options; i++) + { + if (!strcmp(def, options[i])) { + selected = i; + break; + } + } + } + return selected; +} + +int32_t settings_dropdown_ex(struct nk_context *context, char *label, const char **options, const char **opt_display, uint32_t num_options, int32_t current, char *path) +{ + nk_label(context, label, NK_TEXT_LEFT); + int32_t next = nk_combo(context, opt_display, num_options, current, 30, nk_vec2(300, 300)); + if (next != current) { + config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(options[next])}, TVAL_PTR); + } + return next; +} + +int32_t settings_dropdown(struct nk_context *context, char *label, const char **options, uint32_t num_options, int32_t current, char *path) +{ + return settings_dropdown_ex(context, label, options, options, num_options, current, path); +} + +void view_audio_settings(struct nk_context *context) +{ + const char *rates[] = { + "192000", + "96000", + "48000", + "44100", + "22050" + }; + const char *sizes[] = { + "1024", + "512", + "256", + "128", + "64" + }; + const uint32_t num_rates = sizeof(rates)/sizeof(*rates); + const uint32_t num_sizes = sizeof(sizes)/sizeof(*sizes); + static int32_t selected_rate = -1; + static int32_t selected_size = -1; + if (selected_rate < 0 || selected_size < 0) { + selected_rate = find_match(rates, num_rates, "autio\0rate\0", "48000"); + selected_size = find_match(sizes, num_sizes, "audio\0buffer\0", "512"); + } + uint32_t width = render_width(); + uint32_t height = render_height(); + if (nk_begin(context, "Audio Settings", nk_rect(0, 0, width, height), 0)) { + nk_layout_row_static(context, 30, width > 300 ? 300 : width, 2); + selected_rate = settings_dropdown(context, "Rate in Hz", rates, num_rates, selected_rate, "audio\0rate\0"); + selected_size = settings_dropdown(context, "Buffer Samples", sizes, num_sizes, selected_size, "audio\0buffer\0"); + settings_int_input(context, "Lowpass Cutoff Hz", "audio\0lowpass_cutoff\0", "3390"); + if (nk_button_label(context, "Back")) { + pop_view(); + } + nk_end(context); + } +} +void view_system_settings(struct nk_context *context) +{ + const char *regions[] = { + "J - Japan", + "U - Americas", + "E - Europe" + }; + const char *region_codes[] = {"J", "U", "E"}; + const uint32_t num_regions = sizeof(regions)/sizeof(*regions); + static int32_t selected_region = -1; + if (selected_region < 0) { + selected_region = find_match(region_codes, num_regions, "system\0default_region\0", "U"); + } + const char *formats[] = { + "native", + "gst" + }; + const uint32_t num_formats = sizeof(formats)/sizeof(*formats); + int32_t selected_format = -1; + if (selected_format < 0) { + selected_format = find_match(formats, num_formats, "ui\0state_format\0", "native"); + } + const char *ram_inits[] = { + "zero", + "random" + }; + const uint32_t num_inits = sizeof(ram_inits)/sizeof(*ram_inits); + static int32_t selected_init = -1; + if (selected_init < 0) { + selected_init = find_match(ram_inits, num_inits, "system\0ram_init\0", "zero"); + } + const char *io_opts_1[] = { + "gamepad2.1", + "gamepad3.1", + "gamepad6.1", + "mouse", + "saturn keyboard", + "xband keyboard" + }; + const char *io_opts_2[] = { + "gamepad2.2", + "gamepad3.2", + "gamepad6.2", + "mouse", + "saturn keyboard", + "xband keyboard" + }; + static int32_t selected_io_1 = -1; + static int32_t selected_io_2 = -1; + const uint32_t num_io = sizeof(io_opts_1)/sizeof(*io_opts_1); + if (selected_io_1 < 0 || selected_io_2 < 0) { + selected_io_1 = find_match(io_opts_1, num_io, "io\0devices\0""1\0", "gamepad6.1"); + selected_io_2 = find_match(io_opts_2, num_io, "io\0devices\0""2\0", "gamepad6.2"); + } + + uint32_t width = render_width(); + uint32_t height = render_height(); + if (nk_begin(context, "System Settings", nk_rect(0, 0, width, height), 0)) { + nk_layout_row_static(context, 30, width > 300 ? 300 : width, 2); + settings_int_property(context, "68000 Clock Divider", "", "clocks\0m68k_divider\0", 7, 1, 53); + settings_toggle(context, "Remember ROM Path", "ui\0remember_path\0", 1); + selected_region = settings_dropdown_ex(context, "Default Region", region_codes, regions, num_regions, selected_region, "system\0default_region\0"); + selected_format = settings_dropdown(context, "Save State Format", formats, num_formats, selected_format, "ui\0state_format\0"); + selected_init = settings_dropdown(context, "Initial RAM Value", ram_inits, num_inits, selected_init, "system\0ram_init\0"); + selected_io_1 = settings_dropdown_ex(context, "IO Port 1 Device", io_opts_1, device_type_names, num_io, selected_io_1, "io\0devices\0""1\0"); + selected_io_2 = settings_dropdown_ex(context, "IO Port 2 Device", io_opts_2, device_type_names, num_io, selected_io_2, "io\0devices\0""2\0"); + if (nk_button_label(context, "Back")) { + pop_view(); + } + nk_end(context); + } +} + +void view_back(struct nk_context *context) +{ + pop_view(); + pop_view(); + current_view(context); +} + +void view_settings(struct nk_context *context) +{ + static menu_item items[] = { + {"Key Bindings", view_key_bindings}, + {"Controllers", view_controllers}, + {"Video", view_video_settings}, + {"Audio", view_audio_settings}, + {"System", view_system_settings}, + {"Back", view_back} + }; + + const uint32_t num_buttons = 6; + if (nk_begin(context, "Settings Menu", nk_rect(0, 0, render_width(), render_height()), 0)) { + menu(context, sizeof(items)/sizeof(*items), items); + nk_end(context); + } +} + +void view_pause(struct nk_context *context) +{ + static menu_item items[] = { + {"Resume", view_play}, + {"Load ROM", view_load}, + {"Lock On", view_lock_on}, + {"Save State", view_save_state}, + {"Load State", view_load_state}, + {"Settings", view_settings}, + {"Exit", NULL} + }; + + const uint32_t num_buttons = 3; + if (nk_begin(context, "Main Menu", nk_rect(0, 0, render_width(), render_height()), 0)) { + menu(context, sizeof(items)/sizeof(*items), items); + nk_end(context); + } +} + +void view_menu(struct nk_context *context) +{ + static menu_item items[] = { + {"Load ROM", view_load}, + {"Settings", view_settings}, + {"About", view_about}, + {"Exit", NULL} + }; + + const uint32_t num_buttons = 3; + if (nk_begin(context, "Main Menu", nk_rect(0, 0, render_width(), render_height()), 0)) { + menu(context, sizeof(items)/sizeof(*items), items); + nk_end(context); + } +} + +void blastem_nuklear_render(void) +{ + nk_input_end(context); + current_view(context); + nk_sdl_render(NK_ANTI_ALIASING_ON, 512 * 1024, 128 * 1024); + nk_input_begin(context); +} + +void ui_idle_loop(void) +{ + const uint32_t MIN_UI_DELAY = 15; + static uint32_t last; + while (current_view != view_play) + { + uint32_t current = render_elapsed_ms(); + if ((current - last) < MIN_UI_DELAY) { + render_sleep_ms(MIN_UI_DELAY - (current - last) - 1); + } + last = current; + render_update_display(); + } +} +static void handle_event(SDL_Event *event) +{ + if (event->type == SDL_KEYDOWN) { + keycode = event->key.keysym.sym; + } + nk_sdl_handle_event(event); +} + +static void context_destroyed(void) +{ + nk_sdl_device_destroy(); +} +static void context_created(void) +{ + nk_sdl_device_create(); + struct nk_font_atlas *atlas; + nk_sdl_font_stash_begin(&atlas); + uint32_t font_size; + uint8_t *font = default_font(&font_size); + if (!font) { + fatal_error("Failed to find default font path\n"); + } + struct nk_font *def_font = nk_font_atlas_add_from_memory(atlas, font, font_size, 30, NULL); + nk_sdl_font_stash_end(); + nk_style_set_font(context, &def_font->handle); +} + +void show_pause_menu(void) +{ + context->style.window.background = nk_rgba(0, 0, 0, 128); + context->style.window.fixed_background = nk_style_item_color(nk_rgba(0, 0, 0, 128)); + current_view = view_pause; + current_system->request_exit(current_system); +} + +static uint8_t active; +uint8_t is_nuklear_active(void) +{ + return active; +} + +uint8_t is_nuklear_available(void) +{ + if (!render_has_gl()) { + //currently no fallback if GL2 unavailable + return 0; + } + char *style = tern_find_path(config, "ui\0style\0", TVAL_PTR).ptrval; + if (!style) { + return 1; + } + return strcmp(style, "rom") != 0; +} + +void blastem_nuklear_init(uint8_t file_loaded) +{ + context = nk_sdl_init(render_get_window()); + + struct nk_font_atlas *atlas; + nk_sdl_font_stash_begin(&atlas); + //char *font = default_font_path(); + uint32_t font_size; + uint8_t *font = default_font(&font_size); + if (!font) { + fatal_error("Failed to find default font path\n"); + } + //struct nk_font *def_font = nk_font_atlas_add_from_file(atlas, font, 30, NULL); + struct nk_font *def_font = nk_font_atlas_add_from_memory(atlas, font, font_size, 30, NULL); + nk_sdl_font_stash_end(); + nk_style_set_font(context, &def_font->handle); + current_view = file_loaded ? view_play : view_menu; + render_set_ui_render_fun(blastem_nuklear_render); + render_set_event_handler(handle_event); + render_set_gl_context_handlers(context_destroyed, context_created); + active = 1; + ui_idle_loop(); +} |