Mercurial > repos > blastem
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(¤t_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 } |