comparison nuklear_ui/blastem_nuklear.c @ 1692:5dacaef602a7 segacd

Merge from default
author Michael Pavone <pavone@retrodev.com>
date Sat, 05 Jan 2019 00:58:08 -0800
parents 357b4951d9b2
children 3f1c8258e20f
comparison
equal deleted inserted replaced
1504:95b3a1a8b26c 1692:5dacaef602a7
1 #define NK_IMPLEMENTATION
2 #define NK_SDL_GLES2_IMPLEMENTATION
3
4 #include <stdlib.h>
5 #include <limits.h>
6 #include "blastem_nuklear.h"
7 #include "font.h"
8 #include "../render.h"
9 #include "../render_sdl.h"
10 #include "../util.h"
11 #include "../paths.h"
12 #include "../saves.h"
13 #include "../blastem.h"
14 #include "../config.h"
15 #include "../io.h"
16 #include "../png.h"
17 #include "../controller_info.h"
18 #include "../bindings.h"
19
20 static struct nk_context *context;
21
22 typedef struct
23 {
24 uint32_t *image_data;
25 uint32_t width, height;
26 struct nk_image ui;
27 } ui_image;
28
29 static ui_image **ui_images, *controller_360, *controller_ps4, *controller_ps4_6b;
30 static uint32_t num_ui_images, ui_image_storage;
31
32 typedef void (*view_fun)(struct nk_context *);
33 static view_fun current_view;
34 static view_fun *previous_views;
35 static uint32_t view_storage;
36 static uint32_t num_prev;
37 static struct nk_font *def_font;
38 static uint8_t config_dirty;
39
40 static void push_view(view_fun new_view)
41 {
42 if (num_prev == view_storage) {
43 view_storage = view_storage ? 2*view_storage : 2;
44 previous_views = realloc(previous_views, view_storage*sizeof(view_fun));
45 }
46 previous_views[num_prev++] = current_view;
47 current_view = new_view;
48 }
49
50 static void pop_view()
51 {
52 if (num_prev) {
53 current_view = previous_views[--num_prev];
54 }
55 }
56
57 static void clear_view_stack()
58 {
59 num_prev = 0;
60 }
61
62 void view_play(struct nk_context *context)
63 {
64
65 }
66
67 void view_file_browser(struct nk_context *context, uint8_t normal_open)
68 {
69 static char *current_path;
70 static dir_entry *entries;
71 static size_t num_entries;
72 static int32_t selected_entry = -1;
73 static char **ext_list;
74 static uint32_t num_exts;
75 static uint8_t got_ext_list;
76 if (!current_path) {
77 get_initial_browse_path(&current_path);
78 }
79 if (!entries) {
80 entries = get_dir_list(current_path, &num_entries);
81 if (entries) {
82 sort_dir_list(entries, num_entries);
83 }
84 }
85 if (!got_ext_list) {
86 ext_list = get_extension_list(config, &num_exts);
87 got_ext_list = 1;
88 }
89 uint32_t width = render_width();
90 uint32_t height = render_height();
91 if (nk_begin(context, "Load ROM", nk_rect(0, 0, width, height), 0)) {
92 nk_layout_row_static(context, height - context->style.font->height * 3, width - 60, 1);
93 int32_t old_selected = selected_entry;
94 if (nk_group_begin(context, "Select ROM", NK_WINDOW_BORDER | NK_WINDOW_TITLE)) {
95 nk_layout_row_static(context, context->style.font->height - 2, width-100, 1);
96 for (int32_t i = 0; i < num_entries; i++)
97 {
98 if (entries[i].name[0] == '.' && entries[i].name[1] != '.') {
99 continue;
100 }
101 if (num_exts && !entries[i].is_dir && !path_matches_extensions(entries[i].name, ext_list, num_exts)) {
102 continue;
103 }
104 int selected = i == selected_entry;
105 nk_selectable_label(context, entries[i].name, NK_TEXT_ALIGN_LEFT, &selected);
106 if (selected) {
107 selected_entry = i;
108 } else if (i == selected_entry) {
109 selected_entry = -1;
110 }
111 }
112 nk_group_end(context);
113 }
114 nk_layout_row_static(context, context->style.font->height * 1.75, width > 600 ? 300 : width / 2, 2);
115 if (nk_button_label(context, "Back")) {
116 pop_view();
117 }
118 if (nk_button_label(context, "Open") || (old_selected >= 0 && selected_entry < 0)) {
119 if (selected_entry < 0) {
120 selected_entry = old_selected;
121 }
122 char *full_path = path_append(current_path, entries[selected_entry].name);
123 if (entries[selected_entry].is_dir) {
124 free(current_path);
125 current_path = full_path;
126 free_dir_list(entries, num_entries);
127 entries = NULL;
128 } else {
129 if(normal_open) {
130 if (current_system) {
131 current_system->next_rom = full_path;
132 current_system->request_exit(current_system);
133 } else {
134 init_system_with_media(full_path, SYSTEM_UNKNOWN);
135 free(full_path);
136 }
137 } else {
138 lockon_media(full_path);
139 free(full_path);
140 }
141 clear_view_stack();
142 show_play_view();
143 }
144 selected_entry = -1;
145 }
146 nk_end(context);
147 }
148 }
149
150 void view_load(struct nk_context *context)
151 {
152 view_file_browser(context, 1);
153 }
154
155 void view_lock_on(struct nk_context *context)
156 {
157 view_file_browser(context, 0);
158 }
159
160 void view_about(struct nk_context *context)
161 {
162 const char *lines[] = {
163 "BlastEm v0.6.1",
164 "Copyright 2012-2017 Michael Pavone",
165 "",
166 "BlastEm is a high performance open source",
167 "(GPLv3) Genesis/Megadrive emulator",
168 };
169 const uint32_t NUM_LINES = sizeof(lines)/sizeof(*lines);
170 const char *thanks[] = {
171 "Nemesis: Documentation and test ROMs",
172 "Charles MacDonald: Documentation",
173 "Eke-Eke: Documentation",
174 "Bart Trzynadlowski: Documentation",
175 "KanedaFR: Hosting the best Sega forum",
176 "Titan: Awesome demos and documentation",
177 "flamewing: BCD info and test ROM",
178 "r57shell: Opcode size test ROM",
179 "micky: Testing",
180 "Sasha: Testing",
181 "lol-frank: Testing",
182 "Sik: Testing",
183 "Tim Lawrence : Testing",
184 "ComradeOj: Testing",
185 "Vladikcomper: Testing"
186 };
187 const uint32_t NUM_THANKS = sizeof(thanks)/sizeof(*thanks);
188 uint32_t width = render_width();
189 uint32_t height = render_height();
190 if (nk_begin(context, "About", nk_rect(0, 0, width, height), 0)) {
191 nk_layout_row_static(context, context->style.font->height, width-40, 1);
192 for (uint32_t i = 0; i < NUM_LINES; i++)
193 {
194 nk_label(context, lines[i], NK_TEXT_LEFT);
195 }
196 nk_layout_row_static(context, height - (context->style.font->height * 2 + 20) - (context->style.font->height +4)*NUM_LINES, width-40, 1);
197 if (nk_group_begin(context, "Special Thanks", NK_WINDOW_TITLE)) {
198 nk_layout_row_static(context, context->style.font->height, width - 80, 1);
199 for (uint32_t i = 0; i < NUM_THANKS; i++)
200 {
201 nk_label(context, thanks[i], NK_TEXT_LEFT);
202 }
203 nk_group_end(context);
204 }
205 nk_layout_row_static(context, context->style.font->height * 1.75, width/3, 1);
206 if (nk_button_label(context, "Back")) {
207 pop_view();
208 }
209 nk_end(context);
210 }
211 }
212
213 typedef struct {
214 const char *title;
215 view_fun next_view;
216 } menu_item;
217
218 static save_slot_info *slots;
219 static uint32_t num_slots, selected_slot;
220
221 void view_choose_state(struct nk_context *context, uint8_t is_load)
222 {
223 uint32_t width = render_width();
224 uint32_t height = render_height();
225 if (nk_begin(context, "Slot Picker", nk_rect(0, 0, width, height), 0)) {
226 nk_layout_row_static(context, height - context->style.font->height * 3, width - 60, 1);
227 if (nk_group_begin(context, "Select Save Slot", NK_WINDOW_BORDER | NK_WINDOW_TITLE)) {
228 nk_layout_row_static(context, context->style.font->height - 2, width-100, 1);
229 if (!slots) {
230 slots = get_slot_info(current_system, &num_slots);
231 }
232 for (uint32_t i = 0; i < num_slots; i++)
233 {
234 int selected = i == selected_slot;
235 nk_selectable_label(context, slots[i].desc, NK_TEXT_ALIGN_LEFT, &selected);
236 if (selected && (slots[i].modification_time || !is_load)) {
237 selected_slot = i;
238 }
239 }
240 nk_group_end(context);
241 }
242 nk_layout_row_static(context, context->style.font->height * 1.75, width > 600 ? 300 : width / 2, 2);
243 if (nk_button_label(context, "Back")) {
244 pop_view();
245 }
246 if (is_load) {
247 if (nk_button_label(context, "Load")) {
248 current_system->load_state(current_system, selected_slot);
249 show_play_view();
250 }
251 } else {
252 if (nk_button_label(context, "Save")) {
253 current_system->save_state = selected_slot + 1;
254 show_play_view();
255 }
256 }
257 nk_end(context);
258 }
259 }
260
261 void view_save_state(struct nk_context *context)
262 {
263 view_choose_state(context, 0);
264 }
265
266 void view_load_state(struct nk_context *context)
267 {
268 view_choose_state(context, 1);
269 }
270
271 typedef void (*menu_handler)(uint32_t index);
272
273 static void menu(struct nk_context *context, uint32_t num_entries, const menu_item *items, menu_handler handler)
274 {
275 const uint32_t button_height = context->style.font->height * 1.75;
276 const uint32_t ideal_button_width = context->style.font->height * 10;
277 const uint32_t button_space = 6;
278
279 uint32_t width = render_width();
280 uint32_t height = render_height();
281 uint32_t top = height/2 - (button_height * num_entries)/2;
282 uint32_t button_width = width > ideal_button_width ? ideal_button_width : width;
283 uint32_t left = width/2 - button_width/2;
284
285 nk_layout_space_begin(context, NK_STATIC, top + button_height * num_entries, num_entries);
286 for (uint32_t i = 0; i < num_entries; i++)
287 {
288 nk_layout_space_push(context, nk_rect(left, top + i * button_height, button_width, button_height-button_space));
289 if (nk_button_label(context, items[i].title)) {
290 if (items[i].next_view) {
291 push_view(items[i].next_view);
292 if (current_view == view_save_state || current_view == view_load_state) {
293 free_slot_info(slots);
294 slots = NULL;
295 } else if (current_view == view_play) {
296 set_content_binding_state(1);
297 }
298 } else {
299 handler(i);
300 }
301 }
302 }
303 nk_layout_space_end(context);
304 }
305
306 void binding_loop(char *key, tern_val val, uint8_t valtype, void *data)
307 {
308 if (valtype != TVAL_PTR) {
309 return;
310 }
311 tern_node **binding_lookup = data;
312 *binding_lookup = tern_insert_ptr(*binding_lookup, val.ptrval, strdup(key));
313 }
314
315 static int32_t keycode;
316 static const char *set_binding;
317 char *set_label;
318 void binding_group(struct nk_context *context, char *name, const char **binds, const char **bind_names, uint32_t num_binds, tern_node *binding_lookup)
319 {
320 nk_layout_row_static(context, (context->style.font->height + 4)*num_binds+context->style.font->height+30, render_width() - 80, 1);
321 if (nk_group_begin(context, name, NK_WINDOW_TITLE)) {
322 nk_layout_row_static(context, context->style.font->height, render_width()/2 - 80, 2);
323
324 for (int i = 0; i < num_binds; i++)
325 {
326 char *label_alloc = bind_names ? NULL : path_extension(binds[i]);
327 const char *label = label_alloc;
328 if (!label) {
329 label = bind_names ? bind_names[i] : binds[i];
330 }
331 nk_label(context, label, NK_TEXT_LEFT);
332 if (nk_button_label(context, tern_find_ptr_default(binding_lookup, binds[i], "Not Set"))) {
333 set_binding = binds[i];
334 set_label = strdup(label);
335 keycode = 0;
336 }
337 if (label_alloc) {
338 free(label_alloc);
339 }
340 }
341 nk_group_end(context);
342 }
343 }
344
345 static char *get_key_name(int32_t keycode)
346 {
347 char *name = NULL;
348 if (keycode > ' ' && keycode < 0x80) {
349 //key corresponds to a printable non-whitespace character
350 name = malloc(2);
351 name[0] = keycode;
352 name[1] = 0;
353 } else {
354 switch (keycode)
355 {
356 case RENDERKEY_UP: name = "up"; break;
357 case RENDERKEY_DOWN: name = "down"; break;
358 case RENDERKEY_LEFT: name = "left"; break;
359 case RENDERKEY_RIGHT: name = "right"; break;
360 case '\r': name = "enter"; break;
361 case ' ': name = "space"; break;
362 case '\t': name = "tab"; break;
363 case '\b': name = "backspace"; break;
364 case RENDERKEY_ESC: name = "esc"; break;
365 case RENDERKEY_DEL: name = "delete"; break;
366 case RENDERKEY_LSHIFT: name = "lshift"; break;
367 case RENDERKEY_RSHIFT: name = "rshift"; break;
368 case RENDERKEY_LCTRL: name = "lctrl"; break;
369 case RENDERKEY_RCTRL: name = "rctrl"; break;
370 case RENDERKEY_LALT: name = "lalt"; break;
371 case RENDERKEY_RALT: name = "ralt"; break;
372 case RENDERKEY_HOME: name = "home"; break;
373 case RENDERKEY_END: name = "end"; break;
374 case RENDERKEY_PAGEUP: name = "pageup"; break;
375 case RENDERKEY_PAGEDOWN: name = "pagedown"; break;
376 case RENDERKEY_F1: name = "f1"; break;
377 case RENDERKEY_F2: name = "f2"; break;
378 case RENDERKEY_F3: name = "f3"; break;
379 case RENDERKEY_F4: name = "f4"; break;
380 case RENDERKEY_F5: name = "f5"; break;
381 case RENDERKEY_F6: name = "f6"; break;
382 case RENDERKEY_F7: name = "f7"; break;
383 case RENDERKEY_F8: name = "f8"; break;
384 case RENDERKEY_F9: name = "f9"; break;
385 case RENDERKEY_F10: name = "f10"; break;
386 case RENDERKEY_F11: name = "f11"; break;
387 case RENDERKEY_F12: name = "f12"; break;
388 case RENDERKEY_SELECT: name = "select"; break;
389 case RENDERKEY_PLAY: name = "play"; break;
390 case RENDERKEY_SEARCH: name = "search"; break;
391 case RENDERKEY_BACK: name = "back"; break;
392 case RENDERKEY_NP0: name = "np0"; break;
393 case RENDERKEY_NP1: name = "np1"; break;
394 case RENDERKEY_NP2: name = "np2"; break;
395 case RENDERKEY_NP3: name = "np3"; break;
396 case RENDERKEY_NP4: name = "np4"; break;
397 case RENDERKEY_NP5: name = "np5"; break;
398 case RENDERKEY_NP6: name = "np6"; break;
399 case RENDERKEY_NP7: name = "np7"; break;
400 case RENDERKEY_NP8: name = "np8"; break;
401 case RENDERKEY_NP9: name = "np9"; break;
402 case RENDERKEY_NP_DIV: name = "np/"; break;
403 case RENDERKEY_NP_MUL: name = "np*"; break;
404 case RENDERKEY_NP_MIN: name = "np-"; break;
405 case RENDERKEY_NP_PLUS: name = "np+"; break;
406 case RENDERKEY_NP_ENTER: name = "npenter"; break;
407 case RENDERKEY_NP_STOP: name = "np."; break;
408 }
409 if (name) {
410 name = strdup(name);
411 }
412 }
413 return name;
414 }
415
416 void view_key_bindings(struct nk_context *context)
417 {
418 const char *controller1_binds[] = {
419 "gamepads.1.up", "gamepads.1.down", "gamepads.1.left", "gamepads.1.right",
420 "gamepads.1.a", "gamepads.1.b", "gamepads.1.c",
421 "gamepads.1.x", "gamepads.1.y", "gamepads.1.z",
422 "gamepads.1.start", "gamepads.1.mode"
423 };
424 const char *controller2_binds[] = {
425 "gamepads.2.up", "gamepads.2.down", "gamepads.2.left", "gamepads.2.right",
426 "gamepads.2.a", "gamepads.2.b", "gamepads.2.c",
427 "gamepads.2.x", "gamepads.2.y", "gamepads.2.z",
428 "gamepads.2.start", "gamepads.2.mode"
429 };
430 const char *general_binds[] = {
431 "ui.exit", "ui.save_state", "ui.toggle_fullscreen", "ui.soft_reset", "ui.reload",
432 "ui.screenshot", "ui.sms_pause", "ui.toggle_keyboard_cpatured", "ui.release_mouse"
433 };
434 const char *general_names[] = {
435 "Show Menu", "Quick Save", "Toggle Fullscreen", "Soft Reset", "Reload Media",
436 "Internal Screenshot", "SMS Pause", "Capture Keyboard", "Release Mouse"
437 };
438 const char *speed_binds[] = {
439 "ui.next_speed", "ui.prev_speed",
440 "ui.set_speed.0", "ui.set_speed.1", "ui.set_speed.2" ,"ui.set_speed.3", "ui.set_speed.4",
441 "ui.set_speed.5", "ui.set_speed.6", "ui.set_speed.7" ,"ui.set_speed.8", "ui.set_speed.9",
442 };
443 const char *speed_names[] = {
444 "Next", "Previous",
445 "Default Speed", "Set Speed 1", "Set Speed 2", "Set Speed 3", "Set Speed 4",
446 "Set Speed 5", "Set Speed 6", "Set Speed 7", "Set Speed 8", "Set Speed 9"
447 };
448 const char *debug_binds[] = {
449 "ui.enter_debugger", "ui.plane_debug", "ui.vram_debug", "ui.cram_debug",
450 "ui.compositing_debug", "ui.vdp_debug_mode"
451 };
452 const char *debug_names[] = {
453 "CPU Debugger", "Plane Debugger", "VRAM Debugger", "CRAM Debugger",
454 "Layer Debugger", "Cycle Mode/Pal"
455 };
456 const uint32_t NUM_C1_BINDS = sizeof(controller1_binds)/sizeof(*controller1_binds);
457 const uint32_t NUM_C2_BINDS = sizeof(controller2_binds)/sizeof(*controller2_binds);
458 const uint32_t NUM_SPEED_BINDS = sizeof(speed_binds)/sizeof(*speed_binds);
459 const uint32_t NUM_GEN_BINDS = sizeof(general_binds)/sizeof(*general_binds);
460 const uint32_t NUM_DBG_BINDS = sizeof(debug_binds)/sizeof(*debug_binds);
461 static tern_node *binding_lookup;
462 if (!binding_lookup) {
463 tern_node *bindings = tern_find_path(config, "bindings\0keys\0", TVAL_NODE).ptrval;
464 if (bindings) {
465 tern_foreach(bindings, binding_loop, &binding_lookup);
466 }
467 }
468 uint32_t width = render_width();
469 uint32_t height = render_height();
470 if (nk_begin(context, "Keyboard Bindings", nk_rect(0, 0, width, height), 0)) {
471 binding_group(context, "Controller 1", controller1_binds, NULL, NUM_C1_BINDS, binding_lookup);
472 binding_group(context, "Controller 2", controller2_binds, NULL, NUM_C2_BINDS, binding_lookup);
473 binding_group(context, "General", general_binds, general_names, NUM_GEN_BINDS, binding_lookup);
474 binding_group(context, "Speed Control", speed_binds, speed_names, NUM_SPEED_BINDS, binding_lookup);
475 binding_group(context, "Debug", debug_binds, debug_names, NUM_DBG_BINDS, binding_lookup);
476 nk_layout_row_static(context, context->style.font->height * 1.1333, (render_width() - 80) / 2, 1);
477 if (nk_button_label(context, "Back")) {
478 pop_view();
479 }
480 nk_end(context);
481 }
482 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)) {
483 nk_layout_row_static(context, 30, width/2-30, 1);
484 nk_label(context, "Press new key for", NK_TEXT_CENTERED);
485 nk_label(context, set_label, NK_TEXT_CENTERED);
486 if (nk_button_label(context, "Cancel")) {
487 free(set_label);
488 set_binding = set_label = NULL;
489 } else if (keycode) {
490 char *name = get_key_name(keycode);
491 if (name) {
492 uint32_t prefix_len = strlen("bindings") + strlen("keys") + 2;
493 char * old = tern_find_ptr(binding_lookup, set_binding);
494 if (old) {
495 uint32_t suffix_len = strlen(old) + 1;
496 char *old_path = malloc(prefix_len + suffix_len + 1);
497 memcpy(old_path, "bindings\0keys\0", prefix_len);
498 memcpy(old_path + prefix_len, old, suffix_len);
499 old_path[prefix_len + suffix_len] = 0;
500 tern_val old_val;
501 if (tern_delete_path(&config, old_path, &old_val) == TVAL_PTR) {
502 free(old_val.ptrval);
503 }
504 }
505 uint32_t suffix_len = strlen(name) + 1;
506 char *path = malloc(prefix_len + suffix_len + 1);
507 memcpy(path, "bindings\0keys\0", prefix_len);
508 memcpy(path + prefix_len, name, suffix_len);
509 path[prefix_len + suffix_len] = 0;
510
511 config_dirty = 1;
512 config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(set_binding)}, TVAL_PTR);
513 free(path);
514 free(name);
515 tern_free(binding_lookup);
516 binding_lookup = NULL;
517 }
518 free(set_label);
519 set_binding = set_label = NULL;
520 }
521 nk_end(context);
522 }
523 }
524
525 static int selected_controller;
526 static controller_info selected_controller_info;
527 //#define MIN_BIND_BOX_WIDTH 140
528 #define MAX_BIND_BOX_WIDTH 350
529
530 #define AXIS 0x40000000
531 #define STICKDIR 0x30000000
532 #define LEFTSTICK 0x10000000
533 #define RIGHTSTICK 0x20000000
534 enum {
535 UP,DOWN,RIGHT,LEFT,NUM_AXIS_DIRS
536 };
537
538 static char * config_ps_names[] = {
539 [SDL_CONTROLLER_BUTTON_A] = "cross",
540 [SDL_CONTROLLER_BUTTON_B] = "circle",
541 [SDL_CONTROLLER_BUTTON_X] = "square",
542 [SDL_CONTROLLER_BUTTON_Y] = "triangle",
543 [SDL_CONTROLLER_BUTTON_BACK] = "share",
544 [SDL_CONTROLLER_BUTTON_START] = "options",
545 [SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = "l1",
546 [SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] = "r1",
547 [SDL_CONTROLLER_BUTTON_LEFTSTICK] = "l3",
548 [SDL_CONTROLLER_BUTTON_RIGHTSTICK] = "r3",
549 };
550
551 typedef struct {
552 const char *button_binds[SDL_CONTROLLER_BUTTON_MAX];
553 const char *left_stick[NUM_AXIS_DIRS];
554 const char *right_stick[NUM_AXIS_DIRS];
555 const char *triggers[2];
556 } pad_bind_config;
557
558 static const char **current_bind_dest;
559
560 const char *translate_binding_option(const char *option)
561 {
562 static tern_node *conf_names;
563 if (!conf_names) {
564 conf_names = tern_insert_ptr(conf_names, "gamepads.n.up", "Pad Up");
565 conf_names = tern_insert_ptr(conf_names, "gamepads.n.down", "Pad Down");
566 conf_names = tern_insert_ptr(conf_names, "gamepads.n.left", "Pad Left");
567 conf_names = tern_insert_ptr(conf_names, "gamepads.n.right", "Pad Right");
568 conf_names = tern_insert_ptr(conf_names, "gamepads.n.a", "Pad A");
569 conf_names = tern_insert_ptr(conf_names, "gamepads.n.b", "Pad B");
570 conf_names = tern_insert_ptr(conf_names, "gamepads.n.c", "Pad C");
571 conf_names = tern_insert_ptr(conf_names, "gamepads.n.x", "Pad X");
572 conf_names = tern_insert_ptr(conf_names, "gamepads.n.y", "Pad Y");
573 conf_names = tern_insert_ptr(conf_names, "gamepads.n.z", "Pad Z");
574 conf_names = tern_insert_ptr(conf_names, "gamepads.n.start", "Pad Start");
575 conf_names = tern_insert_ptr(conf_names, "gamepads.n.mode", "Pad Mode");
576 conf_names = tern_insert_ptr(conf_names, "ui.release_mouse", "Release Mouse");
577 conf_names = tern_insert_ptr(conf_names, "ui.vdp_debug_mode", "VDP Debug Mode");
578 conf_names = tern_insert_ptr(conf_names, "ui.vdp_debug_pal", "VDP Debug Palette");
579 conf_names = tern_insert_ptr(conf_names, "ui.enter_debugger", "Enter CPU Debugger");
580 conf_names = tern_insert_ptr(conf_names, "ui.screenshot", "Take Screenshot");
581 conf_names = tern_insert_ptr(conf_names, "ui.exit", "Show Menu");
582 conf_names = tern_insert_ptr(conf_names, "ui.save_state", "Quick Save");
583 conf_names = tern_insert_ptr(conf_names, "ui.set_speed.0", "Set Speed 0");
584 conf_names = tern_insert_ptr(conf_names, "ui.set_speed.1", "Set Speed 1");
585 conf_names = tern_insert_ptr(conf_names, "ui.set_speed.2", "Set Speed 2");
586 conf_names = tern_insert_ptr(conf_names, "ui.set_speed.3", "Set Speed 3");
587 conf_names = tern_insert_ptr(conf_names, "ui.set_speed.4", "Set Speed 4");
588 conf_names = tern_insert_ptr(conf_names, "ui.set_speed.5", "Set Speed 5");
589 conf_names = tern_insert_ptr(conf_names, "ui.set_speed.6", "Set Speed 6");
590 conf_names = tern_insert_ptr(conf_names, "ui.set_speed.7", "Set Speed 7");
591 conf_names = tern_insert_ptr(conf_names, "ui.set_speed.8", "Set Speed 8");
592 conf_names = tern_insert_ptr(conf_names, "ui.set_speed.9", "Set Speed 9");
593 conf_names = tern_insert_ptr(conf_names, "ui.next_speed", "Next Speed");
594 conf_names = tern_insert_ptr(conf_names, "ui.prev_speed", "Prev. Speed");
595 conf_names = tern_insert_ptr(conf_names, "ui.toggle_fullscreen", "Toggle Fullscreen");
596 conf_names = tern_insert_ptr(conf_names, "ui.soft_reset", "Soft Reset");
597 conf_names = tern_insert_ptr(conf_names, "ui.reload", "Reload ROM");
598 conf_names = tern_insert_ptr(conf_names, "ui.sms_pause", "SMS Pause");
599 conf_names = tern_insert_ptr(conf_names, "ui.toggle_keyboard_captured", "Toggle Keyboard Capture");
600 }
601 return tern_find_ptr_default(conf_names, option, (void *)option);
602 }
603
604 static uint8_t controller_binding_changed;
605 static void bind_option_group(struct nk_context *context, char *name, const char **options, uint32_t num_options)
606 {
607 float margin = context->style.font->height * 2;
608 nk_layout_row_static(context, (context->style.font->height + 3) * ((num_options + 2) / 3) + context->style.font->height*2.1, render_width() - margin, 1);
609 if (nk_group_begin(context, name, NK_WINDOW_TITLE|NK_WINDOW_NO_SCROLLBAR)) {
610 nk_layout_row_static(context, context->style.font->height, (render_width() - margin - context->style.font->height) / 3, 3);
611 for (int i = 0; i < num_options; i++)
612 {
613 if (nk_button_label(context, translate_binding_option(options[i]))) {
614 *current_bind_dest = options[i];
615 controller_binding_changed = 1;
616 pop_view();
617 }
618 }
619 nk_group_end(context);
620 }
621 }
622
623 static void view_button_binding(struct nk_context *context)
624 {
625 static const char *pad_opts[] = {
626 "gamepads.n.up",
627 "gamepads.n.down",
628 "gamepads.n.left",
629 "gamepads.n.right",
630 "gamepads.n.a",
631 "gamepads.n.b",
632 "gamepads.n.c",
633 "gamepads.n.x",
634 "gamepads.n.y",
635 "gamepads.n.z",
636 "gamepads.n.start",
637 "gamepads.n.mode"
638 };
639 static const char *system_buttons[] = {
640 "ui.soft_reset",
641 "ui.reload",
642 "ui.sms_pause"
643 };
644 static const char *emu_control[] = {
645 "ui.save_state",
646 "ui.exit",
647 "ui.toggle_fullscreen",
648 "ui.screenshot",
649 "ui.release_mouse",
650 "ui.toggle_keyboard_captured"
651 };
652 static const char *debugger[] = {
653 "ui.vdp_debug_mode",
654 "ui.vdp_debug_pal",
655 "ui.enter_debugger"
656 };
657 static const char *speeds[] = {
658 "ui.next_speed",
659 "ui.prev_speed",
660 "ui.set_speed.0",
661 "ui.set_speed.1",
662 "ui.set_speed.2",
663 "ui.set_speed.3",
664 "ui.set_speed.4",
665 "ui.set_speed.5",
666 "ui.set_speed.6",
667 "ui.set_speed.7",
668 "ui.set_speed.8",
669 "ui.set_speed.9"
670 };
671
672 if (nk_begin(context, "Button Binding", nk_rect(0, 0, render_width(), render_height()), 0)) {
673 bind_option_group(context, "Controller Buttons", pad_opts, sizeof(pad_opts)/sizeof(*pad_opts));
674 bind_option_group(context, "System Buttons", system_buttons, sizeof(system_buttons)/sizeof(*system_buttons));
675 bind_option_group(context, "Emulator Control", emu_control, sizeof(emu_control)/sizeof(*emu_control));
676 bind_option_group(context, "Debugging", debugger, sizeof(debugger)/sizeof(*debugger));
677 bind_option_group(context, "Speed Control", speeds, sizeof(speeds)/sizeof(*speeds));
678
679 nk_layout_row_static(context, context->style.font->height, (render_width() - 80)/4, 1);
680 if (nk_button_label(context, "Back")) {
681 pop_view();
682 }
683 nk_end(context);
684 }
685 }
686
687 static void binding_box(struct nk_context *context, pad_bind_config *bindings, char *name, float x, float y, float width, int num_binds, int *binds)
688 {
689 const struct nk_user_font *font = context->style.font;
690 float row_height = font->height * 2;
691
692 char const **labels = calloc(sizeof(char *), num_binds);
693 char const ***conf_vals = calloc(sizeof(char *), num_binds);
694 float max_width = 0.0f;
695
696 int skipped = 0;
697 for (int i = 0; i < num_binds; i++)
698 {
699 if (binds[i] & AXIS) {
700 labels[i] = get_axis_label(&selected_controller_info, binds[i] & ~AXIS);
701 conf_vals[i] = &bindings->triggers[(binds[i] & ~AXIS) - SDL_CONTROLLER_AXIS_TRIGGERLEFT];
702 } else if (binds[i] & STICKDIR) {
703 static char const * dirs[] = {"Up", "Down", "Right", "Left"};
704 labels[i] = dirs[binds[i] & 3];
705 conf_vals[i] = &(binds[i] & LEFTSTICK ? bindings->left_stick : bindings->right_stick)[binds[i] & 3];
706 } else {
707 labels[i] = get_button_label(&selected_controller_info, binds[i]);
708 conf_vals[i] = &bindings->button_binds[binds[i]];
709 }
710 if (!labels[i]) {
711 skipped++;
712 continue;
713 }
714 float lb_width = font->width(font->userdata, font->height, labels[i], strlen(labels[i]));
715 max_width = max_width < lb_width ? lb_width : max_width;
716 }
717 nk_layout_space_push(context, nk_rect(x, y, width, (num_binds - skipped) * (row_height + 4) + 4));
718 nk_group_begin(context, name, NK_WINDOW_BORDER | NK_WINDOW_NO_SCROLLBAR);
719
720 float widths[] = {max_width + 3, width - (max_width + 6)};
721 nk_layout_row(context, NK_STATIC, row_height, 2, widths);
722 for (int i = 0; i < num_binds; i++)
723 {
724 if (!labels[i]) {
725 continue;
726 }
727 nk_label(context, labels[i], NK_TEXT_LEFT);
728 const char *name = *conf_vals[i] ? translate_binding_option(*conf_vals[i]) : "None";
729 if (nk_button_label(context, name)) {
730 current_bind_dest = conf_vals[i];
731 push_view(view_button_binding);
732 }
733 }
734 free(labels);
735 free(conf_vals);
736 nk_group_end(context);
737 }
738
739 static void button_iter(char *key, tern_val val, uint8_t valtype, void *data)
740 {
741 pad_bind_config *bindings = data;
742 if (valtype != TVAL_PTR) {
743 return;
744 }
745 int button = render_lookup_button(key);
746 if (button != SDL_CONTROLLER_BUTTON_INVALID) {
747 bindings->button_binds[button] = val.ptrval;
748 }
749 }
750
751 static void axis_iter(char *key, tern_val val, uint8_t valtype, void *data)
752 {
753 pad_bind_config *bindings = data;
754 if (valtype != TVAL_PTR) {
755 return;
756 }
757 int axis;
758 uint8_t is_negative = 0;
759 char *period = strchr(key, '.');
760 if (period) {
761 char *tmp = malloc(period-key + 1);
762 memcpy(tmp, key, period-key);
763 tmp[period-key] = 0;
764 axis = render_lookup_axis(tmp);
765 free(tmp);
766 is_negative = strcmp(period+1, "negative") == 0;
767 } else {
768 axis = render_lookup_axis(key);
769 }
770 switch (axis)
771 {
772 case SDL_CONTROLLER_AXIS_LEFTX:
773 case SDL_CONTROLLER_AXIS_LEFTY:
774 bindings->left_stick[(axis - SDL_CONTROLLER_AXIS_LEFTX) * 2 + is_negative] = val.ptrval;
775 break;
776 case SDL_CONTROLLER_AXIS_RIGHTX:
777 case SDL_CONTROLLER_AXIS_RIGHTY:
778 bindings->right_stick[(axis - SDL_CONTROLLER_AXIS_RIGHTX) * 2 + is_negative] = val.ptrval;
779 break;
780 case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
781 case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
782 bindings->triggers[axis-SDL_CONTROLLER_AXIS_TRIGGERLEFT] = val.ptrval;
783 break;
784 }
785 }
786
787 enum {
788 SIMILAR_CONTROLLERS,
789 IDENTICAL_CONTROLLERS,
790 BY_INDEX,
791 DEFAULT,
792 NUM_DEST_TYPES
793 };
794
795 //it would be cleaner to generate this algorithmically for 4th and up,
796 //but BlastEm only supports 8 controllers currently so it's not worth the effort
797 static const char *by_index_names[] = {
798 "Use for 1st controller",
799 "Use for 2nd controller",
800 "Use for 3rd controller",
801 "Use for 4th controller",
802 "Use for 5th controller",
803 "Use for 6th controller",
804 "Use for 7th controller",
805 "Use for 8th controller",
806 };
807
808 static void save_stick_binds(char *axes_key, size_t axes_key_size, const char **bindings, char *prefix)
809 {
810 for (int i = 0; i < NUM_AXIS_DIRS; i++)
811 {
812 char axis = (i / 2) ? 'x' : 'y';
813 char *suffix = (i % 2) ? ".negative" : ".positive";
814 size_t prefix_len = strlen(prefix), suffix_len = strlen(suffix);
815 size_t full_key_size = axes_key_size + prefix_len + 1 + suffix_len + 2;
816 char *full_key = malloc(full_key_size);
817 memcpy(full_key, axes_key, axes_key_size);
818 memcpy(full_key + axes_key_size, prefix, prefix_len);
819 full_key[axes_key_size+prefix_len] = axis;
820 memcpy(full_key + axes_key_size + prefix_len + 1, suffix, suffix_len +1);
821 full_key[axes_key_size + prefix_len + 1 + suffix_len + 1] = 0;
822
823 if (bindings[i]) {
824 tern_insert_path(config, full_key, (tern_val){.ptrval = strdup(bindings[i])}, TVAL_PTR);
825 } else {
826 tern_val prev_val;
827 uint8_t prev_type = tern_delete_path(&config, full_key, &prev_val);
828 if (prev_type == TVAL_PTR) {
829 free(prev_val.ptrval);
830 }
831 }
832
833 free(full_key);
834 }
835 }
836
837 static pad_bind_config *bindings;
838 static void handle_dest_clicked(uint32_t dest)
839 {
840 char key_buf[12];
841 char *key;
842 switch (dest)
843 {
844 case SIMILAR_CONTROLLERS:
845 key = make_controller_type_key(&selected_controller_info);
846 break;
847 case IDENTICAL_CONTROLLERS:
848 key = render_joystick_type_id(selected_controller);
849 break;
850 case BY_INDEX:
851 snprintf(key_buf, sizeof(key_buf), "%d", selected_controller);
852 key = key_buf;
853 break;
854 default:
855 key = "default";
856 break;
857 }
858 static const char base_path[] = "bindings\0pads";
859 size_t pad_key_size = sizeof(base_path) + strlen(key) + 1;
860 char *pad_key = malloc(pad_key_size);
861 memcpy(pad_key, base_path, sizeof(base_path));
862 strcpy(pad_key + sizeof(base_path), key);
863 static const char dpad_base[] = "dpads\0""0";
864 size_t dpad_key_size = pad_key_size + sizeof(dpad_base);
865 char *dpad_key = malloc(dpad_key_size);
866 memcpy(dpad_key, pad_key, pad_key_size);
867 memcpy(dpad_key + pad_key_size, dpad_base, sizeof(dpad_base));
868 static const char button_base[] = "buttons";
869 size_t button_key_size = pad_key_size + sizeof(button_base);
870 char *button_key = malloc(button_key_size);
871 memcpy(button_key, pad_key, pad_key_size);
872 memcpy(button_key + pad_key_size, button_base, sizeof(button_base));
873
874 char *final_key;
875 for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++)
876 {
877 char *base;
878 const char *suffix;
879 size_t base_key_len;
880 if ( i < SDL_CONTROLLER_BUTTON_DPAD_UP) {
881 suffix = SDL_GameControllerGetStringForButton(i);
882 base_key_len = button_key_size;
883 base = button_key;
884
885
886 } else {
887 static const char *dir_keys[] = {"up", "down", "left", "right"};
888 suffix = dir_keys[i - SDL_CONTROLLER_BUTTON_DPAD_UP];
889 base = dpad_key;
890 base_key_len = dpad_key_size;
891 }
892 size_t suffix_len = strlen(suffix);
893 final_key = malloc(base_key_len + suffix_len + 2);
894 memcpy(final_key, base, base_key_len);
895 memcpy(final_key + base_key_len, suffix, suffix_len + 1);
896 final_key[base_key_len + suffix_len + 1] = 0;
897 if (bindings->button_binds[i]) {
898 tern_insert_path(config, final_key, (tern_val){.ptrval = strdup(bindings->button_binds[i])}, TVAL_PTR);
899 } else {
900 tern_val prev_val;
901 uint8_t prev_type = tern_delete_path(&config, final_key, &prev_val);
902 if (prev_type == TVAL_PTR) {
903 free(prev_val.ptrval);
904 }
905 }
906 free(final_key);
907 }
908 free(button_key);
909 free(dpad_key);
910
911 static const char axes_base[] = "axes";
912 size_t axes_key_size = pad_key_size + sizeof(axes_base);
913 char *axes_key = malloc(axes_key_size);
914 memcpy(axes_key, pad_key, pad_key_size);
915 memcpy(axes_key + pad_key_size, axes_base, sizeof(axes_base));
916
917 save_stick_binds(axes_key, axes_key_size,bindings->left_stick, "left");
918 save_stick_binds(axes_key, axes_key_size,bindings->right_stick, "right");
919 for (int i = SDL_CONTROLLER_AXIS_TRIGGERLEFT; i < SDL_CONTROLLER_AXIS_MAX; i++)
920 {
921 const char *suffix = SDL_GameControllerGetStringForAxis(i);
922 size_t suffix_len = strlen(suffix);
923 final_key = malloc(axes_key_size + suffix_len + 2);
924 memcpy(final_key, axes_key, axes_key_size);
925 memcpy(final_key + axes_key_size, suffix, suffix_len + 1);
926 final_key[axes_key_size + suffix_len + 1] = 0;
927 if (bindings->triggers[i - SDL_CONTROLLER_AXIS_TRIGGERLEFT]) {
928 tern_insert_path(config, final_key, (tern_val){.ptrval = strdup(bindings->triggers[i - SDL_CONTROLLER_AXIS_TRIGGERLEFT])}, TVAL_PTR);
929 } else {
930 tern_val prev_val;
931 uint8_t prev_type = tern_delete_path(&config, final_key, &prev_val);
932 if (prev_type == TVAL_PTR) {
933 free(prev_val.ptrval);
934 }
935 }
936 free(final_key);
937 }
938 free(axes_key);
939
940 free(pad_key);
941 if (dest == SIMILAR_CONTROLLERS) {
942 free(key);
943 }
944 pop_view();
945 config_dirty = 1;
946 }
947
948 void view_select_binding_dest(struct nk_context *context)
949 {
950 static menu_item options[NUM_DEST_TYPES];
951 options[IDENTICAL_CONTROLLERS].title = "Use for identical controllers";
952 options[DEFAULT].title = "Use as default";
953 options[BY_INDEX].title = by_index_names[selected_controller];
954 options[SIMILAR_CONTROLLERS].title = make_human_readable_type_name(&selected_controller_info);
955
956 if (nk_begin(context, "Select Binding Dest", nk_rect(0, 0, render_width(), render_height()), NK_WINDOW_NO_SCROLLBAR)) {
957 menu(context, NUM_DEST_TYPES, options, handle_dest_clicked);
958 nk_end(context);
959 }
960 free((char *)options[SIMILAR_CONTROLLERS].title);
961 }
962
963 static ui_image *select_best_image(controller_info *info)
964 {
965 if (info->variant != VARIANT_NORMAL) {
966 return controller_ps4_6b;
967 } else if (info->type == TYPE_PSX) {
968 return controller_ps4;
969 } else {
970 return controller_360;
971 }
972 }
973
974 void view_controller_bindings(struct nk_context *context)
975 {
976 if (nk_begin(context, "Controller Bindings", nk_rect(0, 0, render_width(), render_height()), NK_WINDOW_NO_SCROLLBAR)) {
977 if (!bindings) {
978 bindings = calloc(1, sizeof(*bindings));
979 tern_node *pad = get_binding_node_for_pad(selected_controller);
980 if (pad) {
981 tern_foreach(tern_find_node(pad, "buttons"), button_iter, bindings);
982 tern_foreach(tern_find_node(pad, "axes"), axis_iter, bindings);
983 tern_node *dpad = tern_find_path(pad, "dpads\0" "0\0", TVAL_NODE).ptrval;
984 const char *dir_keys[] = {"up", "down", "right", "left"};
985 const int button_idx[] = {SDL_CONTROLLER_BUTTON_DPAD_UP, SDL_CONTROLLER_BUTTON_DPAD_DOWN, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, SDL_CONTROLLER_BUTTON_DPAD_LEFT};
986 for (int i = 0; i < NUM_AXIS_DIRS; i++)
987 {
988 bindings->button_binds[button_idx[i]] = tern_find_ptr(dpad, dir_keys[i]);
989 }
990 }
991 }
992
993 float orig_height = def_font->handle.height;
994 def_font->handle.height *= 0.5f;
995
996 uint32_t avail_height = render_height() - 2 * orig_height;
997 float desired_width = render_width() * 0.5f, desired_height = avail_height * 0.5f;
998 ui_image *controller_image = select_best_image(&selected_controller_info);
999
1000 float controller_ratio = (float)controller_image->width / (float)controller_image->height;
1001
1002 const struct nk_user_font *font = context->style.font;
1003 int MIN_BIND_BOX_WIDTH = font->width(font->userdata, font->height, "Right", strlen("Right"))
1004 + def_font->handle.width(font->userdata, font->height, "Internal Screenshot", strlen("Internal Screenshot"));
1005
1006 if (render_width() - desired_width < 2.5f*MIN_BIND_BOX_WIDTH) {
1007 desired_width = render_width() - 2.5f*MIN_BIND_BOX_WIDTH;
1008 }
1009
1010 if (desired_width / desired_height > controller_ratio) {
1011 desired_width = desired_height * controller_ratio;
1012 } else {
1013 desired_height = desired_width / controller_ratio;
1014 }
1015 float img_left = render_width() / 2.0f - desired_width / 2.0f;
1016 float img_top = avail_height / 2.0f - desired_height / 2.0f;
1017 float img_right = img_left + desired_width;
1018 float img_bot = img_top + desired_height;
1019 nk_layout_space_begin(context, NK_STATIC, avail_height, INT_MAX);
1020 nk_layout_space_push(context, nk_rect(img_left, img_top, desired_width, desired_height));
1021 nk_image(context, controller_image->ui);
1022
1023 float bind_box_width = (render_width() - img_right) * 0.8f;
1024 if (bind_box_width < MIN_BIND_BOX_WIDTH) {
1025 bind_box_width = render_width() - img_right;
1026 if (bind_box_width > MIN_BIND_BOX_WIDTH) {
1027 bind_box_width = MIN_BIND_BOX_WIDTH;
1028 }
1029 } else if (bind_box_width > MAX_BIND_BOX_WIDTH) {
1030 bind_box_width = MAX_BIND_BOX_WIDTH;
1031 }
1032 float bind_box_left;
1033 if (bind_box_width >= (render_width() - img_right)) {
1034 bind_box_left = img_right;
1035 } else {
1036 bind_box_left = img_right + (render_width() - img_right) / 2.0f - bind_box_width / 2.0f;
1037 }
1038
1039 if (selected_controller_info.variant == VARIANT_NORMAL) {
1040 binding_box(context, bindings, "Action Buttons", bind_box_left, img_top, bind_box_width, 4, (int[]){
1041 SDL_CONTROLLER_BUTTON_A,
1042 SDL_CONTROLLER_BUTTON_B,
1043 SDL_CONTROLLER_BUTTON_X,
1044 SDL_CONTROLLER_BUTTON_Y
1045 });
1046 } else {
1047 binding_box(context, bindings, "Action Buttons", bind_box_left, img_top, bind_box_width, 6, (int[]){
1048 SDL_CONTROLLER_BUTTON_A,
1049 SDL_CONTROLLER_BUTTON_B,
1050 selected_controller_info.variant == VARIANT_6B_RIGHT ? AXIS | SDL_CONTROLLER_AXIS_TRIGGERRIGHT : SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
1051 SDL_CONTROLLER_BUTTON_X,
1052 SDL_CONTROLLER_BUTTON_Y,
1053 selected_controller_info.variant == VARIANT_6B_RIGHT ? SDL_CONTROLLER_BUTTON_RIGHTSHOULDER : SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
1054 });
1055 }
1056
1057 binding_box(context, bindings, "Right Shoulder", bind_box_left, font->height/2, bind_box_width,
1058 selected_controller_info.variant == VARIANT_6B_BUMPERS ? 1 : 2,
1059 (int[]){
1060 selected_controller_info.variant == VARIANT_6B_RIGHT ? SDL_CONTROLLER_BUTTON_LEFTSHOULDER : SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
1061 AXIS | SDL_CONTROLLER_AXIS_TRIGGERLEFT
1062 });
1063
1064 binding_box(context, bindings, "Misc Buttons", (render_width() - bind_box_width) / 2, font->height/2, bind_box_width, 3, (int[]){
1065 SDL_CONTROLLER_BUTTON_BACK,
1066 SDL_CONTROLLER_BUTTON_GUIDE,
1067 SDL_CONTROLLER_BUTTON_START
1068 });
1069
1070 if (selected_controller_info.variant == VARIANT_NORMAL)
1071 {
1072 binding_box(context, bindings, "Right Stick", img_right - desired_width/3, img_bot, bind_box_width, 5, (int[]){
1073 RIGHTSTICK | UP,
1074 RIGHTSTICK | DOWN,
1075 RIGHTSTICK | LEFT,
1076 RIGHTSTICK | RIGHT,
1077 SDL_CONTROLLER_BUTTON_RIGHTSTICK
1078 });
1079 }
1080
1081 bind_box_left -= img_right;
1082 float dpad_left, dpad_top;
1083 if (selected_controller_info.variant == VARIANT_NORMAL)
1084 {
1085 binding_box(context, bindings, "Left Stick", bind_box_left, img_top, bind_box_width, 5, (int[]){
1086 LEFTSTICK | UP,
1087 LEFTSTICK | DOWN,
1088 LEFTSTICK | LEFT,
1089 LEFTSTICK | RIGHT,
1090 SDL_CONTROLLER_BUTTON_LEFTSTICK
1091 });
1092 dpad_left = img_left - desired_width/6;
1093 dpad_top = img_bot + font->height * 1.5;
1094 } else {
1095 dpad_left = bind_box_left;
1096 dpad_top = img_top;
1097 }
1098
1099 binding_box(context, bindings, "Left Shoulder", bind_box_left, font->height/2, bind_box_width,
1100 selected_controller_info.variant == VARIANT_6B_BUMPERS ? 1 : 2,
1101 (int[]){
1102 selected_controller_info.variant == VARIANT_6B_RIGHT ? SDL_CONTROLLER_BUTTON_LEFTSTICK : SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
1103 SDL_CONTROLLER_BUTTON_RIGHTSTICK
1104 });
1105
1106 binding_box(context, bindings, "D-pad", dpad_left, dpad_top, bind_box_width, 4, (int[]){
1107 SDL_CONTROLLER_BUTTON_DPAD_UP,
1108 SDL_CONTROLLER_BUTTON_DPAD_DOWN,
1109 SDL_CONTROLLER_BUTTON_DPAD_LEFT,
1110 SDL_CONTROLLER_BUTTON_DPAD_RIGHT
1111 });
1112
1113 nk_layout_space_end(context);
1114
1115 def_font->handle.height = orig_height;
1116 nk_layout_row_static(context, orig_height + 4, (render_width() - 2*orig_height) / 4, 1);
1117 if (nk_button_label(context, "Back")) {
1118 pop_view();
1119 if (controller_binding_changed) {
1120 push_view(view_select_binding_dest);
1121 }
1122 }
1123 nk_end(context);
1124 }
1125 }
1126
1127 static int current_button;
1128 static int current_axis;
1129 static int button_pressed, last_button;
1130 static int hat_moved, hat_value, last_hat, last_hat_value;
1131 static int axis_moved, axis_value, last_axis;
1132 static char *mapping_string;
1133 static size_t mapping_pos;
1134
1135 static void start_mapping(void)
1136 {
1137 const char *name;
1138 mapping_string[mapping_pos++] = ',';
1139 if (current_button != SDL_CONTROLLER_BUTTON_MAX) {
1140 name = SDL_GameControllerGetStringForButton(current_button);
1141 } else {
1142 name = SDL_GameControllerGetStringForAxis(current_axis);
1143 }
1144 size_t namesz = strlen(name);
1145 memcpy(mapping_string + mapping_pos, name, namesz);
1146 mapping_pos += namesz;
1147 mapping_string[mapping_pos++] = ':';
1148 }
1149
1150 #define QUIET_FRAMES 9
1151 static void view_controller_mappings(struct nk_context *context)
1152 {
1153 char buffer[512];
1154 static int quiet, button_a = -1, button_a_axis = -1;
1155 uint8_t added_mapping = 0;
1156 if (nk_begin(context, "Controllers", nk_rect(0, 0, render_width(), render_height()), NK_WINDOW_NO_SCROLLBAR)) {
1157
1158 nk_layout_space_begin(context, NK_STATIC, render_height() - context->style.font->height, 3);
1159
1160 if (current_button < SDL_CONTROLLER_BUTTON_MAX) {
1161 snprintf(buffer, sizeof(buffer), "Press Button %s", get_button_label(&selected_controller_info, current_button));
1162 } else {
1163 snprintf(buffer, sizeof(buffer), "Move Axis %s", get_axis_label(&selected_controller_info, current_axis));
1164 }
1165
1166 float height = context->style.font->height * 1.25;
1167 float top = render_height()/2 - 1.5 * height;
1168 float width = render_width() - context->style.font->height;
1169
1170 nk_layout_space_push(context, nk_rect(0, top, width, height));
1171 nk_label(context, buffer, NK_TEXT_CENTERED);
1172 if (current_button > SDL_CONTROLLER_BUTTON_B) {
1173 nk_layout_space_push(context, nk_rect(0, top + height, width, height));
1174 nk_label(context, "OR", NK_TEXT_CENTERED);
1175
1176 nk_layout_space_push(context, nk_rect(0, top + 2.0 * height, width, height));
1177 snprintf(buffer, sizeof(buffer), "Press Button %s to skip", get_button_label(&selected_controller_info, SDL_CONTROLLER_BUTTON_A));
1178 nk_label(context, buffer, NK_TEXT_CENTERED);
1179 }
1180
1181 nk_layout_space_end(context);
1182 if (quiet) {
1183 --quiet;
1184 } else {
1185 if (button_pressed >= 0 && button_pressed != last_button) {
1186 if (current_button <= SDL_CONTROLLER_BUTTON_B || button_pressed != button_a) {
1187 start_mapping();
1188 mapping_string[mapping_pos++] = 'b';
1189 if (button_pressed > 9) {
1190 mapping_string[mapping_pos++] = '0' + button_pressed / 10;
1191 }
1192 mapping_string[mapping_pos++] = '0' + button_pressed % 10;
1193 last_button = button_pressed;
1194 if (current_button == SDL_CONTROLLER_BUTTON_A) {
1195 button_a = button_pressed;
1196 }
1197 }
1198 added_mapping = 1;
1199 } else if (hat_moved >= 0 && hat_value && (hat_moved != last_hat || hat_value != last_hat_value)) {
1200 start_mapping();
1201 mapping_string[mapping_pos++] = 'h';
1202 mapping_string[mapping_pos++] = '0' + hat_moved;
1203 mapping_string[mapping_pos++] = '.';
1204 mapping_string[mapping_pos++] = '0' + hat_value;
1205 added_mapping = 1;
1206
1207 last_hat = hat_moved;
1208 last_hat_value = hat_value;
1209 } else if (axis_moved >= 0 && abs(axis_value) > 1000 && axis_moved != last_axis) {
1210 if (current_button <= SDL_CONTROLLER_BUTTON_B || axis_moved != button_a_axis) {
1211 start_mapping();
1212 mapping_string[mapping_pos++] = 'a';
1213 if (axis_moved > 9) {
1214 mapping_string[mapping_pos++] = '0' + axis_moved / 10;
1215 }
1216 mapping_string[mapping_pos++] = '0' + axis_moved % 10;
1217 last_axis = axis_moved;
1218 }
1219 added_mapping = 1;
1220 }
1221 }
1222
1223 if (added_mapping) {
1224 quiet = QUIET_FRAMES;
1225 if (current_button < SDL_CONTROLLER_BUTTON_MAX) {
1226 current_button++;
1227 if (current_button == SDL_CONTROLLER_BUTTON_MAX) {
1228 current_axis = 0;
1229 }
1230 } else {
1231 current_axis++;
1232 if (current_axis == SDL_CONTROLLER_AXIS_MAX) {
1233 button_a = -1;
1234 button_a_axis = -1;
1235 mapping_string[mapping_pos] = 0;
1236 save_controller_mapping(selected_controller, mapping_string);
1237 free(mapping_string);
1238 pop_view();
1239 push_view(view_controller_bindings);
1240 controller_binding_changed = 0;
1241 }
1242 }
1243 }
1244 button_pressed = -1;
1245 hat_moved = -1;
1246 axis_moved = -1;
1247 nk_end(context);
1248 }
1249 }
1250
1251 static void view_controller_variant(struct nk_context *context)
1252 {
1253 uint8_t selected = 0;
1254 if (nk_begin(context, "Controller Type", nk_rect(0, 0, render_width(), render_height()), 0)) {
1255 nk_layout_row_static(context, context->style.font->height*1.25, render_width() - context->style.font->height * 2, 1);
1256 nk_label(context, "", NK_TEXT_CENTERED);
1257 nk_label(context, "Select the layout that", NK_TEXT_CENTERED);
1258 nk_label(context, "best matches your controller", NK_TEXT_CENTERED);
1259 nk_label(context, "", NK_TEXT_CENTERED);
1260 if (nk_button_label(context, "4 face buttons")) {
1261 selected_controller_info.variant = VARIANT_NORMAL;
1262 selected = 1;
1263 }
1264 char buffer[512];
1265 snprintf(buffer, sizeof(buffer), "6 face buttons including %s and %s",
1266 get_button_label(&selected_controller_info, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER),
1267 get_axis_label(&selected_controller_info, SDL_CONTROLLER_AXIS_TRIGGERRIGHT)
1268 );
1269 if (nk_button_label(context, buffer)) {
1270 selected_controller_info.variant = VARIANT_6B_RIGHT;
1271 selected = 1;
1272 }
1273 snprintf(buffer, sizeof(buffer), "6 face buttons including %s and %s",
1274 get_button_label(&selected_controller_info, SDL_CONTROLLER_BUTTON_LEFTSHOULDER),
1275 get_button_label(&selected_controller_info, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)
1276 );
1277 if (nk_button_label(context, buffer)) {
1278 selected_controller_info.variant = VARIANT_6B_BUMPERS;
1279 selected = 1;
1280 }
1281 nk_end(context);
1282 }
1283 if (selected) {
1284 save_controller_info(selected_controller, &selected_controller_info);
1285 pop_view();
1286 SDL_GameController *controller = render_get_controller(selected_controller);
1287 if (controller) {
1288 push_view(view_controller_bindings);
1289 controller_binding_changed = 0;
1290 SDL_GameControllerClose(controller);
1291 } else {
1292 current_button = SDL_CONTROLLER_BUTTON_A;
1293 button_pressed = -1;
1294 last_button = -1;
1295 last_hat = -1;
1296 axis_moved = -1;
1297 last_axis = -1;
1298 SDL_Joystick *joy = render_get_joystick(selected_controller);
1299 const char *name = SDL_JoystickName(joy);
1300 size_t namesz = strlen(name);
1301 mapping_string = malloc(512 + namesz);
1302 for (mapping_pos = 0; mapping_pos < namesz; mapping_pos++)
1303 {
1304 char c = name[mapping_pos];
1305 if (c == ',' || c == '\n' || c == '\r') {
1306 c = ' ';
1307 }
1308 mapping_string[mapping_pos] = c;
1309 }
1310
1311 push_view(view_controller_mappings);
1312 }
1313 }
1314 }
1315
1316 static void controller_type_group(struct nk_context *context, char *name, int type_id, int first_subtype_id, const char **types, uint32_t num_types)
1317 {
1318 nk_layout_row_static(context, (context->style.font->height + 3) * num_types + context->style.font->height, render_width() - 80, 1);
1319 if (nk_group_begin(context, name, NK_WINDOW_TITLE)) {
1320 nk_layout_row_static(context, context->style.font->height, render_width()/2 - 80, 2);
1321 for (int i = 0; i < num_types; i++)
1322 {
1323 if (nk_button_label(context, types[i])) {
1324 selected_controller_info.type = type_id;
1325 selected_controller_info.subtype = first_subtype_id + i;
1326 pop_view();
1327 push_view(view_controller_variant);
1328 }
1329 }
1330 nk_group_end(context);
1331 }
1332 }
1333
1334 void view_controller_type(struct nk_context *context)
1335 {
1336 if (nk_begin(context, "Controller Type", nk_rect(0, 0, render_width(), render_height()), 0)) {
1337 controller_type_group(context, "Xbox", TYPE_XBOX, SUBTYPE_XBOX, (const char *[]){
1338 "Original", "Xbox 360", "Xbox One"
1339 }, 3);
1340 controller_type_group(context, "Playstation", TYPE_PSX, SUBTYPE_PS3, (const char *[]){
1341 "PS3", "PS4"
1342 }, 2);
1343 controller_type_group(context, "Sega", TYPE_SEGA, SUBTYPE_GENESIS, (const char *[]){
1344 "Genesis", "Saturn"
1345 }, 2);
1346 controller_type_group(context, "Nintendo", TYPE_NINTENDO, SUBTYPE_WIIU, (const char *[]){
1347 "WiiU", "Switch"
1348 }, 2);
1349 nk_end(context);
1350 }
1351 }
1352
1353 void view_controllers(struct nk_context *context)
1354 {
1355 if (nk_begin(context, "Controllers", nk_rect(0, 0, render_width(), render_height()), NK_WINDOW_NO_SCROLLBAR)) {
1356 int height = (render_width() - 2*context->style.font->height) / MAX_JOYSTICKS;
1357 for (int i = 0; i < MAX_JOYSTICKS; i++)
1358 {
1359 SDL_Joystick *joy = render_get_joystick(i);
1360 if (joy) {
1361 controller_info info = get_controller_info(i);
1362 ui_image *controller_image = select_best_image(&info);
1363 int image_width = height * controller_image->width / controller_image->height;
1364 nk_layout_row_begin(context, NK_STATIC, height, 2);
1365 nk_layout_row_push(context, image_width);
1366 if (info.type == TYPE_UNKNOWN || info.type == TYPE_GENERIC_MAPPING) {
1367 nk_label(context, "?", NK_TEXT_CENTERED);
1368 } else {
1369 nk_image(context, controller_image->ui);
1370 }
1371 nk_layout_row_push(context, render_width() - image_width - 2 * context->style.font->height);
1372 if (nk_button_label(context, info.name)) {
1373 selected_controller = i;
1374 selected_controller_info = info;
1375 if (info.type == TYPE_UNKNOWN || info.type == TYPE_GENERIC_MAPPING) {
1376 push_view(view_controller_type);
1377 } else {
1378 push_view(view_controller_bindings);
1379 controller_binding_changed = 0;
1380 }
1381
1382 }
1383 nk_layout_row_end(context);
1384 }
1385 }
1386 nk_layout_row_static(context, context->style.font->height, (render_width() - 2 * context->style.font->height) / 2, 2);
1387 nk_label(context, "", NK_TEXT_LEFT);
1388 if (nk_button_label(context, "Back")) {
1389 pop_view();
1390 }
1391 nk_end(context);
1392 }
1393 }
1394
1395 void settings_toggle(struct nk_context *context, char *label, char *path, uint8_t def)
1396 {
1397 uint8_t curval = !strcmp("on", tern_find_path_default(config, path, (tern_val){.ptrval = def ? "on": "off"}, TVAL_PTR).ptrval);
1398 nk_label(context, label, NK_TEXT_LEFT);
1399 uint8_t newval = nk_check_label(context, "", curval);
1400 if (newval != curval) {
1401 config_dirty = 1;
1402 config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(newval ? "on" : "off")}, TVAL_PTR);
1403 }
1404 }
1405
1406 void settings_int_input(struct nk_context *context, char *label, char *path, char *def)
1407 {
1408 char buffer[12];
1409 nk_label(context, label, NK_TEXT_LEFT);
1410 uint32_t curval;
1411 char *curstr = tern_find_path_default(config, path, (tern_val){.ptrval = def}, TVAL_PTR).ptrval;
1412 uint32_t len = strlen(curstr);
1413 if (len > 11) {
1414 len = 11;
1415 }
1416 memcpy(buffer, curstr, len);
1417 memset(buffer+len, 0, sizeof(buffer)-len);
1418 nk_edit_string(context, NK_EDIT_SIMPLE, buffer, &len, sizeof(buffer)-1, nk_filter_decimal);
1419 buffer[len] = 0;
1420 if (strcmp(buffer, curstr)) {
1421 config_dirty = 1;
1422 config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(buffer)}, TVAL_PTR);
1423 }
1424 }
1425
1426 void settings_int_property(struct nk_context *context, char *label, char *name, char *path, int def, int min, int max)
1427 {
1428 char *curstr = tern_find_path(config, path, TVAL_PTR).ptrval;
1429 int curval = curstr ? atoi(curstr) : def;
1430 nk_label(context, label, NK_TEXT_LEFT);
1431 int val = curval;
1432 nk_property_int(context, name, min, &val, max, 1, 1.0f);
1433 if (val != curval) {
1434 char buffer[12];
1435 sprintf(buffer, "%d", val);
1436 config_dirty = 1;
1437 config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(buffer)}, TVAL_PTR);
1438 }
1439 }
1440
1441 typedef struct {
1442 char *fragment;
1443 char *vertex;
1444 } shader_prog;
1445
1446 shader_prog *get_shader_progs(dir_entry *entries, size_t num_entries, shader_prog *progs, uint32_t *num_existing, uint32_t *storage)
1447 {
1448 uint32_t num_progs = *num_existing;
1449 uint32_t prog_storage = *storage;
1450 uint32_t starting = num_progs;
1451
1452 for (uint32_t i = 0; i < num_entries; i++) {
1453 if (entries[i].is_dir) {
1454 continue;
1455 }
1456 char *no_ext = basename_no_extension(entries[i].name);
1457 uint32_t len = strlen(no_ext);
1458 if (no_ext[len-1] == 'f' && no_ext[len-2] == '.') {
1459 uint8_t dupe = 0;;
1460 for (uint32_t j = 0; j < starting; j++) {
1461 if (!strcmp(entries[i].name, progs[j].fragment)) {
1462 dupe = 1;
1463 break;
1464 }
1465 }
1466 if (!dupe) {
1467 if (num_progs == prog_storage) {
1468 prog_storage = prog_storage ? prog_storage*2 : 4;
1469 progs = realloc(progs, sizeof(progs) * prog_storage);
1470 }
1471 progs[num_progs].vertex = NULL;
1472 progs[num_progs++].fragment = strdup(entries[i].name);
1473 }
1474 }
1475 free(no_ext);
1476 }
1477
1478 for (uint32_t i = 0; i < num_entries; i++) {
1479 if (entries[i].is_dir) {
1480 continue;
1481 }
1482 char *no_ext = basename_no_extension(entries[i].name);
1483 uint32_t len = strlen(no_ext);
1484 if (no_ext[len-1] == 'v' && no_ext[len-2] == '.') {
1485 for (uint32_t j = 0; j < num_progs; j++) {
1486 if (!strncmp(no_ext, progs[j].fragment, len-1) && progs[j].fragment[len-1] == 'f' && progs[j].fragment[len] == '.') {
1487 progs[j].vertex = strdup(entries[i].name);
1488 }
1489 }
1490 }
1491 free(no_ext);
1492 }
1493 free_dir_list(entries, num_entries);
1494 *num_existing = num_progs;
1495 *storage = prog_storage;
1496 return progs;
1497 }
1498
1499 shader_prog *get_shader_list(uint32_t *num_out)
1500 {
1501 char *shader_dir = path_append(get_config_dir(), "shaders");
1502 size_t num_entries;
1503 dir_entry *entries = get_dir_list(shader_dir, &num_entries);
1504 free(shader_dir);
1505 shader_prog *progs;
1506 uint32_t num_progs = 0, prog_storage;
1507 if (num_entries) {
1508 progs = calloc(num_entries, sizeof(shader_prog));
1509 prog_storage = num_entries;
1510 progs = get_shader_progs(entries, num_entries, progs, &num_progs, &prog_storage);
1511 } else {
1512 progs = NULL;
1513 prog_storage = 0;
1514 }
1515 shader_dir = path_append(get_exe_dir(), "shaders");
1516 entries = get_dir_list(shader_dir, &num_entries);
1517 progs = get_shader_progs(entries, num_entries, progs, &num_progs, &prog_storage);
1518 *num_out = num_progs;
1519 return progs;
1520 }
1521
1522 int32_t find_match(const char **options, uint32_t num_options, char *path, char *def)
1523 {
1524 char *setting = tern_find_path_default(config, path, (tern_val){.ptrval = def}, TVAL_PTR).ptrval;
1525 int32_t selected = -1;
1526 for (uint32_t i = 0; i < num_options; i++)
1527 {
1528 if (!strcmp(setting, options[i])) {
1529 selected = i;
1530 break;
1531 }
1532 }
1533 if (selected == -1) {
1534 for (uint32_t i = 0; i < num_options; i++)
1535 {
1536 if (!strcmp(def, options[i])) {
1537 selected = i;
1538 break;
1539 }
1540 }
1541 }
1542 return selected;
1543 }
1544
1545 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)
1546 {
1547 nk_label(context, label, NK_TEXT_LEFT);
1548 int32_t next = nk_combo(context, opt_display, num_options, current, 30, nk_vec2(300, 300));
1549 if (next != current) {
1550 config_dirty = 1;
1551 config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(options[next])}, TVAL_PTR);
1552 }
1553 return next;
1554 }
1555
1556 int32_t settings_dropdown(struct nk_context *context, char *label, const char **options, uint32_t num_options, int32_t current, char *path)
1557 {
1558 return settings_dropdown_ex(context, label, options, options, num_options, current, path);
1559 }
1560
1561 void view_video_settings(struct nk_context *context)
1562 {
1563 const char *vsync_opts[] = {"on", "off", "tear"};
1564 const char *vsync_opt_names[] = {
1565 "On",
1566 "Off",
1567 "On, tear if late"
1568 };
1569 const uint32_t num_vsync_opts = sizeof(vsync_opts)/sizeof(*vsync_opts);
1570 static shader_prog *progs;
1571 static char **prog_names;
1572 static uint32_t num_progs;
1573 static uint32_t selected_prog;
1574 static int32_t selected_vsync = -1;
1575 if (selected_vsync < 0) {
1576 selected_vsync = find_match(vsync_opts, num_vsync_opts, "video\0vsync\0", "off");
1577 }
1578 if(!progs) {
1579 progs = get_shader_list(&num_progs);
1580 prog_names = calloc(num_progs, sizeof(char*));
1581 for (uint32_t i = 0; i < num_progs; i++)
1582 {
1583 prog_names[i] = basename_no_extension(progs[i].fragment);;
1584 uint32_t len = strlen(prog_names[i]);
1585 if (len > 2) {
1586 prog_names[i][len-2] = 0;
1587 }
1588 if (!progs[i].vertex) {
1589 progs[i].vertex = strdup("default.v.glsl");
1590 }
1591 if (!strcmp(
1592 progs[i].fragment,
1593 tern_find_path_default(config, "video\0fragment_shader\0", (tern_val){.ptrval = "default.f.glsl"}, TVAL_PTR).ptrval
1594 )) {
1595 selected_prog = i;
1596 }
1597 }
1598 }
1599 uint32_t width = render_width();
1600 uint32_t height = render_height();
1601 uint32_t desired_width = context->style.font->height * 10;
1602 if (desired_width > width) {
1603 desired_width = width;
1604 }
1605 if (nk_begin(context, "Video Settings", nk_rect(0, 0, width, height), 0)) {
1606 nk_layout_row_static(context, context->style.font->height, desired_width, 2);
1607 settings_toggle(context, "Fullscreen", "video\0fullscreen\0", 0);
1608 settings_toggle(context, "Open GL", "video\0gl\0", 1);
1609 settings_toggle(context, "Scanlines", "video\0scanlines\0", 0);
1610 selected_vsync = settings_dropdown_ex(context, "VSync", vsync_opts, vsync_opt_names, num_vsync_opts, selected_vsync, "video\0vsync\0");
1611 settings_int_input(context, "Windowed Width", "video\0width\0", "640");
1612 nk_label(context, "Shader", NK_TEXT_LEFT);
1613 uint32_t next_selected = nk_combo(context, (const char **)prog_names, num_progs, selected_prog, context->style.font->height, nk_vec2(desired_width, desired_width));
1614 if (next_selected != selected_prog) {
1615 selected_prog = next_selected;
1616 config_dirty = 1;
1617 config = tern_insert_path(config, "video\0fragment_shader\0", (tern_val){.ptrval = strdup(progs[next_selected].fragment)}, TVAL_PTR);
1618 config = tern_insert_path(config, "video\0vertex_shader\0", (tern_val){.ptrval = strdup(progs[next_selected].vertex)}, TVAL_PTR);
1619 }
1620 settings_int_property(context, "NTSC Overscan", "Top", "video\0ntsc\0overscan\0top\0", 2, 0, 32);
1621 settings_int_property(context, "", "Bottom", "video\0ntsc\0overscan\0bottom\0", 17, 0, 32);
1622 settings_int_property(context, "", "Left", "video\0ntsc\0overscan\0left\0", 13, 0, 32);
1623 settings_int_property(context, "", "Right", "video\0ntsc\0overscan\0right\0", 14, 0, 32);
1624 settings_int_property(context, "PAL Overscan", "Top", "video\0pal\0overscan\0top\0", 2, 0, 32);
1625 settings_int_property(context, "", "Bottom", "video\0pal\0overscan\0bottom\0", 17, 0, 32);
1626 settings_int_property(context, "", "Left", "video\0pal\0overscan\0left\0", 13, 0, 32);
1627 settings_int_property(context, "", "Right", "video\0pal\0overscan\0right\0", 14, 0, 32);
1628
1629 if (nk_button_label(context, "Back")) {
1630 pop_view();
1631 }
1632 nk_end(context);
1633 }
1634 }
1635
1636 void view_audio_settings(struct nk_context *context)
1637 {
1638 const char *rates[] = {
1639 "192000",
1640 "96000",
1641 "48000",
1642 "44100",
1643 "22050"
1644 };
1645 const char *sizes[] = {
1646 "1024",
1647 "512",
1648 "256",
1649 "128",
1650 "64"
1651 };
1652 const uint32_t num_rates = sizeof(rates)/sizeof(*rates);
1653 const uint32_t num_sizes = sizeof(sizes)/sizeof(*sizes);
1654 static int32_t selected_rate = -1;
1655 static int32_t selected_size = -1;
1656 if (selected_rate < 0 || selected_size < 0) {
1657 selected_rate = find_match(rates, num_rates, "autio\0rate\0", "48000");
1658 selected_size = find_match(sizes, num_sizes, "audio\0buffer\0", "512");
1659 }
1660 uint32_t width = render_width();
1661 uint32_t height = render_height();
1662 uint32_t desired_width = context->style.font->height * 10;
1663 if (desired_width > width) {
1664 desired_width = width;
1665 }
1666 if (nk_begin(context, "Audio Settings", nk_rect(0, 0, width, height), 0)) {
1667 nk_layout_row_static(context, context->style.font->height , desired_width, 2);
1668 selected_rate = settings_dropdown(context, "Rate in Hz", rates, num_rates, selected_rate, "audio\0rate\0");
1669 selected_size = settings_dropdown(context, "Buffer Samples", sizes, num_sizes, selected_size, "audio\0buffer\0");
1670 settings_int_input(context, "Lowpass Cutoff Hz", "audio\0lowpass_cutoff\0", "3390");
1671 if (nk_button_label(context, "Back")) {
1672 pop_view();
1673 }
1674 nk_end(context);
1675 }
1676 }
1677 void view_system_settings(struct nk_context *context)
1678 {
1679 const char *sync_opts[] = {
1680 "video",
1681 "audio"
1682 };
1683 const uint32_t num_sync_opts = sizeof(sync_opts)/sizeof(*sync_opts);
1684 static int32_t selected_sync = -1;
1685 if (selected_sync < 0) {
1686 selected_sync = find_match(sync_opts, num_sync_opts, "system\0sync_source\0", "video");
1687 }
1688 const char *regions[] = {
1689 "J - Japan",
1690 "U - Americas",
1691 "E - Europe"
1692 };
1693 const char *region_codes[] = {"J", "U", "E"};
1694 const uint32_t num_regions = sizeof(regions)/sizeof(*regions);
1695 static int32_t selected_region = -1;
1696 if (selected_region < 0) {
1697 selected_region = find_match(region_codes, num_regions, "system\0default_region\0", "U");
1698 }
1699 const char *formats[] = {
1700 "native",
1701 "gst"
1702 };
1703 const uint32_t num_formats = sizeof(formats)/sizeof(*formats);
1704 int32_t selected_format = -1;
1705 if (selected_format < 0) {
1706 selected_format = find_match(formats, num_formats, "ui\0state_format\0", "native");
1707 }
1708 const char *ram_inits[] = {
1709 "zero",
1710 "random"
1711 };
1712 const uint32_t num_inits = sizeof(ram_inits)/sizeof(*ram_inits);
1713 static int32_t selected_init = -1;
1714 if (selected_init < 0) {
1715 selected_init = find_match(ram_inits, num_inits, "system\0ram_init\0", "zero");
1716 }
1717 const char *io_opts_1[] = {
1718 "gamepad2.1",
1719 "gamepad3.1",
1720 "gamepad6.1",
1721 "mouse.1",
1722 "saturn keyboard",
1723 "xband keyboard"
1724 };
1725 const char *io_opts_2[] = {
1726 "gamepad2.2",
1727 "gamepad3.2",
1728 "gamepad6.2",
1729 "mouse.1",
1730 "saturn keyboard",
1731 "xband keyboard"
1732 };
1733 static int32_t selected_io_1 = -1;
1734 static int32_t selected_io_2 = -1;
1735 const uint32_t num_io = sizeof(io_opts_1)/sizeof(*io_opts_1);
1736 if (selected_io_1 < 0 || selected_io_2 < 0) {
1737 selected_io_1 = find_match(io_opts_1, num_io, "io\0devices\0""1\0", "gamepad6.1");
1738 selected_io_2 = find_match(io_opts_2, num_io, "io\0devices\0""2\0", "gamepad6.2");
1739 }
1740
1741 uint32_t width = render_width();
1742 uint32_t height = render_height();
1743 uint32_t desired_width = context->style.font->height * 10;
1744 if (nk_begin(context, "System Settings", nk_rect(0, 0, width, height), 0)) {
1745 nk_layout_row_static(context, context->style.font->height, desired_width, 2);
1746 selected_sync = settings_dropdown(context, "Sync Source", sync_opts, num_sync_opts, selected_sync, "system\0sync_source\0");
1747 settings_int_property(context, "68000 Clock Divider", "", "clocks\0m68k_divider\0", 7, 1, 53);
1748 settings_toggle(context, "Remember ROM Path", "ui\0remember_path\0", 1);
1749 selected_region = settings_dropdown_ex(context, "Default Region", region_codes, regions, num_regions, selected_region, "system\0default_region\0");
1750 selected_format = settings_dropdown(context, "Save State Format", formats, num_formats, selected_format, "ui\0state_format\0");
1751 selected_init = settings_dropdown(context, "Initial RAM Value", ram_inits, num_inits, selected_init, "system\0ram_init\0");
1752 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");
1753 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");
1754 if (nk_button_label(context, "Back")) {
1755 pop_view();
1756 }
1757 nk_end(context);
1758 }
1759 }
1760
1761 void view_back(struct nk_context *context)
1762 {
1763 pop_view();
1764 pop_view();
1765 current_view(context);
1766 }
1767
1768 void view_settings(struct nk_context *context)
1769 {
1770 static menu_item items[] = {
1771 {"Key Bindings", view_key_bindings},
1772 {"Controllers", view_controllers},
1773 {"Video", view_video_settings},
1774 {"Audio", view_audio_settings},
1775 {"System", view_system_settings},
1776 {"Back", view_back}
1777 };
1778
1779 if (nk_begin(context, "Settings Menu", nk_rect(0, 0, render_width(), render_height()), 0)) {
1780 menu(context, sizeof(items)/sizeof(*items), items, NULL);
1781 nk_end(context);
1782 }
1783 }
1784
1785 void exit_handler(uint32_t index)
1786 {
1787 exit(0);
1788 }
1789
1790 void view_pause(struct nk_context *context)
1791 {
1792 static menu_item items[] = {
1793 {"Resume", view_play},
1794 {"Load ROM", view_load},
1795 {"Lock On", view_lock_on},
1796 {"Save State", view_save_state},
1797 {"Load State", view_load_state},
1798 {"Settings", view_settings},
1799 {"Exit", NULL}
1800 };
1801
1802 if (nk_begin(context, "Main Menu", nk_rect(0, 0, render_width(), render_height()), 0)) {
1803 menu(context, sizeof(items)/sizeof(*items), items, exit_handler);
1804 nk_end(context);
1805 }
1806 }
1807
1808 void view_menu(struct nk_context *context)
1809 {
1810 static menu_item items[] = {
1811 {"Load ROM", view_load},
1812 {"Settings", view_settings},
1813 {"About", view_about},
1814 {"Exit", NULL}
1815 };
1816
1817 if (nk_begin(context, "Main Menu", nk_rect(0, 0, render_width(), render_height()), 0)) {
1818 menu(context, sizeof(items)/sizeof(*items), items, exit_handler);
1819 nk_end(context);
1820 }
1821 }
1822
1823 void blastem_nuklear_render(void)
1824 {
1825 if (current_view != view_play) {
1826 nk_input_end(context);
1827 current_view(context);
1828 nk_sdl_render(NK_ANTI_ALIASING_ON, 512 * 1024, 128 * 1024);
1829 nk_input_begin(context);
1830 }
1831 }
1832
1833 void ui_idle_loop(void)
1834 {
1835 const uint32_t MIN_UI_DELAY = 15;
1836 static uint32_t last;
1837 while (current_view != view_play)
1838 {
1839 uint32_t current = render_elapsed_ms();
1840 if ((current - last) < MIN_UI_DELAY) {
1841 render_sleep_ms(MIN_UI_DELAY - (current - last) - 1);
1842 }
1843 last = current;
1844 render_update_display();
1845 }
1846 if (config_dirty) {
1847 apply_updated_config();
1848 persist_config(config);
1849 config_dirty = 0;
1850 }
1851 }
1852 static void handle_event(SDL_Event *event)
1853 {
1854 if (event->type == SDL_KEYDOWN) {
1855 keycode = event->key.keysym.sym;
1856 }
1857 else if (event->type == SDL_JOYBUTTONDOWN) {
1858 button_pressed = event->jbutton.button;
1859 }
1860 else if (event->type == SDL_JOYHATMOTION) {
1861 hat_moved = event->jhat.hat;
1862 hat_value = event->jhat.value;
1863 }
1864 else if (event->type == SDL_JOYAXISMOTION) {
1865 if (event->jaxis.axis == axis_moved || abs(event->jaxis.value) > abs(axis_value) || abs(event->jaxis.value) > 1000) {
1866 axis_moved = event->jaxis.axis;
1867 axis_value = event->jaxis.value;
1868 }
1869 }
1870 nk_sdl_handle_event(event);
1871 }
1872
1873 static void context_destroyed(void)
1874 {
1875 nk_sdl_shutdown();
1876 }
1877
1878 static struct nk_image load_image_texture(uint32_t *buf, uint32_t width, uint32_t height)
1879 {
1880 GLuint tex;
1881 glGenTextures(1, &tex);
1882 glBindTexture(GL_TEXTURE_2D, tex);
1883 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1884 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1885 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1886 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1887 #ifdef USE_GLES
1888 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf);
1889 #else
1890 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, buf);
1891 #endif
1892 return nk_image_id((int)tex);
1893 }
1894
1895 static void texture_init(void)
1896 {
1897 struct nk_font_atlas *atlas;
1898 nk_sdl_font_stash_begin(&atlas);
1899 uint32_t font_size;
1900 uint8_t *font = default_font(&font_size);
1901 if (!font) {
1902 fatal_error("Failed to find default font path\n");
1903 }
1904 def_font = nk_font_atlas_add_from_memory(atlas, font, font_size, render_height() / 16, NULL);
1905 free(font);
1906 nk_sdl_font_stash_end();
1907 nk_style_set_font(context, &def_font->handle);
1908 for (uint32_t i = 0; i < num_ui_images; i++)
1909 {
1910 ui_images[i]->ui = load_image_texture(ui_images[i]->image_data, ui_images[i]->width, ui_images[i]->height);
1911 }
1912 }
1913
1914 static void context_created(void)
1915 {
1916 context = nk_sdl_init(render_get_window());
1917 texture_init();
1918 }
1919
1920 void show_pause_menu(void)
1921 {
1922 set_content_binding_state(0);
1923 context->style.window.background = nk_rgba(0, 0, 0, 128);
1924 context->style.window.fixed_background = nk_style_item_color(nk_rgba(0, 0, 0, 128));
1925 current_view = view_pause;
1926 current_system->request_exit(current_system);
1927 }
1928
1929 void show_play_view(void)
1930 {
1931 set_content_binding_state(1);
1932 current_view = view_play;
1933 }
1934
1935 static uint8_t active;
1936 uint8_t is_nuklear_active(void)
1937 {
1938 return active;
1939 }
1940
1941 uint8_t is_nuklear_available(void)
1942 {
1943 if (!render_has_gl()) {
1944 //currently no fallback if GL2 unavailable
1945 return 0;
1946 }
1947 char *style = tern_find_path(config, "ui\0style\0", TVAL_PTR).ptrval;
1948 if (!style) {
1949 return 1;
1950 }
1951 return strcmp(style, "rom") != 0;
1952 }
1953
1954 static void persist_config_exit(void)
1955 {
1956 if (config_dirty) {
1957 persist_config(config);
1958 }
1959 }
1960
1961 ui_image *load_ui_image(char *name)
1962 {
1963 uint32_t buf_size;
1964 uint8_t *buf = (uint8_t *)read_bundled_file(name, &buf_size);
1965 if (buf) {
1966 num_ui_images++;
1967 if (num_ui_images > ui_image_storage) {
1968 ui_image_storage = (ui_image_storage + 1) * 2;
1969 ui_images = realloc(ui_images, ui_image_storage * sizeof(*ui_images));
1970 }
1971 ui_image *this_image = ui_images[num_ui_images-1] = calloc(1, sizeof(ui_image));
1972 this_image->image_data = load_png(buf, buf_size, &this_image->width, &this_image->height);
1973 #ifdef USE_GLES
1974 uint32_t *cur = this_image->image_data;
1975 for (int i = 0; i < this_image->width*this_image->height; i++, cur++)
1976 {
1977 uint32_t pixel = *cur;
1978 *cur = (pixel & 0xFF00FF00) | (pixel << 16 & 0xFF0000) | (pixel >> 16 & 0xFF);
1979 }
1980 #endif
1981 free(buf);
1982 if (!this_image->image_data) {
1983 num_ui_images--;
1984 free(this_image);
1985 return NULL;
1986 }
1987 return this_image;
1988 } else {
1989 return NULL;
1990 }
1991 }
1992
1993 void blastem_nuklear_init(uint8_t file_loaded)
1994 {
1995 context = nk_sdl_init(render_get_window());
1996
1997 controller_360 = load_ui_image("images/360.png");
1998 controller_ps4 = load_ui_image("images/ps4.png");
1999 controller_ps4_6b = load_ui_image("images/ps4_6b.png");
2000
2001 texture_init();
2002
2003 if (file_loaded) {
2004 current_view = view_play;
2005 } else {
2006 current_view = view_menu;
2007 set_content_binding_state(0);
2008 }
2009 render_set_ui_render_fun(blastem_nuklear_render);
2010 render_set_event_handler(handle_event);
2011 render_set_gl_context_handlers(context_destroyed, context_created);
2012
2013 atexit(persist_config_exit);
2014
2015 active = 1;
2016 ui_idle_loop();
2017 }