comparison nuklear_ui/blastem_nuklear.c @ 1541:f8ef74e7c800

Merged nuklear_ui into default
author Michael Pavone <pavone@retrodev.com>
date Sun, 25 Mar 2018 12:01:49 -0700
parents 4f6e8acd7b6a
children 3faf917bab56
comparison
equal deleted inserted replaced
1533:78b7fc03c7c6 1541:f8ef74e7c800
1 #define NK_IMPLEMENTATION
2 #define NK_SDL_GLES2_IMPLEMENTATION
3
4 #include <stdlib.h>
5 #include "blastem_nuklear.h"
6 #include "font.h"
7 #include "../render.h"
8 #include "../render_sdl.h"
9 #include "../util.h"
10 #include "../paths.h"
11 #include "../saves.h"
12 #include "../blastem.h"
13 #include "../config.h"
14 #include "../io.h"
15
16 static struct nk_context *context;
17
18 typedef void (*view_fun)(struct nk_context *);
19 static view_fun current_view;
20 static view_fun *previous_views;
21 static uint32_t view_storage;
22 static uint32_t num_prev;
23
24 static void push_view(view_fun new_view)
25 {
26 if (num_prev == view_storage) {
27 view_storage = view_storage ? 2*view_storage : 2;
28 previous_views = realloc(previous_views, view_storage*sizeof(view_fun));
29 }
30 previous_views[num_prev++] = current_view;
31 current_view = new_view;
32 }
33
34 static void pop_view()
35 {
36 if (num_prev) {
37 current_view = previous_views[--num_prev];
38 }
39 }
40
41 static void clear_view_stack()
42 {
43 num_prev = 0;
44 }
45
46 void view_play(struct nk_context *context)
47 {
48
49 }
50
51 void view_file_browser(struct nk_context *context, uint8_t normal_open)
52 {
53 static char *current_path;
54 static dir_entry *entries;
55 static size_t num_entries;
56 static uint32_t selected_entry;
57 static char **ext_list;
58 static uint32_t num_exts;
59 static uint8_t got_ext_list;
60 if (!current_path) {
61 get_initial_browse_path(&current_path);
62 }
63 if (!entries) {
64 entries = get_dir_list(current_path, &num_entries);
65 if (entries) {
66 sort_dir_list(entries, num_entries);
67 }
68 }
69 if (!got_ext_list) {
70 ext_list = get_extension_list(config, &num_exts);
71 got_ext_list = 1;
72 }
73 uint32_t width = render_width();
74 uint32_t height = render_height();
75 if (nk_begin(context, "Load ROM", nk_rect(0, 0, width, height), 0)) {
76 nk_layout_row_static(context, height - 100, width - 60, 1);
77 if (nk_group_begin(context, "Select ROM", NK_WINDOW_BORDER | NK_WINDOW_TITLE)) {
78 nk_layout_row_static(context, 28, width-100, 1);
79 for (uint32_t i = 0; i < num_entries; i++)
80 {
81 if (entries[i].name[0] == '.' && entries[i].name[1] != '.') {
82 continue;
83 }
84 if (num_exts && !entries[i].is_dir && !path_matches_extensions(entries[i].name, ext_list, num_exts)) {
85 continue;
86 }
87 int selected = i == selected_entry;
88 nk_selectable_label(context, entries[i].name, NK_TEXT_ALIGN_LEFT, &selected);
89 if (selected) {
90 selected_entry = i;
91 }
92 }
93 nk_group_end(context);
94 }
95 nk_layout_row_static(context, 52, width > 600 ? 300 : width / 2, 2);
96 if (nk_button_label(context, "Back")) {
97 pop_view();
98 }
99 if (nk_button_label(context, "Open")) {
100 char *full_path = path_append(current_path, entries[selected_entry].name);
101 if (entries[selected_entry].is_dir) {
102 free(current_path);
103 current_path = full_path;
104 free_dir_list(entries, num_entries);
105 entries = NULL;
106 } else {
107 if(normal_open) {
108 if (current_system) {
109 current_system->next_rom = full_path;
110 current_system->request_exit(current_system);
111 } else {
112 init_system_with_media(full_path, SYSTEM_UNKNOWN);
113 free(full_path);
114 }
115 } else {
116 lockon_media(full_path);
117 free(full_path);
118 }
119 clear_view_stack();
120 current_view = view_play;
121 }
122 }
123 nk_end(context);
124 }
125 }
126
127 void view_load(struct nk_context *context)
128 {
129 view_file_browser(context, 1);
130 }
131
132 void view_lock_on(struct nk_context *context)
133 {
134 view_file_browser(context, 0);
135 }
136
137 void view_about(struct nk_context *context)
138 {
139 const char *lines[] = {
140 "BlastEm v0.6.0",
141 "Copyright 2012-2017 Michael Pavone",
142 "",
143 "BlastEm is a high performance open source",
144 "(GPLv3) Genesis/Megadrive emulator",
145 };
146 const uint32_t NUM_LINES = sizeof(lines)/sizeof(*lines);
147 const char *thanks[] = {
148 "Nemesis: Documentatino and test ROMs",
149 "Charles MacDonald: Documentation",
150 "Eke-Eke: Documentation",
151 "Bart Trzynadlowski: Documentation",
152 "KanedaFR: Hosting the best Sega forum",
153 "Titan: Awesome demos and documentation",
154 "micky: Testing",
155 "Sasha: Testing",
156 "lol-frank: Testing",
157 "Sik: Testing",
158 "Tim Lawrence : Testing",
159 "ComradeOj: Testing",
160 "Vladikcomper: Testing"
161 };
162 const uint32_t NUM_THANKS = sizeof(thanks)/sizeof(*thanks);
163 uint32_t width = render_width();
164 uint32_t height = render_height();
165 if (nk_begin(context, "About", nk_rect(0, 0, width, height), 0)) {
166 nk_layout_row_static(context, 30, width-40, 1);
167 for (uint32_t i = 0; i < NUM_LINES; i++)
168 {
169 nk_label(context, lines[i], NK_TEXT_LEFT);
170 }
171 nk_layout_row_static(context, height - 80 - 34*NUM_LINES, width-40, 1);
172 if (nk_group_begin(context, "Special Thanks", NK_WINDOW_TITLE)) {
173 nk_layout_row_static(context, 30, width - 80, 1);
174 for (uint32_t i = 0; i < NUM_THANKS; i++)
175 {
176 nk_label(context, thanks[i], NK_TEXT_LEFT);
177 }
178 nk_group_end(context);
179 }
180 nk_layout_row_static(context, 52, width/3, 1);
181 if (nk_button_label(context, "Back")) {
182 pop_view();
183 }
184 nk_end(context);
185 }
186 }
187
188 typedef struct {
189 const char *title;
190 view_fun next_view;
191 } menu_item;
192
193 static save_slot_info *slots;
194 static uint32_t num_slots, selected_slot;
195
196 void view_choose_state(struct nk_context *context, uint8_t is_load)
197 {
198 uint32_t width = render_width();
199 uint32_t height = render_height();
200 if (nk_begin(context, "Slot Picker", nk_rect(0, 0, width, height), 0)) {
201 nk_layout_row_static(context, height - 100, width - 60, 1);
202 if (nk_group_begin(context, "Select Save Slot", NK_WINDOW_BORDER | NK_WINDOW_TITLE)) {
203 nk_layout_row_static(context, 28, width-100, 1);
204 if (!slots) {
205 slots = get_slot_info(current_system, &num_slots);
206 }
207 for (uint32_t i = 0; i < num_slots; i++)
208 {
209 int selected = i == selected_slot;
210 nk_selectable_label(context, slots[i].desc, NK_TEXT_ALIGN_LEFT, &selected);
211 if (selected && (slots[i].modification_time || !is_load)) {
212 selected_slot = i;
213 }
214 }
215 nk_group_end(context);
216 }
217 nk_layout_row_static(context, 52, width > 600 ? 300 : width / 2, 2);
218 if (nk_button_label(context, "Back")) {
219 pop_view();
220 }
221 if (is_load) {
222 if (nk_button_label(context, "Load")) {
223 current_system->load_state(current_system, selected_slot);
224 current_view = view_play;
225 }
226 } else {
227 if (nk_button_label(context, "Save")) {
228 current_system->save_state = selected_slot + 1;
229 current_view = view_play;
230 }
231 }
232 nk_end(context);
233 }
234 }
235
236 void view_save_state(struct nk_context *context)
237 {
238 view_choose_state(context, 0);
239 }
240
241 void view_load_state(struct nk_context *context)
242 {
243 view_choose_state(context, 1);
244 }
245
246 static void menu(struct nk_context *context, uint32_t num_entries, const menu_item *items)
247 {
248 const uint32_t button_height = 52;
249 const uint32_t ideal_button_width = 300;
250 const uint32_t button_space = 6;
251
252 uint32_t width = render_width();
253 uint32_t height = render_height();
254 uint32_t top = height/2 - (button_height * num_entries)/2;
255 uint32_t button_width = width > ideal_button_width ? ideal_button_width : width;
256 uint32_t left = width/2 - button_width/2;
257
258 nk_layout_space_begin(context, NK_STATIC, top + button_height * num_entries, num_entries);
259 for (uint32_t i = 0; i < num_entries; i++)
260 {
261 nk_layout_space_push(context, nk_rect(left, top + i * button_height, button_width, button_height-button_space));
262 if (nk_button_label(context, items[i].title)) {
263 push_view(items[i].next_view);
264 if (!current_view) {
265 exit(0);
266 }
267 if (current_view == view_save_state || current_view == view_load_state) {
268 free_slot_info(slots);
269 slots = NULL;
270 }
271 }
272 }
273 nk_layout_space_end(context);
274 }
275
276 void binding_loop(char *key, tern_val val, uint8_t valtype, void *data)
277 {
278 if (valtype != TVAL_PTR) {
279 return;
280 }
281 tern_node **binding_lookup = data;
282 *binding_lookup = tern_insert_ptr(*binding_lookup, val.ptrval, strdup(key));
283 }
284
285 static int32_t keycode;
286 static const char *set_binding;
287 char *set_label;
288 void binding_group(struct nk_context *context, char *name, const char **binds, const char **bind_names, uint32_t num_binds, tern_node *binding_lookup)
289 {
290 nk_layout_row_static(context, 34*num_binds+60, render_width() - 80, 1);
291 if (nk_group_begin(context, name, NK_WINDOW_TITLE)) {
292 nk_layout_row_static(context, 30, render_width()/2 - 80, 2);
293
294 for (int i = 0; i < num_binds; i++)
295 {
296 char *label_alloc = bind_names ? NULL : path_extension(binds[i]);
297 const char *label = label_alloc;
298 if (!label) {
299 label = bind_names ? bind_names[i] : binds[i];
300 }
301 nk_label(context, label, NK_TEXT_LEFT);
302 if (nk_button_label(context, tern_find_ptr_default(binding_lookup, binds[i], "Not Set"))) {
303 set_binding = binds[i];
304 set_label = strdup(label);
305 keycode = 0;
306 }
307 if (label_alloc) {
308 free(label_alloc);
309 }
310 }
311 nk_group_end(context);
312 }
313 }
314
315 static char *get_key_name(int32_t keycode)
316 {
317 char *name = NULL;
318 if (keycode > ' ' && keycode < 0x80) {
319 //key corresponds to a printable non-whitespace character
320 name = malloc(2);
321 name[0] = keycode;
322 name[1] = 0;
323 } else {
324 switch (keycode)
325 {
326 case RENDERKEY_UP: name = "up"; break;
327 case RENDERKEY_DOWN: name = "down"; break;
328 case RENDERKEY_LEFT: name = "left"; break;
329 case RENDERKEY_RIGHT: name = "right"; break;
330 case '\r': name = "enter"; break;
331 case ' ': name = "space"; break;
332 case '\t': name = "tab"; break;
333 case '\b': name = "backspace"; break;
334 case RENDERKEY_ESC: name = "esc"; break;
335 case RENDERKEY_DEL: name = "delete"; break;
336 case RENDERKEY_LSHIFT: name = "lshift"; break;
337 case RENDERKEY_RSHIFT: name = "rshift"; break;
338 case RENDERKEY_LCTRL: name = "lctrl"; break;
339 case RENDERKEY_RCTRL: name = "rctrl"; break;
340 case RENDERKEY_LALT: name = "lalt"; break;
341 case RENDERKEY_RALT: name = "ralt"; break;
342 case RENDERKEY_HOME: name = "home"; break;
343 case RENDERKEY_END: name = "end"; break;
344 case RENDERKEY_PAGEUP: name = "pageup"; break;
345 case RENDERKEY_PAGEDOWN: name = "pagedown"; break;
346 case RENDERKEY_F1: name = "f1"; break;
347 case RENDERKEY_F2: name = "f2"; break;
348 case RENDERKEY_F3: name = "f3"; break;
349 case RENDERKEY_F4: name = "f4"; break;
350 case RENDERKEY_F5: name = "f5"; break;
351 case RENDERKEY_F6: name = "f6"; break;
352 case RENDERKEY_F7: name = "f7"; break;
353 case RENDERKEY_F8: name = "f8"; break;
354 case RENDERKEY_F9: name = "f9"; break;
355 case RENDERKEY_F10: name = "f10"; break;
356 case RENDERKEY_F11: name = "f11"; break;
357 case RENDERKEY_F12: name = "f12"; break;
358 case RENDERKEY_SELECT: name = "select"; break;
359 case RENDERKEY_PLAY: name = "play"; break;
360 case RENDERKEY_SEARCH: name = "search"; break;
361 case RENDERKEY_BACK: name = "back"; break;
362 }
363 if (name) {
364 name = strdup(name);
365 }
366 }
367 return name;
368 }
369
370 void view_key_bindings(struct nk_context *context)
371 {
372 const char *controller1_binds[] = {
373 "gamepads.1.up", "gamepads.1.down", "gamepads.1.left", "gamepads.1.right",
374 "gamepads.1.a", "gamepads.1.b", "gamepads.1.c",
375 "gamepads.1.x", "gamepads.1.y", "gamepads.1.z",
376 "gamepads.1.start", "gamepads.1.mode"
377 };
378 const char *controller2_binds[] = {
379 "gamepads.2.up", "gamepads.2.down", "gamepads.2.left", "gamepads.2.right",
380 "gamepads.2.a", "gamepads.2.b", "gamepads.2.c",
381 "gamepads.2.x", "gamepads.2.y", "gamepads.2.z",
382 "gamepads.2.start", "gamepads.2.mode"
383 };
384 const char *general_binds[] = {
385 "ui.exit", "ui.save_state", "ui.toggle_fullscreen", "ui.soft_reset", "ui.reload",
386 "ui.screenshot", "ui.sms_pause", "ui.toggle_keyboard_cpatured", "ui.release_mouse"
387 };
388 const char *general_names[] = {
389 "Show Menu", "Quick Save", "Toggle Fullscreen", "Soft Reset", "Reload Media",
390 "Internal Screenshot", "SMS Pause", "Capture Keyboard", "Release Mouse"
391 };
392 const char *speed_binds[] = {
393 "ui.next_speed", "ui.prev_speed",
394 "ui.set_speed.0", "ui.set_speed.1", "ui.set_speed.2" ,"ui.set_speed.3", "ui.set_speed.4",
395 "ui.set_speed.5", "ui.set_speed.6", "ui.set_speed.7" ,"ui.set_speed.8", "ui.set_speed.9",
396 };
397 const char *speed_names[] = {
398 "Next", "Previous",
399 "Default Speed", "Set Speed 1", "Set Speed 2", "Set Speed 3", "Set Speed 4",
400 "Set Speed 5", "Set Speed 6", "Set Speed 7", "Set Speed 8", "Set Speed 9"
401 };
402 const char *debug_binds[] = {
403 "ui.enter_debugger", "ui.vdp_debug_mode", "ui.vdp_debug_pal"
404 };
405 const char *debug_names[] = {
406 "Enter Debugger", "VDP Debug Mode", "Debug Palette"
407 };
408 const uint32_t NUM_C1_BINDS = sizeof(controller1_binds)/sizeof(*controller1_binds);
409 const uint32_t NUM_C2_BINDS = sizeof(controller2_binds)/sizeof(*controller2_binds);
410 const uint32_t NUM_SPEED_BINDS = sizeof(speed_binds)/sizeof(*speed_binds);
411 const uint32_t NUM_GEN_BINDS = sizeof(general_binds)/sizeof(*general_binds);
412 const uint32_t NUM_DBG_BINDS = sizeof(debug_binds)/sizeof(*debug_binds);
413 static tern_node *binding_lookup;
414 if (!binding_lookup) {
415 tern_node *bindings = tern_find_path(config, "bindings\0keys\0", TVAL_NODE).ptrval;
416 if (bindings) {
417 tern_foreach(bindings, binding_loop, &binding_lookup);
418 }
419 }
420 uint32_t width = render_width();
421 uint32_t height = render_height();
422 if (nk_begin(context, "Keyboard Bindings", nk_rect(0, 0, width, height), 0)) {
423 binding_group(context, "Controller 1", controller1_binds, NULL, NUM_C1_BINDS, binding_lookup);
424 binding_group(context, "Controller 2", controller2_binds, NULL, NUM_C2_BINDS, binding_lookup);
425 binding_group(context, "General", general_binds, general_names, NUM_GEN_BINDS, binding_lookup);
426 binding_group(context, "Speed Control", speed_binds, speed_names, NUM_SPEED_BINDS, binding_lookup);
427 binding_group(context, "Debug", debug_binds, debug_names, NUM_DBG_BINDS, binding_lookup);
428
429 nk_end(context);
430 }
431 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)) {
432 nk_layout_row_static(context, 30, width/2-30, 1);
433 nk_label(context, "Press new key for", NK_TEXT_CENTERED);
434 nk_label(context, set_label, NK_TEXT_CENTERED);
435 if (nk_button_label(context, "Cancel")) {
436 free(set_label);
437 set_binding = set_label = NULL;
438 } else if (keycode) {
439 char *name = get_key_name(keycode);
440 if (name) {
441 uint32_t prefix_len = strlen("bindings") + strlen("keys") + 2;
442 char * old = tern_find_ptr(binding_lookup, set_binding);
443 if (old) {
444 uint32_t suffix_len = strlen(old) + 1;
445 char *old_path = malloc(prefix_len + suffix_len + 1);
446 memcpy(old_path, "bindings\0keys\0", prefix_len);
447 memcpy(old_path + prefix_len, old, suffix_len);
448 old_path[prefix_len + suffix_len] = 0;
449 tern_val old_val;
450 if (tern_delete_path(&config, old_path, &old_val) == TVAL_PTR) {
451 free(old_val.ptrval);
452 }
453 }
454 uint32_t suffix_len = strlen(name) + 1;
455 char *path = malloc(prefix_len + suffix_len + 1);
456 memcpy(path, "bindings\0keys\0", prefix_len);
457 memcpy(path + prefix_len, name, suffix_len);
458 path[prefix_len + suffix_len] = 0;
459
460 config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(set_binding)}, TVAL_PTR);
461 free(path);
462 free(name);
463 tern_free(binding_lookup);
464 binding_lookup = NULL;
465 }
466 free(set_label);
467 set_binding = set_label = NULL;
468 }
469 nk_end(context);
470 }
471 }
472 void view_controllers(struct nk_context *context)
473 {
474
475 }
476
477 void settings_toggle(struct nk_context *context, char *label, char *path, uint8_t def)
478 {
479 uint8_t curval = !strcmp("on", tern_find_path_default(config, path, (tern_val){.ptrval = def ? "on": "off"}, TVAL_PTR).ptrval);
480 nk_label(context, label, NK_TEXT_LEFT);
481 uint8_t newval = nk_check_label(context, "", curval);
482 if (newval != curval) {
483 config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(newval ? "on" : "off")}, TVAL_PTR);
484 }
485 }
486
487 void settings_int_input(struct nk_context *context, char *label, char *path, char *def)
488 {
489 char buffer[12];
490 nk_label(context, label, NK_TEXT_LEFT);
491 uint32_t curval;
492 char *curstr = tern_find_path_default(config, path, (tern_val){.ptrval = def}, TVAL_PTR).ptrval;
493 uint32_t len = strlen(curstr);
494 if (len > 11) {
495 len = 11;
496 }
497 memcpy(buffer, curstr, len);
498 nk_edit_string(context, NK_EDIT_SIMPLE, buffer, &len, sizeof(buffer)-1, nk_filter_decimal);
499 buffer[len] = 0;
500 if (strcmp(buffer, curstr)) {
501 config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(buffer)}, TVAL_PTR);
502 }
503 }
504
505 void settings_int_property(struct nk_context *context, char *label, char *name, char *path, int def, int min, int max)
506 {
507 char *curstr = tern_find_path(config, path, TVAL_PTR).ptrval;
508 int curval = curstr ? atoi(curstr) : def;
509 nk_label(context, label, NK_TEXT_LEFT);
510 int val = curval;
511 nk_property_int(context, name, min, &val, max, 1, 1.0f);
512 if (val != curval) {
513 char buffer[12];
514 sprintf(buffer, "%d", val);
515 config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(buffer)}, TVAL_PTR);
516 }
517 }
518
519 typedef struct {
520 char *fragment;
521 char *vertex;
522 } shader_prog;
523
524 shader_prog *get_shader_progs(dir_entry *entries, size_t num_entries, shader_prog *progs, uint32_t *num_existing, uint32_t *storage)
525 {
526 uint32_t num_progs = *num_existing;
527 uint32_t prog_storage = *storage;
528 uint32_t starting = num_progs;
529
530 for (uint32_t i = 0; i < num_entries; i++) {
531 if (entries[i].is_dir) {
532 continue;
533 }
534 char *no_ext = basename_no_extension(entries[i].name);
535 uint32_t len = strlen(no_ext);
536 if (no_ext[len-1] == 'f' && no_ext[len-2] == '.') {
537 uint8_t dupe = 0;;
538 for (uint32_t j = 0; j < starting; j++) {
539 if (!strcmp(entries[i].name, progs[j].fragment)) {
540 dupe = 1;
541 break;
542 }
543 }
544 if (!dupe) {
545 if (num_progs == prog_storage) {
546 prog_storage = prog_storage ? prog_storage*2 : 4;
547 progs = realloc(progs, sizeof(progs) * prog_storage);
548 }
549 progs[num_progs].vertex = NULL;
550 progs[num_progs++].fragment = strdup(entries[i].name);
551 }
552 }
553 free(no_ext);
554 }
555
556 for (uint32_t i = 0; i < num_entries; i++) {
557 if (entries[i].is_dir) {
558 continue;
559 }
560 char *no_ext = basename_no_extension(entries[i].name);
561 uint32_t len = strlen(no_ext);
562 if (no_ext[len-1] == 'v' && no_ext[len-2] == '.') {
563 for (uint32_t j = 0; j < num_progs; j++) {
564 if (!strncmp(no_ext, progs[j].fragment, len-1) && progs[j].fragment[len-1] == 'f' && progs[j].fragment[len] == '.') {
565 progs[j].vertex = strdup(entries[i].name);
566 }
567 }
568 }
569 free(no_ext);
570 }
571 free_dir_list(entries, num_entries);
572 *num_existing = num_progs;
573 *storage = prog_storage;
574 return progs;
575 }
576
577 shader_prog *get_shader_list(uint32_t *num_out)
578 {
579 char *shader_dir = path_append(get_config_dir(), "shaders");
580 size_t num_entries;
581 dir_entry *entries = get_dir_list(shader_dir, &num_entries);
582 free(shader_dir);
583 shader_prog *progs;
584 uint32_t num_progs = 0, prog_storage;
585 if (num_entries) {
586 progs = calloc(num_entries, sizeof(shader_prog));
587 prog_storage = num_entries;
588 progs = get_shader_progs(entries, num_entries, progs, &num_progs, &prog_storage);
589 } else {
590 progs = NULL;
591 prog_storage = 0;
592 }
593 shader_dir = path_append(get_exe_dir(), "shaders");
594 entries = get_dir_list(shader_dir, &num_entries);
595 progs = get_shader_progs(entries, num_entries, progs, &num_progs, &prog_storage);
596 *num_out = num_progs;
597 return progs;
598 }
599
600 void view_video_settings(struct nk_context *context)
601 {
602 static shader_prog *progs;
603 static char **prog_names;
604 static uint32_t num_progs;
605 static uint32_t selected_prog;
606 if(!progs) {
607 progs = get_shader_list(&num_progs);
608 prog_names = calloc(num_progs, sizeof(char*));
609 for (uint32_t i = 0; i < num_progs; i++)
610 {
611 prog_names[i] = basename_no_extension(progs[i].fragment);;
612 uint32_t len = strlen(prog_names[i]);
613 if (len > 2) {
614 prog_names[i][len-2] = 0;
615 }
616 if (!progs[i].vertex) {
617 progs[i].vertex = strdup("default.v.glsl");
618 }
619 if (!strcmp(
620 progs[i].fragment,
621 tern_find_path_default(config, "video\0fragment_shader\0", (tern_val){.ptrval = "default.f.glsl"}, TVAL_PTR).ptrval
622 )) {
623 selected_prog = i;
624 }
625 }
626 }
627 uint32_t width = render_width();
628 uint32_t height = render_height();
629 if (nk_begin(context, "Video Settings", nk_rect(0, 0, width, height), 0)) {
630 nk_layout_row_static(context, 30, width > 300 ? 300 : width, 2);
631 settings_toggle(context, "Fullscreen", "video\0fullscreen\0", 0);
632 settings_toggle(context, "Open GL", "video\0gl\0", 1);
633 settings_toggle(context, "Scanlines", "video\0scanlines\0", 0);
634 settings_int_input(context, "Windowed Width", "video\0width\0", "640");
635 nk_label(context, "Shader", NK_TEXT_LEFT);
636 uint32_t next_selected = nk_combo(context, (const char **)prog_names, num_progs, selected_prog, 30, nk_vec2(300, 300));
637 if (next_selected != selected_prog) {
638 selected_prog = next_selected;
639 config = tern_insert_path(config, "video\0fragment_shader\0", (tern_val){.ptrval = strdup(progs[next_selected].fragment)}, TVAL_PTR);
640 config = tern_insert_path(config, "video\0vertex_shader\0", (tern_val){.ptrval = strdup(progs[next_selected].vertex)}, TVAL_PTR);
641 }
642 settings_int_property(context, "NTSC Overscan", "Top", "video\0ntsc\0overscan\0top\0", 2, 0, 32);
643 settings_int_property(context, "", "Bottom", "video\0ntsc\0overscan\0bottom\0", 17, 0, 32);
644 settings_int_property(context, "", "Left", "video\0ntsc\0overscan\0left\0", 13, 0, 32);
645 settings_int_property(context, "", "Right", "video\0ntsc\0overscan\0right\0", 14, 0, 32);
646 settings_int_property(context, "PAL Overscan", "Top", "video\0pal\0overscan\0top\0", 2, 0, 32);
647 settings_int_property(context, "", "Bottom", "video\0pal\0overscan\0bottom\0", 17, 0, 32);
648 settings_int_property(context, "", "Left", "video\0pal\0overscan\0left\0", 13, 0, 32);
649 settings_int_property(context, "", "Right", "video\0pal\0overscan\0right\0", 14, 0, 32);
650
651 if (nk_button_label(context, "Back")) {
652 pop_view();
653 }
654 nk_end(context);
655 }
656 }
657
658 int32_t find_match(const char **options, uint32_t num_options, char *path, char *def)
659 {
660 char *setting = tern_find_path_default(config, path, (tern_val){.ptrval = def}, TVAL_PTR).ptrval;
661 int32_t selected = -1;
662 for (uint32_t i = 0; i < num_options; i++)
663 {
664 if (!strcmp(setting, options[i])) {
665 selected = i;
666 break;
667 }
668 }
669 if (selected == -1) {
670 for (uint32_t i = 0; i < num_options; i++)
671 {
672 if (!strcmp(def, options[i])) {
673 selected = i;
674 break;
675 }
676 }
677 }
678 return selected;
679 }
680
681 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)
682 {
683 nk_label(context, label, NK_TEXT_LEFT);
684 int32_t next = nk_combo(context, opt_display, num_options, current, 30, nk_vec2(300, 300));
685 if (next != current) {
686 config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(options[next])}, TVAL_PTR);
687 }
688 return next;
689 }
690
691 int32_t settings_dropdown(struct nk_context *context, char *label, const char **options, uint32_t num_options, int32_t current, char *path)
692 {
693 return settings_dropdown_ex(context, label, options, options, num_options, current, path);
694 }
695
696 void view_audio_settings(struct nk_context *context)
697 {
698 const char *rates[] = {
699 "192000",
700 "96000",
701 "48000",
702 "44100",
703 "22050"
704 };
705 const char *sizes[] = {
706 "1024",
707 "512",
708 "256",
709 "128",
710 "64"
711 };
712 const uint32_t num_rates = sizeof(rates)/sizeof(*rates);
713 const uint32_t num_sizes = sizeof(sizes)/sizeof(*sizes);
714 static int32_t selected_rate = -1;
715 static int32_t selected_size = -1;
716 if (selected_rate < 0 || selected_size < 0) {
717 selected_rate = find_match(rates, num_rates, "autio\0rate\0", "48000");
718 selected_size = find_match(sizes, num_sizes, "audio\0buffer\0", "512");
719 }
720 uint32_t width = render_width();
721 uint32_t height = render_height();
722 if (nk_begin(context, "Audio Settings", nk_rect(0, 0, width, height), 0)) {
723 nk_layout_row_static(context, 30, width > 300 ? 300 : width, 2);
724 selected_rate = settings_dropdown(context, "Rate in Hz", rates, num_rates, selected_rate, "audio\0rate\0");
725 selected_size = settings_dropdown(context, "Buffer Samples", sizes, num_sizes, selected_size, "audio\0buffer\0");
726 settings_int_input(context, "Lowpass Cutoff Hz", "audio\0lowpass_cutoff\0", "3390");
727 if (nk_button_label(context, "Back")) {
728 pop_view();
729 }
730 nk_end(context);
731 }
732 }
733 void view_system_settings(struct nk_context *context)
734 {
735 const char *regions[] = {
736 "J - Japan",
737 "U - Americas",
738 "E - Europe"
739 };
740 const char *region_codes[] = {"J", "U", "E"};
741 const uint32_t num_regions = sizeof(regions)/sizeof(*regions);
742 static int32_t selected_region = -1;
743 if (selected_region < 0) {
744 selected_region = find_match(region_codes, num_regions, "system\0default_region\0", "U");
745 }
746 const char *formats[] = {
747 "native",
748 "gst"
749 };
750 const uint32_t num_formats = sizeof(formats)/sizeof(*formats);
751 int32_t selected_format = -1;
752 if (selected_format < 0) {
753 selected_format = find_match(formats, num_formats, "ui\0state_format\0", "native");
754 }
755 const char *ram_inits[] = {
756 "zero",
757 "random"
758 };
759 const uint32_t num_inits = sizeof(ram_inits)/sizeof(*ram_inits);
760 static int32_t selected_init = -1;
761 if (selected_init < 0) {
762 selected_init = find_match(ram_inits, num_inits, "system\0ram_init\0", "zero");
763 }
764 const char *io_opts_1[] = {
765 "gamepad2.1",
766 "gamepad3.1",
767 "gamepad6.1",
768 "mouse",
769 "saturn keyboard",
770 "xband keyboard"
771 };
772 const char *io_opts_2[] = {
773 "gamepad2.2",
774 "gamepad3.2",
775 "gamepad6.2",
776 "mouse",
777 "saturn keyboard",
778 "xband keyboard"
779 };
780 static int32_t selected_io_1 = -1;
781 static int32_t selected_io_2 = -1;
782 const uint32_t num_io = sizeof(io_opts_1)/sizeof(*io_opts_1);
783 if (selected_io_1 < 0 || selected_io_2 < 0) {
784 selected_io_1 = find_match(io_opts_1, num_io, "io\0devices\0""1\0", "gamepad6.1");
785 selected_io_2 = find_match(io_opts_2, num_io, "io\0devices\0""2\0", "gamepad6.2");
786 }
787
788 uint32_t width = render_width();
789 uint32_t height = render_height();
790 if (nk_begin(context, "System Settings", nk_rect(0, 0, width, height), 0)) {
791 nk_layout_row_static(context, 30, width > 300 ? 300 : width, 2);
792 settings_int_property(context, "68000 Clock Divider", "", "clocks\0m68k_divider\0", 7, 1, 53);
793 settings_toggle(context, "Remember ROM Path", "ui\0remember_path\0", 1);
794 selected_region = settings_dropdown_ex(context, "Default Region", region_codes, regions, num_regions, selected_region, "system\0default_region\0");
795 selected_format = settings_dropdown(context, "Save State Format", formats, num_formats, selected_format, "ui\0state_format\0");
796 selected_init = settings_dropdown(context, "Initial RAM Value", ram_inits, num_inits, selected_init, "system\0ram_init\0");
797 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");
798 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");
799 if (nk_button_label(context, "Back")) {
800 pop_view();
801 }
802 nk_end(context);
803 }
804 }
805
806 void view_back(struct nk_context *context)
807 {
808 pop_view();
809 pop_view();
810 current_view(context);
811 }
812
813 void view_settings(struct nk_context *context)
814 {
815 static menu_item items[] = {
816 {"Key Bindings", view_key_bindings},
817 {"Controllers", view_controllers},
818 {"Video", view_video_settings},
819 {"Audio", view_audio_settings},
820 {"System", view_system_settings},
821 {"Back", view_back}
822 };
823
824 const uint32_t num_buttons = 6;
825 if (nk_begin(context, "Settings Menu", nk_rect(0, 0, render_width(), render_height()), 0)) {
826 menu(context, sizeof(items)/sizeof(*items), items);
827 nk_end(context);
828 }
829 }
830
831 void view_pause(struct nk_context *context)
832 {
833 static menu_item items[] = {
834 {"Resume", view_play},
835 {"Load ROM", view_load},
836 {"Lock On", view_lock_on},
837 {"Save State", view_save_state},
838 {"Load State", view_load_state},
839 {"Settings", view_settings},
840 {"Exit", NULL}
841 };
842
843 const uint32_t num_buttons = 3;
844 if (nk_begin(context, "Main Menu", nk_rect(0, 0, render_width(), render_height()), 0)) {
845 menu(context, sizeof(items)/sizeof(*items), items);
846 nk_end(context);
847 }
848 }
849
850 void view_menu(struct nk_context *context)
851 {
852 static menu_item items[] = {
853 {"Load ROM", view_load},
854 {"Settings", view_settings},
855 {"About", view_about},
856 {"Exit", NULL}
857 };
858
859 const uint32_t num_buttons = 3;
860 if (nk_begin(context, "Main Menu", nk_rect(0, 0, render_width(), render_height()), 0)) {
861 menu(context, sizeof(items)/sizeof(*items), items);
862 nk_end(context);
863 }
864 }
865
866 void blastem_nuklear_render(void)
867 {
868 nk_input_end(context);
869 current_view(context);
870 nk_sdl_render(NK_ANTI_ALIASING_ON, 512 * 1024, 128 * 1024);
871 nk_input_begin(context);
872 }
873
874 void ui_idle_loop(void)
875 {
876 const uint32_t MIN_UI_DELAY = 15;
877 static uint32_t last;
878 while (current_view != view_play)
879 {
880 uint32_t current = render_elapsed_ms();
881 if ((current - last) < MIN_UI_DELAY) {
882 render_sleep_ms(MIN_UI_DELAY - (current - last) - 1);
883 }
884 last = current;
885 render_update_display();
886 }
887 }
888 static void handle_event(SDL_Event *event)
889 {
890 if (event->type == SDL_KEYDOWN) {
891 keycode = event->key.keysym.sym;
892 }
893 nk_sdl_handle_event(event);
894 }
895
896 static void context_destroyed(void)
897 {
898 nk_sdl_device_destroy();
899 }
900 static void context_created(void)
901 {
902 nk_sdl_device_create();
903 struct nk_font_atlas *atlas;
904 nk_sdl_font_stash_begin(&atlas);
905 uint32_t font_size;
906 uint8_t *font = default_font(&font_size);
907 if (!font) {
908 fatal_error("Failed to find default font path\n");
909 }
910 struct nk_font *def_font = nk_font_atlas_add_from_memory(atlas, font, font_size, 30, NULL);
911 nk_sdl_font_stash_end();
912 nk_style_set_font(context, &def_font->handle);
913 }
914
915 void show_pause_menu(void)
916 {
917 context->style.window.background = nk_rgba(0, 0, 0, 128);
918 context->style.window.fixed_background = nk_style_item_color(nk_rgba(0, 0, 0, 128));
919 current_view = view_pause;
920 current_system->request_exit(current_system);
921 }
922
923 static uint8_t active;
924 uint8_t is_nuklear_active(void)
925 {
926 return active;
927 }
928
929 uint8_t is_nuklear_available(void)
930 {
931 if (!render_has_gl()) {
932 //currently no fallback if GL2 unavailable
933 return 0;
934 }
935 char *style = tern_find_path(config, "ui\0style\0", TVAL_PTR).ptrval;
936 if (!style) {
937 return 1;
938 }
939 return strcmp(style, "rom") != 0;
940 }
941
942 void blastem_nuklear_init(uint8_t file_loaded)
943 {
944 context = nk_sdl_init(render_get_window());
945
946 struct nk_font_atlas *atlas;
947 nk_sdl_font_stash_begin(&atlas);
948 //char *font = default_font_path();
949 uint32_t font_size;
950 uint8_t *font = default_font(&font_size);
951 if (!font) {
952 fatal_error("Failed to find default font path\n");
953 }
954 //struct nk_font *def_font = nk_font_atlas_add_from_file(atlas, font, 30, NULL);
955 struct nk_font *def_font = nk_font_atlas_add_from_memory(atlas, font, font_size, 30, NULL);
956 nk_sdl_font_stash_end();
957 nk_style_set_font(context, &def_font->handle);
958 current_view = file_loaded ? view_play : view_menu;
959 render_set_ui_render_fun(blastem_nuklear_render);
960 render_set_event_handler(handle_event);
961 render_set_gl_context_handlers(context_destroyed, context_created);
962 active = 1;
963 ui_idle_loop();
964 }