24#if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_MENU_IMPL_ONCE) && defined(SDL_LIBRETRO_ENABLE_MENU) && !defined(SDL_LIBRETRO_DISABLE_MENU)
25#define SDL_LIBRETRO_MENU_IMPL_ONCE
29#ifndef NK_INCLUDE_FIXED_TYPES
30#define NK_INCLUDE_FIXED_TYPES
32#ifndef NK_INCLUDE_STANDARD_VARARGS
33#define NK_INCLUDE_STANDARD_VARARGS
35#ifndef NK_INCLUDE_DEFAULT_ALLOCATOR
36#define NK_INCLUDE_DEFAULT_ALLOCATOR
38#ifndef NK_INCLUDE_STANDARD_IO
39#define NK_INCLUDE_STANDARD_IO
41#ifndef NK_INCLUDE_FONT_BAKING
42#define NK_INCLUDE_FONT_BAKING
44#ifndef NK_INCLUDE_DEFAULT_FONT
45#define NK_INCLUDE_DEFAULT_FONT
47#ifndef NK_INCLUDE_COMMAND_USERDATA
48#define NK_INCLUDE_COMMAND_USERDATA
50#ifndef NK_INCLUDE_VERTEX_BUFFER_OUTPUT
51#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
53#ifndef NK_BUTTON_TRIGGER_ON_RELEASE
54#define NK_BUTTON_TRIGGER_ON_RELEASE
57#ifndef SDL_LIBRETRO_MENU_NO_NK_IMPLEMENTATION
58#define NK_IMPLEMENTATION
60#ifndef SDL_LIBRETRO_MENU_NUKLEAR_H
61#define SDL_LIBRETRO_MENU_NUKLEAR_H "nuklear.h"
63#include SDL_LIBRETRO_MENU_NUKLEAR_H
65#ifndef SDL_LIBRETRO_MENU_NO_NK_IMPLEMENTATION
66#define NK_SDL3_RENDERER_IMPLEMENTATION
68#ifndef SDL_LIBRETRO_MENU_NUKLEAR_SDL3_RENDERER_H
69#define SDL_LIBRETRO_MENU_NUKLEAR_SDL3_RENDERER_H "demo/sdl3_renderer/nuklear_sdl3_renderer.h"
71#include SDL_LIBRETRO_MENU_NUKLEAR_SDL3_RENDERER_H
73#ifndef SDL_LIBRETRO_MENU_NO_NK_IMPLEMENTATION
74#define NK_GAMEPAD_IMPLEMENTATION
76#ifndef SDL_LIBRETRO_MENU_NUKLEAR_GAMEPAD_H
77#define SDL_LIBRETRO_MENU_NUKLEAR_GAMEPAD_H "nuklear_gamepad.h"
79#include SDL_LIBRETRO_MENU_NUKLEAR_GAMEPAD_H
83#ifndef SDL_LIBRETRO_MENU_NO_NK_IMPLEMENTATION
84#define NK_CONSOLE_IMPLEMENTATION
90#ifndef SDL_LIBRETRO_MENU_NUKLEAR_CONSOLE_H
91#define SDL_LIBRETRO_MENU_NUKLEAR_CONSOLE_H "nuklear_console.h"
93#include SDL_LIBRETRO_MENU_NUKLEAR_CONSOLE_H
94#include "nuklear_console_sdl.h"
97#include <emscripten.h>
100#ifndef SDL_LIBRETRO_MENU_TOGGLE_KEY
106#define SDL_LIBRETRO_MENU_TOGGLE_KEY SDLK_F1
109#ifndef SDL_LIBRETRO_MENU_FONT_HEIGHT
116#define SDL_LIBRETRO_MENU_FONT_HEIGHT 13
119#ifndef SDL_LIBRETRO_MENU_DEFAULT_STYLE
125#define SDL_LIBRETRO_MENU_DEFAULT_STYLE SDL_LIBRETRO_MENU_STYLE_CATPPUCCIN_MOCHA
132#define SDL_LIBRETRO_MENU_STYLE_NAMES "Mocha|Latte|Frappe|Macchiato|Dracula|Dark"
139#define SDL_LIBRETRO_MENU_LANGUAGE_NAMES \
140 "Auto|English|Japanese|French|Spanish|German|Italian|Dutch|" \
141 "Portuguese (Brazil)|Portuguese (Portugal)|Russian|Korean|" \
142 "Chinese (Traditional)|Chinese (Simplified)|Esperanto|Polish|Vietnamese|Arabic|" \
143 "Greek|Turkish|Slovak|Persian|Hebrew|Asturian|Finnish|Indonesian|Swedish|" \
144 "Ukrainian|Czech|Catalan (Valencia)|Catalan|British English|Hungarian|" \
145 "Belarusian|Galician|Norwegian|Irish|Thai"
154typedef struct SDL_LibretroMenuOptionState {
160} SDL_LibretroMenuOptionState;
168typedef struct SDL_LibretroMenuPortState {
174} SDL_LibretroMenuPortState;
180typedef struct SDL_LibretroMenuKeyBind {
184} SDL_LibretroMenuKeyBind;
190typedef struct SDL_LibretroMenuPadBind {
194 enum nk_gamepad_button captured;
195} SDL_LibretroMenuPadBind;
201typedef struct SDL_LibretroMenuPadPort {
206 SDL_LibretroMenuPadBind binds[SDL_LIBRETRO_MAX_JOYPAD_BUTTONS];
207} SDL_LibretroMenuPadPort;
216 struct nk_context* ctx;
218 struct nk_gamepads gamepads;
219 struct nk_font_atlas* atlas;
225 bool keysHeldAtOpen[SDL_SCANCODE_COUNT];
226 unsigned int padButtonsHeldAtOpen[NK_GAMEPAD_MAX];
230 nk_console* resumeButton;
231 nk_console* stateRow;
232 nk_console* screenshotButton;
233 nk_console* resetButton;
234 nk_console* optionsButton;
235 nk_console* controllersButton;
236 nk_console* loadGameButton;
245 nk_bool fullscreenChecked;
246 nk_bool vsyncChecked;
248 nk_bool rewindChecked;
256 char loadGamePath[SDL_LIBRETRO_MAX_PATH];
259 SDL_LibretroMenuOptionState* optionStates;
260 unsigned optionStateCount;
263 unsigned builtOptionCount;
264 unsigned builtCoreGeneration;
270 SDL_LibretroMenuKeyBind keyBinds[SDL_LIBRETRO_MAX_JOYPAD_BUTTONS];
274 nk_console* gamepadButton;
278 nk_console* disksButton;
280 unsigned builtDiskCount;
284 nk_console* osdProgressWidget;
285 nk_size osdProgressValue;
286 char osdProgressLabel[128];
288 nk_console* quitButton;
291 char coreDirBuffer[SDL_LIBRETRO_MAX_PATH];
292 char saveDirBuffer[SDL_LIBRETRO_MAX_PATH];
293 char systemDirBuffer[SDL_LIBRETRO_MAX_PATH];
294 char browseDirBuffer[SDL_LIBRETRO_MAX_PATH];
295 char usernameBuffer[64];
299 nk_console* aboutButton;
300 char aboutText[4096];
301 size_t aboutTextLength;
310static void SDL_Libretro_MenuResumeClicked(SDL_LibretroMenu* menu,
void* userdata) {
321static void SDL_Libretro_MenuBackClicked(nk_console* widget,
void* user_data) {
323 SDL_Libretro_MenuResumeClicked((SDL_LibretroMenu*)user_data, NULL);
330static void SDL_Libretro_MenuLoadPendingGame(SDL_LibretroMenu* menu) {
331 if (menu->loadGamePath[0] ==
'\0') {
338#if defined(SDL_LIBRETRO_ENABLE_PHYSFS) && !defined(SDL_LIBRETRO_DISABLE_PHYSFS)
348 SDL_Log(
"Failed to load game: %s", SDL_GetError());
349 nk_console_show_message(menu->console,
"Failed to load game");
351 menu->loadGamePath[0] =
'\0';
354#ifndef __EMSCRIPTEN__
358static void SDL_Libretro_MenuGameFileChanged(nk_console* widget,
void* user_data) {
360 SDL_Libretro_MenuLoadPendingGame((SDL_LibretroMenu*)user_data);
372EM_JS(
void, SDL_Libretro_MenuOpenWebFilePicker, (SDL_LibretroMenu * menu), {
373 var input = document.createElement(
'input');
375 input.onchange = function(e) {
376 var file = e.target.files[0];
378 var reader =
new FileReader();
379 reader.onload = function() {
380 var path =
'/' + file.name;
382 Module.FS.writeFile(path,
new Uint8Array(reader.result));
384 console.error(
'SDL_libretro: failed to write picked file to FS:', err);
387 Module.ccall(
'SDL_Libretro_MenuLoadGameFromJS', null, [
'number',
'string'], [menu, path]);
389 reader.onerror = function() {
390 console.error(
'SDL_libretro: failed to read picked file:', reader.error);
392 reader.readAsArrayBuffer(file);
402EMSCRIPTEN_KEEPALIVE
void SDL_Libretro_MenuLoadGameFromJS(SDL_LibretroMenu* menu,
const char* path) {
403 if (menu == NULL || path == NULL) {
406 SDL_strlcpy(menu->loadGamePath, path,
sizeof(menu->loadGamePath));
407 SDL_Libretro_MenuLoadPendingGame(menu);
413static void SDL_Libretro_MenuWebLoadGameClicked(SDL_LibretroMenu* menu,
void* userdata) {
415 SDL_Libretro_MenuOpenWebFilePicker(menu);
422static void SDL_Libretro_MenuSaveStateClicked(SDL_LibretroMenu* menu,
void* userdata) {
424 char savePath[SDL_LIBRETRO_MAX_PATH];
427 nk_console_show_message(menu->console,
"State saved");
430 nk_console_show_message(menu->console,
"Save State failed");
437static void SDL_Libretro_MenuLoadStateClicked(SDL_LibretroMenu* menu,
void* userdata) {
439 char savePath[SDL_LIBRETRO_MAX_PATH];
445 nk_console_show_message(menu->console,
"Load State failed");
467 nk_console_show_message(menu->console,
"Screenshot saved");
470 SDL_Log(
"Failed to save screenshot: %s", SDL_GetError());
471 nk_console_show_message(menu->console,
"Screenshot failed");
478static void SDL_Libretro_MenuResetClicked(SDL_LibretroMenu* menu,
void* userdata) {
489static const char* SDL_Libretro_MenuPixelFormatName(
enum retro_pixel_format format) {
491 case RETRO_PIXEL_FORMAT_0RGB1555:
493 case RETRO_PIXEL_FORMAT_XRGB8888:
495 case RETRO_PIXEL_FORMAT_RGB565:
506static void SDL_Libretro_MenuAddBackButton(nk_console* parent,
const char* label) {
507 nk_console_button_set_symbol(
508 nk_console_button_onclick(parent, label, &nk_console_button_back),
509 NK_SYMBOL_TRIANGLE_UP);
516static nk_console* SDL_Libretro_MenuAddSubmenu(nk_console* parent,
const char* label,
enum nk_symbol_type symbol) {
517 nk_console* button = nk_console_button(parent, label);
518 nk_console_button_set_symbol(button, symbol);
519 SDL_Libretro_MenuAddBackButton(button, label);
529static nk_console* SDL_Libretro_MenuOnChanged(nk_console* widget, nk_console_event callback,
void* user_data) {
530 nk_console_add_event_handler(widget, NK_CONSOLE_EVENT_CHANGED, callback, user_data, NULL);
539static void SDL_Libretro_MenuGuardActiveParent(SDL_LibretroMenu* menu, nk_console* root) {
540 nk_console* active = nk_console_active_parent(menu->console);
541 for (nk_console* it = active; it != NULL; it = it->parent) {
543 if (active != root) {
544 nk_console_set_active_parent(root);
558typedef struct SDL_LibretroMenuItem {
565} SDL_LibretroMenuItem;
570static void SDL_Libretro_MenuItemChanged(nk_console* widget,
void* user_data) {
572 SDL_LibretroMenuItem* item = (SDL_LibretroMenuItem*)user_data;
573 if (item->value != NULL) {
574 *item->value = item->checked == nk_true;
576 if (item->callback != NULL) {
577 item->callback(item->menu, item->userdata);
584static void SDL_Libretro_MenuItemDestroy(nk_console* widget,
void* user_data) {
586 SDL_LibretroMenuItem* item = (SDL_LibretroMenuItem*)user_data;
587 SDL_free(item->label);
594static SDL_LibretroMenuItem* SDL_Libretro_MenuCreateItem(SDL_LibretroMenu* menu,
const char* label,
SDL_LibretroMenuCallback callback,
void* userdata) {
595 SDL_LibretroMenuItem* item = (SDL_LibretroMenuItem*)SDL_calloc(1,
sizeof(SDL_LibretroMenuItem));
600 item->callback = callback;
601 item->userdata = userdata;
602 item->label = SDL_strdup(label);
603 if (item->label == NULL) {
618static nk_console* SDL_Libretro_MenuAddButton(SDL_LibretroMenu* menu, nk_console* parent,
const char* label,
enum nk_symbol_type symbol,
SDL_LibretroMenuCallback callback,
void* userdata) {
619 SDL_LibretroMenuItem* item = SDL_Libretro_MenuCreateItem(menu, label, callback, userdata);
623 nk_console* button = nk_console_button_onclick_handler(parent, item->label, &SDL_Libretro_MenuItemChanged, item, &SDL_Libretro_MenuItemDestroy);
624 if (button == NULL) {
625 SDL_Libretro_MenuItemDestroy(NULL, item);
628 nk_console_button_set_symbol(button, symbol);
641static nk_console* SDL_Libretro_MenuAddCheckbox(SDL_LibretroMenu* menu, nk_console* parent,
const char* label, nk_bool* checked,
bool* value,
SDL_LibretroMenuCallback callback,
void* userdata) {
642 SDL_LibretroMenuItem* item = SDL_Libretro_MenuCreateItem(menu, label, callback, userdata);
646 nk_bool* storage = checked != NULL ? checked : &item->checked;
649 *storage = (nk_bool)*value;
651 nk_console* checkbox = nk_console_checkbox(parent, item->label, storage);
652 if (checkbox == NULL) {
653 SDL_Libretro_MenuItemDestroy(NULL, item);
656 nk_console_add_event_handler(checkbox, NK_CONSOLE_EVENT_CHANGED, &SDL_Libretro_MenuItemChanged, item, &SDL_Libretro_MenuItemDestroy);
665static void SDL_Libretro_MenuAboutLine(SDL_LibretroMenu* menu,
const char* format, ...) {
666 char* line = menu->aboutText + menu->aboutTextLength;
667 size_t available =
sizeof(menu->aboutText) - menu->aboutTextLength;
672 va_start(args, format);
673 int written = SDL_vsnprintf(line, available, format, args);
678 if ((
size_t)written >= available) {
679 written = (int)available - 1;
681 menu->aboutTextLength += (size_t)written + 1;
682 nk_console_label(menu->aboutButton, line);
690static void SDL_Libretro_MenuBuildAbout(SDL_LibretroMenu* menu) {
693 nk_console_free_children(menu->aboutButton);
694 menu->aboutTextLength = 0;
695 SDL_Libretro_MenuAddBackButton(menu->aboutButton,
"About");
699 int sdlVersion = SDL_GetVersion();
700 SDL_Libretro_MenuAboutLine(menu,
"SDL %d.%d.%d %s", SDL_VERSIONNUM_MAJOR(sdlVersion), SDL_VERSIONNUM_MINOR(sdlVersion), SDL_VERSIONNUM_MICRO(sdlVersion), SDL_GetRevision());
701 const char* rendererName = SDL_GetRendererName(lr->renderer);
702 SDL_Libretro_MenuAboutLine(menu,
"Renderer: %s", rendererName != NULL ? rendererName :
"Unknown");
708 if (coreVersion[0] !=
'\0') {
709 SDL_Libretro_MenuAboutLine(menu,
"Core Version: %s", coreVersion);
712 SDL_Libretro_MenuAboutLine(menu,
"Extensions: %s", extensions[0] !=
'\0' ? extensions :
"(any)");
713 SDL_Libretro_MenuAboutLine(menu,
"Pixel Format: %s", SDL_Libretro_MenuPixelFormatName(lr->core.pixelFormat));
714 if (lr->core.sampleRate > 0.0) {
715 SDL_Libretro_MenuAboutLine(menu,
"Sample Rate: %g Hz", lr->core.sampleRate);
721 char contentName[128] =
"";
723 SDL_Libretro_MenuAboutLine(menu,
"Content: %s", contentName[0] !=
'\0' ? contentName :
"(none)");
727 SDL_Libretro_MenuAboutLine(menu,
"Size: %dx%d @ %.2f FPS", width, height,
SDL_Libretro_GetFPS(lr));
739 unsigned availableCores = 0;
740 for (
unsigned i = 0; i < lr->coreLibraryCount; i++) {
741 availableCores += SDL_GetPathInfo(lr->coreLibrary[i].path, NULL) ? 1 : 0;
743 SDL_Libretro_MenuAboutLine(menu,
"Available Cores: %u", availableCores);
744 for (
unsigned i = 0; i < lr->coreLibraryCount; i++) {
745 if (SDL_GetPathInfo(lr->coreLibrary[i].path, NULL)) {
746 SDL_Libretro_MenuAboutLine(menu,
" %s", lr->coreLibrary[i].corename);
750 nk_console_label(menu->aboutButton,
"https://github.com/RobLoach/SDL_libretro");
758static void SDL_Libretro_MenuAboutOpened(SDL_LibretroMenu* menu,
void* userdata) {
760 SDL_Libretro_MenuBuildAbout(menu);
761 nk_console_set_active_parent(menu->aboutButton);
767static void SDL_Libretro_MenuQuitClicked(SDL_LibretroMenu* menu,
void* userdata) {
772 event.type = SDL_EVENT_QUIT;
773 event.quit.timestamp = SDL_GetTicksNS();
774 SDL_PushEvent(&event);
780static void SDL_Libretro_MenuVolumeChanged(nk_console* widget,
void* user_data) {
789static void SDL_Libretro_MenuFitModeChanged(nk_console* widget,
void* user_data) {
798static void SDL_Libretro_MenuFilterChanged(nk_console* widget,
void* user_data) {
807static void SDL_Libretro_MenuFullscreenChanged(SDL_LibretroMenu* menu,
void* userdata) {
809 SDL_SetWindowFullscreen(menu->lr->window, menu->fullscreenChecked == nk_true);
810 menu->settingsDirty =
true;
816static void SDL_Libretro_MenuVSyncChanged(SDL_LibretroMenu* menu,
void* userdata) {
818 SDL_SetRenderVSync(menu->lr->renderer, menu->vsyncChecked == nk_true ? 1 : 0);
819 menu->settingsDirty =
true;
825static void SDL_Libretro_MenuRewindChanged(SDL_LibretroMenu* menu,
void* userdata) {
834static void SDL_Libretro_MenuMousePortChanged(nk_console* widget,
void* user_data) {
843static void SDL_Libretro_MenuLanguageChanged(nk_console* widget,
void* user_data) {
852static void SDL_Libretro_MenuRewindBufferChanged(nk_console* widget,
void* user_data) {
862static void SDL_Libretro_MenuMuteChanged(SDL_LibretroMenu* menu,
void* userdata) {
865 if (menu->muteChecked) {
873 menu->settingsDirty =
true;
885 SDL_Scancode scancode;
887} SDL_Libretro_MenuKeyRunes[] = {
888 {SDL_SCANCODE_RETURN, NK_CONSOLE_KEY_ENTER},
889 {SDL_SCANCODE_KP_ENTER, NK_CONSOLE_KEY_ENTER},
890 {SDL_SCANCODE_TAB, NK_CONSOLE_KEY_TAB},
891 {SDL_SCANCODE_BACKSPACE, NK_CONSOLE_KEY_BACKSPACE},
892 {SDL_SCANCODE_ESCAPE, NK_CONSOLE_KEY_ESCAPE},
893 {SDL_SCANCODE_DELETE, NK_CONSOLE_KEY_DELETE},
894 {SDL_SCANCODE_UP, NK_CONSOLE_KEY_UP},
895 {SDL_SCANCODE_DOWN, NK_CONSOLE_KEY_DOWN},
896 {SDL_SCANCODE_LEFT, NK_CONSOLE_KEY_LEFT},
897 {SDL_SCANCODE_RIGHT, NK_CONSOLE_KEY_RIGHT},
898 {SDL_SCANCODE_LSHIFT, NK_CONSOLE_KEY_SHIFT},
899 {SDL_SCANCODE_RSHIFT, NK_CONSOLE_KEY_SHIFT},
900 {SDL_SCANCODE_LCTRL, NK_CONSOLE_KEY_CTRL},
901 {SDL_SCANCODE_RCTRL, NK_CONSOLE_KEY_CTRL},
902 {SDL_SCANCODE_LALT, NK_CONSOLE_KEY_ALT},
903 {SDL_SCANCODE_RALT, NK_CONSOLE_KEY_ALT},
904 {SDL_SCANCODE_F1, NK_CONSOLE_KEY_F1},
905 {SDL_SCANCODE_F2, NK_CONSOLE_KEY_F2},
906 {SDL_SCANCODE_F3, NK_CONSOLE_KEY_F3},
907 {SDL_SCANCODE_F4, NK_CONSOLE_KEY_F4},
908 {SDL_SCANCODE_F5, NK_CONSOLE_KEY_F5},
909 {SDL_SCANCODE_F6, NK_CONSOLE_KEY_F6},
910 {SDL_SCANCODE_F7, NK_CONSOLE_KEY_F7},
911 {SDL_SCANCODE_F8, NK_CONSOLE_KEY_F8},
912 {SDL_SCANCODE_F9, NK_CONSOLE_KEY_F9},
913 {SDL_SCANCODE_F10, NK_CONSOLE_KEY_F10},
914 {SDL_SCANCODE_F11, NK_CONSOLE_KEY_F11},
915 {SDL_SCANCODE_F12, NK_CONSOLE_KEY_F12},
924static nk_rune SDL_Libretro_MenuRuneFromScancode(SDL_Scancode scancode) {
925 for (
size_t i = 0; i < SDL_arraysize(SDL_Libretro_MenuKeyRunes); i++) {
926 if (SDL_Libretro_MenuKeyRunes[i].scancode == scancode) {
927 return SDL_Libretro_MenuKeyRunes[i].rune;
930 SDL_Keycode key = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE,
false);
931 if (key >= 32 && key < 127) {
934 return NK_CONSOLE_KEY_NONE;
942static SDL_Scancode SDL_Libretro_MenuScancodeFromRune(nk_rune rune) {
943 for (
size_t i = 0; i < SDL_arraysize(SDL_Libretro_MenuKeyRunes); i++) {
944 if (SDL_Libretro_MenuKeyRunes[i].rune == rune) {
945 return SDL_Libretro_MenuKeyRunes[i].scancode;
948 if (rune >=
'A' && rune <=
'Z') {
951 if (rune >= 32 && rune < 127) {
952 return SDL_GetScancodeFromKey((SDL_Keycode)rune, NULL);
954 return SDL_SCANCODE_UNKNOWN;
961static void SDL_Libretro_MenuSyncKeyBinds(SDL_LibretroMenu* menu) {
962 for (
int i = 0; i < SDL_LIBRETRO_MAX_JOYPAD_BUTTONS; i++) {
963 menu->keyBinds[i].rune = SDL_Libretro_MenuRuneFromScancode(menu->lr->keyboardPlayer1[i]);
973static void SDL_Libretro_MenuKeyBindChanged(nk_console* widget,
void* user_data) {
975 SDL_LibretroMenuKeyBind* bind = (SDL_LibretroMenuKeyBind*)user_data;
977 SDL_Scancode scancode = SDL_Libretro_MenuScancodeFromRune(bind->rune);
978 if (scancode == SDL_SCANCODE_UNKNOWN) {
979 bind->rune = SDL_Libretro_MenuRuneFromScancode(menu->lr->keyboardPlayer1[bind->button]);
991static enum nk_gamepad_button SDL_Libretro_MenuNkButtonFromSDL(SDL_GamepadButton button) {
992 for (
int i = NK_GAMEPAD_BUTTON_FIRST; i <= NK_GAMEPAD_BUTTON_R3; i++) {
993 if (nk_gamepad_sdl3_map_button(i) == button) {
994 return (
enum nk_gamepad_button)i;
997 return NK_GAMEPAD_BUTTON_INVALID;
1005static void SDL_Libretro_MenuSyncPadBinds(SDL_LibretroMenu* menu) {
1007 SDL_LibretroMenuPadPort* padPort = &menu->padPorts[port];
1008 padPort->analogIndex = (int)menu->lr->analogToDigital[port];
1009 for (
int i = 0; i < SDL_LIBRETRO_MAX_JOYPAD_BUTTONS; i++) {
1010 padPort->binds[i].captured = SDL_Libretro_MenuNkButtonFromSDL(menu->lr->gamepadButtons[port][i]);
1021static void SDL_Libretro_MenuPadBindChanged(nk_console* widget,
void* user_data) {
1023 SDL_LibretroMenuPadBind* bind = (SDL_LibretroMenuPadBind*)user_data;
1025 SDL_GamepadButton button = nk_gamepad_sdl3_map_button((
int)bind->captured);
1026 if (button == SDL_GAMEPAD_BUTTON_INVALID) {
1027 bind->captured = SDL_Libretro_MenuNkButtonFromSDL(menu->lr->gamepadButtons[bind->port][bind->button]);
1037static void SDL_Libretro_MenuAnalogToDigitalChanged(nk_console* widget,
void* user_data) {
1039 SDL_LibretroMenuPadPort* padPort = (SDL_LibretroMenuPadPort*)user_data;
1050static void SDL_Libretro_MenuBuildGamepads(SDL_LibretroMenu* menu) {
1051 if (menu->gamepadButton == NULL) {
1056 SDL_Libretro_MenuGuardActiveParent(menu, menu->gamepadButton);
1057 nk_console_free_children(menu->gamepadButton);
1058 SDL_Libretro_MenuAddBackButton(menu->gamepadButton,
"Gamepad");
1060 SDL_Libretro_MenuSyncPadBinds(menu);
1062 SDL_LibretroMenuPadPort* padPort = &menu->padPorts[port];
1063 padPort->menu = menu;
1064 padPort->port = port;
1066 const char* name = lr->gamepads[port] != NULL ? SDL_GetGamepadName(lr->gamepads[port]) : NULL;
1067 SDL_snprintf(padPort->label,
sizeof(padPort->label),
"%u. %s", port + 1, name != NULL ? name :
"No Gamepad");
1068 nk_console* player = SDL_Libretro_MenuAddSubmenu(menu->gamepadButton, padPort->label, NK_SYMBOL_TRIANGLE_RIGHT);
1069 nk_console_set_tooltip(player,
"Gamepad buttons for this player's retro controller; the triggers stay analog");
1071 nk_console_set_tooltip(
1072 SDL_Libretro_MenuOnChanged(nk_console_combobox(player,
"Analog to Digital",
"None|Left|Right",
'|', &padPort->analogIndex), &SDL_Libretro_MenuAnalogToDigitalChanged, padPort),
1073 "The chosen analog stick also presses the D-Pad directions");
1075 for (
int i = 0; i < SDL_LIBRETRO_MAX_JOYPAD_BUTTONS; i++) {
1076 padPort->binds[i].menu = menu;
1077 padPort->binds[i].port = port;
1078 padPort->binds[i].button = i;
1079 SDL_Libretro_MenuOnChanged(
1080 nk_console_input_gamepad(player, SDL_Libretro_JoypadButtonNames[i], -1, NULL, &padPort->binds[i].captured),
1081 &SDL_Libretro_MenuPadBindChanged,
1082 &padPort->binds[i]);
1095static void SDL_Libretro_MenuLoadState(SDL_LibretroMenu* menu) {
1100 menu->uiScaleIndex = 2;
1101 if (lr->ini != NULL) {
1102 Sint64 savedStyle = INI_GetInt(lr->ini, NULL,
"menutheme", (Sint64)style);
1106 if (INI_HasValue(lr->ini, NULL,
"menuuiscale")) {
1107 Sint64 savedScale = INI_GetInt(lr->ini, NULL,
"menuuiscale", 2);
1108 if (savedScale >= 0 && savedScale <= 4) {
1109 menu->uiScaleIndex = (int)savedScale;
1112 if (INI_HasValue(lr->ini, NULL,
"menufullscreen")) {
1113 SDL_SetWindowFullscreen(lr->window, INI_GetBoolean(lr->ini, NULL,
"menufullscreen",
false));
1115 if (INI_HasValue(lr->ini, NULL,
"menuvsync")) {
1116 SDL_SetRenderVSync(lr->renderer, INI_GetBoolean(lr->ini, NULL,
"menuvsync",
false) ? 1 : 0);
1118 if (INI_GetBoolean(lr->ini, NULL,
"menumuted",
false)) {
1119 menu->muteChecked = nk_true;
1120 menu->preMuteVolume = INI_GetFloat(lr->ini, NULL,
"menumutevolume", 1.0f);
1135static void SDL_Libretro_MenuSaveState(SDL_LibretroMenu* menu) {
1137 menu->settingsDirty =
false;
1138 if (lr->ini == NULL) {
1141 INI_SetInt(lr->ini, NULL,
"menutheme", (Sint64)menu->style);
1142 INI_SetInt(lr->ini, NULL,
"menuuiscale", (Sint64)menu->uiScaleIndex);
1143 INI_SetBoolean(lr->ini, NULL,
"menufullscreen", menu->fullscreenChecked == nk_true);
1144 INI_SetBoolean(lr->ini, NULL,
"menuvsync", menu->vsyncChecked == nk_true);
1145 INI_SetBoolean(lr->ini, NULL,
"menumuted", menu->muteChecked == nk_true);
1146 INI_SetFloat(lr->ini, NULL,
"menumutevolume", menu->preMuteVolume);
1153static bool SDL_Libretro_MenuValueTruthy(
const char* value) {
1154 return value != NULL && (SDL_strcasecmp(value,
"enabled") == 0 ||
1155 SDL_strcasecmp(value,
"true") == 0 ||
1156 SDL_strcasecmp(value,
"on") == 0 ||
1157 SDL_strcasecmp(value,
"yes") == 0 ||
1158 SDL_strcmp(value,
"1") == 0);
1165static bool SDL_Libretro_MenuValueFalsy(
const char* value) {
1166 return value != NULL && (SDL_strcasecmp(value,
"disabled") == 0 ||
1167 SDL_strcasecmp(value,
"false") == 0 ||
1168 SDL_strcasecmp(value,
"off") == 0 ||
1169 SDL_strcasecmp(value,
"no") == 0 ||
1170 SDL_strcmp(value,
"0") == 0);
1183 return (SDL_Libretro_MenuValueTruthy(first) && SDL_Libretro_MenuValueFalsy(second)) ||
1184 (SDL_Libretro_MenuValueFalsy(first) && SDL_Libretro_MenuValueTruthy(second));
1192static void SDL_Libretro_MenuOptionWritten(SDL_LibretroMenu* menu) {
1196 menu->optionsStale =
true;
1203static void SDL_Libretro_MenuOptionChanged(nk_console* widget,
void* user_data) {
1205 SDL_LibretroMenuOptionState* state = (SDL_LibretroMenuOptionState*)user_data;
1208 if (option == NULL || state->selected < 0 || (
unsigned)state->selected >= option->
valuesCount) {
1212 SDL_Libretro_MenuOptionWritten(menu);
1218static void SDL_Libretro_MenuOptionCheckboxChanged(SDL_LibretroMenu* menu,
void* userdata) {
1219 SDL_LibretroMenuOptionState* state = (SDL_LibretroMenuOptionState*)userdata;
1225 bool firstTruthy = SDL_Libretro_MenuValueTruthy(option->
values[0].
value);
1229 SDL_Libretro_MenuOptionWritten(menu);
1235static void SDL_Libretro_MenuResetOptionsClicked(SDL_LibretroMenu* menu,
void* userdata) {
1238 SDL_Libretro_MenuOptionWritten(menu);
1239 menu->optionsStale =
true;
1240 nk_console_show_message(menu->console,
"Core options reset to defaults");
1246static void SDL_Libretro_MenuFreeOptionStates(SDL_LibretroMenu* menu) {
1247 if (menu->optionStates != NULL) {
1248 for (
unsigned i = 0; i < menu->optionStateCount; i++) {
1249 SDL_free(menu->optionStates[i].displayList);
1251 SDL_free(menu->optionStates);
1252 menu->optionStates = NULL;
1254 menu->optionStateCount = 0;
1265static char* SDL_Libretro_MenuJoinPipeList(
unsigned count,
const char* (*item)(
void* userdata,
unsigned index),
void* userdata) {
1267 for (
unsigned i = 0; i < count; i++) {
1268 length += SDL_strlen(item(userdata, i)) + 1;
1270 char* list = (
char*)SDL_malloc(length + 1);
1276 for (
unsigned i = 0; i < count; i++) {
1278 list[offset++] =
'|';
1280 offset += SDL_strlcpy(list + offset, item(userdata, i), length + 1 - offset);
1299static const char* SDL_Libretro_MenuOptionValueText(
void* userdata,
unsigned index) {
1302 return (label != NULL && label[0] !=
'\0') ? label : option->
values[index].
value;
1309static void SDL_Libretro_MenuBuildOptionWidget(SDL_LibretroMenu* menu, nk_console* parent,
unsigned index) {
1311 SDL_LibretroMenuOptionState* state = &menu->optionStates[index];
1313 state->index = index;
1315 const char* label = (option->
desc != NULL && option->
desc[0] !=
'\0') ? option->
desc : option->key;
1318 if (SDL_Libretro_MenuOptionIsBoolean(option)) {
1319 state->checked = (nk_bool)SDL_Libretro_MenuValueTruthy(option->
value);
1320 widget = SDL_Libretro_MenuAddCheckbox(menu, parent, label, &state->checked, NULL, &SDL_Libretro_MenuOptionCheckboxChanged, state);
1324 state->displayList = SDL_Libretro_MenuJoinPipeList(option->
valuesCount, &SDL_Libretro_MenuOptionValueText, (
void*)option);
1325 if (state->displayList == NULL) {
1328 state->selected = 0;
1329 for (
unsigned v = 0; v < option->
valuesCount; v++) {
1331 state->selected = (int)v;
1335 widget = SDL_Libretro_MenuOnChanged(nk_console_combobox(parent, label, state->displayList,
'|', &state->selected), &SDL_Libretro_MenuOptionChanged, state);
1338 if (option->
info != NULL && option->
info[0] !=
'\0') {
1339 nk_console_set_tooltip(widget, option->
info);
1348static int SDL_Libretro_MenuOptionCategory(SDL_Libretro* lr,
const SDL_LibretroOption* option) {
1353 for (
unsigned c = 0; c < count; c++) {
1355 if (category != NULL && category->
key != NULL && SDL_strcmp(option->
category, category->
key) == 0) {
1366static void SDL_Libretro_MenuBuildOptions(SDL_LibretroMenu* menu) {
1367 if (menu->optionsButton == NULL) {
1373 SDL_Libretro_MenuGuardActiveParent(menu, menu->optionsButton);
1374 nk_console_free_children(menu->optionsButton);
1375 SDL_Libretro_MenuFreeOptionStates(menu);
1379 for (
unsigned i = 0; i < count; i++) {
1387 if (nk_console_active_parent(menu->console) == menu->optionsButton) {
1388 nk_console_set_active_parent(menu->optionsButton->parent);
1390 menu->optionsButton->visible = nk_false;
1393 menu->optionsButton->visible = nk_true;
1395 menu->optionStates = (SDL_LibretroMenuOptionState*)SDL_calloc(count,
sizeof(SDL_LibretroMenuOptionState));
1396 if (menu->optionStates == NULL) {
1397 if (nk_console_active_parent(menu->console) == menu->optionsButton) {
1398 nk_console_set_active_parent(menu->optionsButton->parent);
1400 menu->optionsButton->visible = nk_false;
1403 menu->optionStateCount = count;
1405 SDL_Libretro_MenuAddBackButton(menu->optionsButton,
"Core Options");
1408 for (
unsigned i = 0; i < count; i++) {
1410 if (!SDL_Libretro_MenuOptionShown(option) || SDL_Libretro_MenuOptionCategory(lr, option) >= 0) {
1413 SDL_Libretro_MenuBuildOptionWidget(menu, menu->optionsButton, i);
1418 for (
unsigned c = 0; c < categoryCount; c++) {
1420 bool hasVisible =
false;
1421 for (
unsigned i = 0; i < count; i++) {
1423 if (SDL_Libretro_MenuOptionShown(option) && SDL_Libretro_MenuOptionCategory(lr, option) == (
int)c) {
1432 const char* categoryLabel = (category->
desc != NULL && category->
desc[0] !=
'\0') ? category->
desc : category->key;
1433 nk_console* categoryButton = SDL_Libretro_MenuAddSubmenu(menu->optionsButton, categoryLabel, NK_SYMBOL_TRIANGLE_RIGHT);
1434 if (category->
info != NULL && category->
info[0] !=
'\0') {
1435 nk_console_set_tooltip(categoryButton, category->
info);
1438 for (
unsigned i = 0; i < count; i++) {
1440 if (SDL_Libretro_MenuOptionShown(option) && SDL_Libretro_MenuOptionCategory(lr, option) == (
int)c) {
1441 SDL_Libretro_MenuBuildOptionWidget(menu, categoryButton, i);
1446 SDL_Libretro_MenuAddButton(menu, menu->optionsButton,
"Reset to Defaults", NK_SYMBOL_TRIANGLE_LEFT, &SDL_Libretro_MenuResetOptionsClicked, NULL);
1449#ifndef __EMSCRIPTEN__
1458static void SDL_Libretro_MenuAppendExtensions(
char* filter,
size_t filterSize,
const char* extensions) {
1459 if (extensions == NULL) {
1462 for (
const char* token = extensions; *token !=
'\0';) {
1463 const char* end = token;
1464 while (*end !=
'\0' && *end !=
'|') {
1467 size_t tokenLen = (size_t)(end - token);
1470 if (tokenLen > 0 && tokenLen + 2 <=
sizeof(lower)) {
1473 for (
size_t i = 0; i < tokenLen; i++) {
1474 lower[i + 1] = (char)SDL_tolower((
int)(
unsigned char)token[i]);
1475 upper[i + 1] = (char)SDL_toupper((
int)(
unsigned char)token[i]);
1477 lower[tokenLen + 1] =
'\0';
1478 upper[tokenLen + 1] =
'\0';
1482 for (
const char* existing = filter; *existing !=
'\0' && !found;) {
1483 const char* existingEnd = existing;
1484 while (*existingEnd !=
'\0' && *existingEnd !=
';') {
1487 size_t existingLen = (size_t)(existingEnd - existing);
1488 if (existingLen == tokenLen + 1 && SDL_strncasecmp(existing, lower, existingLen) == 0) {
1491 existing = (*existingEnd ==
';') ? existingEnd + 1 : existingEnd;
1495 size_t length = SDL_strlen(filter);
1497 if (length + (tokenLen + 2) * 2 + 2 < filterSize) {
1499 filter[length++] =
';';
1501 length += SDL_strlcpy(filter + length, lower, filterSize - length);
1502 filter[length++] =
';';
1503 SDL_strlcpy(filter + length, upper, filterSize - length);
1507 token = (*end ==
'|') ? end + 1 : end;
1517static void SDL_Libretro_MenuUpdateLoadGameFilter(SDL_LibretroMenu* menu) {
1518#ifndef __EMSCRIPTEN__
1519 if (menu->loadGameButton == NULL) {
1524 char filter[1024] =
"";
1525 for (
unsigned i = 0; i < lr->coreLibraryCount; i++) {
1526 SDL_Libretro_MenuAppendExtensions(filter,
sizeof(filter), lr->coreLibrary[i].supported_extensions);
1529 SDL_Libretro_MenuAppendExtensions(filter,
sizeof(filter), lr->core.validExtensions);
1531#if defined(SDL_LIBRETRO_ENABLE_PHYSFS) && !defined(SDL_LIBRETRO_DISABLE_PHYSFS)
1532 SDL_Libretro_MenuAppendExtensions(filter,
sizeof(filter),
"zip");
1536 nk_console_file_set_filter(menu->loadGameButton, filter[0] !=
'\0' ? filter : NULL);
1546static void SDL_Libretro_MenuSyncSettingsBuffers(SDL_LibretroMenu* menu) {
1549 SDL_strlcpy(menu->coreDirBuffer, value != NULL ? value :
"", sizeof(menu->coreDirBuffer));
1551 SDL_strlcpy(menu->saveDirBuffer, value != NULL ? value :
"", sizeof(menu->saveDirBuffer));
1553 SDL_strlcpy(menu->systemDirBuffer, value != NULL ? value :
"", sizeof(menu->systemDirBuffer));
1554 SDL_strlcpy(menu->browseDirBuffer, lr->fileBrowserStartDirectory,
sizeof(menu->browseDirBuffer));
1556 SDL_strlcpy(menu->usernameBuffer, value != NULL ? value :
"", sizeof(menu->usernameBuffer));
1566static void SDL_Libretro_MenuSettingChanged(nk_console* widget,
void* user_data) {
1570 SDL_Libretro_MenuUpdateLoadGameFilter(menu);
1573 SDL_strlcpy(menu->lr->fileBrowserStartDirectory, menu->browseDirBuffer,
sizeof(menu->lr->fileBrowserStartDirectory));
1574#ifndef __EMSCRIPTEN__
1575 if (menu->loadGameButton != NULL && menu->browseDirBuffer[0] !=
'\0') {
1576 nk_console_file_set_directory(menu->loadGameButton, menu->browseDirBuffer);
1587static nk_console* SDL_Libretro_MenuAddSettingTextedit(nk_console* parent,
const char* label, SDL_LibretroMenu* menu,
char* buffer,
size_t bufferSize) {
1588 return SDL_Libretro_MenuOnChanged(nk_console_textedit(parent, label, buffer, bufferSize), &SDL_Libretro_MenuSettingChanged, menu);
1596static nk_console* SDL_Libretro_MenuAddSettingDir(nk_console* parent,
const char* label, SDL_LibretroMenu* menu,
char* buffer,
size_t bufferSize) {
1597#ifdef __EMSCRIPTEN__
1599 return SDL_Libretro_MenuAddSettingTextedit(parent, label, menu, buffer, bufferSize);
1603 return SDL_Libretro_MenuOnChanged(nk_console_dir(parent, label, buffer, (
int)bufferSize), &SDL_Libretro_MenuSettingChanged, menu);
1610static void SDL_Libretro_MenuFreePortStates(SDL_LibretroMenu* menu) {
1612 SDL_free(menu->portStates[i].deviceList);
1613 menu->portStates[i].deviceList = NULL;
1620static void SDL_Libretro_MenuPortDeviceChanged(nk_console* widget,
void* user_data) {
1622 SDL_LibretroMenuPortState* state = (SDL_LibretroMenuPortState*)user_data;
1624 if (state->port >= lr->core.controllerInfoCount) {
1627 const struct retro_controller_info* info = &lr->core.controllerInfo[state->port];
1628 if (state->selected < 0 || (
unsigned)state->selected >= info->num_types) {
1638static const char* SDL_Libretro_MenuPortDeviceText(
void* userdata,
unsigned index) {
1639 const struct retro_controller_info* info = (
const struct retro_controller_info*)userdata;
1640 return info->types[index].desc != NULL ? info->types[index].desc :
"Unknown";
1648static void SDL_Libretro_MenuBuildControllers(SDL_LibretroMenu* menu) {
1649 if (menu->controllersButton == NULL) {
1655 SDL_Libretro_MenuGuardActiveParent(menu, menu->controllersButton);
1656 nk_console_free_children(menu->controllersButton);
1657 SDL_Libretro_MenuFreePortStates(menu);
1659 SDL_Libretro_MenuAddBackButton(menu->controllersButton,
"Controllers");
1661 unsigned count = lr->core.controllerInfoCount;
1666 bool anyWidget =
false;
1667 for (
unsigned port = 0; port < count; port++) {
1668 const struct retro_controller_info* info = &lr->core.controllerInfo[port];
1669 if (info->types == NULL || info->num_types <= 1) {
1674 SDL_LibretroMenuPortState* state = &menu->portStates[port];
1675 state->deviceList = SDL_Libretro_MenuJoinPipeList(info->num_types, &SDL_Libretro_MenuPortDeviceText, (
void*)info);
1676 if (state->deviceList == NULL) {
1681 state->selected = 0;
1684 for (
unsigned i = 0; i < info->num_types; i++) {
1685 if (info->types[i].id == currentDevice) {
1686 state->selected = (int)i;
1690 SDL_snprintf(state->label,
sizeof(state->label),
"Port %u", port + 1);
1691 SDL_Libretro_MenuOnChanged(nk_console_combobox(menu->controllersButton, state->label, state->deviceList,
'|', &state->selected), &SDL_Libretro_MenuPortDeviceChanged, state);
1695 menu->controllersButton->visible = (nk_bool)anyWidget;
1705typedef struct SDL_LibretroMenuPalette {
1706 struct nk_color text;
1707 struct nk_color window;
1708 struct nk_color frame;
1709 struct nk_color widget;
1710 struct nk_color widgetHover;
1711 struct nk_color widgetActive;
1712 struct nk_color selectActive;
1713 struct nk_color toggle;
1714 struct nk_color toggleHover;
1715 struct nk_color toggleCursor;
1716 struct nk_color slider;
1717 struct nk_color sliderCursor;
1718 struct nk_color sliderCursorHover;
1719 struct nk_color sliderCursorActive;
1720 struct nk_color editCursor;
1721 struct nk_color chartColor;
1722 struct nk_color chartHighlight;
1723 struct nk_color scrollbar;
1724 struct nk_color scrollbarCursor;
1725 struct nk_color scrollbarCursorHover;
1726 struct nk_color scrollbarCursorActive;
1727 struct nk_color knobCursor;
1728 struct nk_color knobCursorHover;
1729 struct nk_color knobCursorActive;
1730} SDL_LibretroMenuPalette;
1734static const SDL_LibretroMenuPalette SDL_Libretro_menuPaletteMocha = {
1735 .text = {205, 214, 244, 255},
1736 .window = {30, 30, 46, 235},
1737 .frame = {24, 24, 37, 255},
1738 .widget = {49, 50, 68, 255},
1739 .widgetHover = {127, 132, 156, 255},
1740 .widgetActive = {108, 112, 134, 255},
1741 .selectActive = {108, 112, 134, 255},
1742 .toggle = {88, 91, 112, 255},
1743 .toggleHover = {147, 153, 178, 255},
1744 .toggleCursor = {180, 190, 254, 255},
1745 .slider = {69, 71, 90, 255},
1746 .sliderCursor = {166, 227, 161, 255},
1747 .sliderCursorHover = {166, 227, 161, 255},
1748 .sliderCursorActive = {166, 227, 161, 255},
1749 .editCursor = {180, 190, 254, 255},
1750 .chartColor = {180, 190, 254, 255},
1751 .chartHighlight = {245, 194, 231, 255},
1752 .scrollbar = {49, 50, 68, 255},
1753 .scrollbarCursor = {108, 112, 134, 255},
1754 .scrollbarCursorHover = {180, 190, 254, 255},
1755 .scrollbarCursorActive = {245, 194, 231, 255},
1756 .knobCursor = {245, 194, 231, 255},
1757 .knobCursorHover = {245, 194, 231, 255},
1758 .knobCursorActive = {245, 194, 231, 255},
1762static const SDL_LibretroMenuPalette SDL_Libretro_menuPaletteLatte = {
1763 .text = {76, 79, 105, 255},
1764 .window = {239, 241, 245, 235},
1765 .frame = {230, 233, 239, 255},
1766 .widget = {204, 208, 218, 255},
1767 .widgetHover = {124, 127, 147, 55},
1768 .widgetActive = {156, 160, 176, 255},
1769 .selectActive = {156, 160, 176, 255},
1770 .toggle = {172, 176, 190, 255},
1771 .toggleHover = {124, 127, 147, 55},
1772 .toggleCursor = {223, 142, 29, 255},
1773 .slider = {188, 192, 204, 255},
1774 .sliderCursor = {23, 146, 153, 255},
1775 .sliderCursorHover = {23, 146, 153, 255},
1776 .sliderCursorActive = {23, 146, 153, 255},
1777 .editCursor = {136, 57, 239, 255},
1778 .chartColor = {23, 146, 153, 255},
1779 .chartHighlight = {136, 57, 239, 255},
1780 .scrollbar = {204, 208, 218, 255},
1781 .scrollbarCursor = {156, 160, 176, 255},
1782 .scrollbarCursorHover = {136, 57, 239, 255},
1783 .scrollbarCursorActive = {136, 57, 239, 255},
1784 .knobCursor = {234, 118, 203, 255},
1785 .knobCursorHover = {234, 118, 203, 255},
1786 .knobCursorActive = {234, 118, 203, 255},
1790static const SDL_LibretroMenuPalette SDL_Libretro_menuPaletteFrappe = {
1791 .text = {198, 208, 245, 255},
1792 .window = {48, 52, 70, 235},
1793 .frame = {41, 44, 60, 255},
1794 .widget = {65, 69, 89, 255},
1795 .widgetHover = {131, 139, 167, 255},
1796 .widgetActive = {115, 121, 148, 255},
1797 .selectActive = {115, 121, 148, 255},
1798 .toggle = {98, 104, 128, 255},
1799 .toggleHover = {148, 156, 187, 255},
1800 .toggleCursor = {244, 184, 228, 255},
1801 .slider = {81, 87, 109, 255},
1802 .sliderCursor = {166, 209, 137, 255},
1803 .sliderCursorHover = {166, 209, 137, 255},
1804 .sliderCursorActive = {166, 209, 137, 255},
1805 .editCursor = {244, 184, 228, 255},
1806 .chartColor = {186, 187, 241, 255},
1807 .chartHighlight = {244, 184, 228, 255},
1808 .scrollbar = {65, 69, 89, 255},
1809 .scrollbarCursor = {115, 121, 148, 255},
1810 .scrollbarCursorHover = {186, 187, 241, 255},
1811 .scrollbarCursorActive = {186, 187, 241, 255},
1812 .knobCursor = {244, 184, 228, 255},
1813 .knobCursorHover = {244, 184, 228, 255},
1814 .knobCursorActive = {244, 184, 228, 255},
1818static const SDL_LibretroMenuPalette SDL_Libretro_menuPaletteMacchiato = {
1819 .text = {202, 211, 245, 255},
1820 .window = {36, 39, 58, 235},
1821 .frame = {30, 32, 48, 255},
1822 .widget = {54, 58, 79, 255},
1823 .widgetHover = {128, 135, 162, 255},
1824 .widgetActive = {110, 115, 141, 255},
1825 .selectActive = {110, 115, 141, 255},
1826 .toggle = {91, 96, 120, 255},
1827 .toggleHover = {147, 154, 183, 255},
1828 .toggleCursor = {238, 212, 159, 255},
1829 .slider = {73, 77, 100, 255},
1830 .sliderCursor = {166, 218, 149, 255},
1831 .sliderCursorHover = {166, 218, 149, 255},
1832 .sliderCursorActive = {166, 218, 149, 255},
1833 .editCursor = {245, 189, 230, 255},
1834 .chartColor = {183, 189, 248, 255},
1835 .chartHighlight = {238, 212, 159, 255},
1836 .scrollbar = {54, 58, 79, 255},
1837 .scrollbarCursor = {110, 115, 141, 255},
1838 .scrollbarCursorHover = {183, 189, 248, 255},
1839 .scrollbarCursorActive = {183, 189, 248, 255},
1840 .knobCursor = {245, 189, 230, 255},
1841 .knobCursorHover = {245, 189, 230, 255},
1842 .knobCursorActive = {245, 189, 230, 255},
1847static const SDL_LibretroMenuPalette SDL_Libretro_menuPaletteDracula = {
1848 .text = {248, 248, 242, 255},
1849 .window = {40, 42, 54, 235},
1850 .frame = {68, 71, 90, 255},
1851 .widget = {68, 71, 90, 255},
1852 .widgetHover = {98, 114, 164, 255},
1853 .widgetActive = {189, 147, 249, 255},
1854 .selectActive = {98, 114, 164, 255},
1855 .toggle = {68, 71, 90, 255},
1856 .toggleHover = {98, 114, 164, 255},
1857 .toggleCursor = {255, 121, 198, 255},
1858 .slider = {40, 42, 54, 235},
1859 .sliderCursor = {68, 71, 90, 255},
1860 .sliderCursorHover = {98, 114, 164, 255},
1861 .sliderCursorActive = {98, 114, 164, 255},
1862 .editCursor = {248, 248, 242, 255},
1863 .chartColor = {98, 114, 164, 255},
1864 .chartHighlight = {189, 147, 249, 255},
1865 .scrollbar = {40, 42, 54, 235},
1866 .scrollbarCursor = {68, 71, 90, 255},
1867 .scrollbarCursorHover = {98, 114, 164, 255},
1868 .scrollbarCursorActive = {189, 147, 249, 255},
1869 .knobCursor = {68, 71, 90, 255},
1870 .knobCursorHover = {98, 114, 164, 255},
1871 .knobCursorActive = {98, 114, 164, 255},
1880 &SDL_Libretro_menuPaletteMocha,
1881 &SDL_Libretro_menuPaletteLatte,
1882 &SDL_Libretro_menuPaletteFrappe,
1883 &SDL_Libretro_menuPaletteMacchiato,
1884 &SDL_Libretro_menuPaletteDracula,
1896static float SDL_Libretro_MenuAutoScale(
int width) {
1897 if (width >= 3840) {
1900 if (width >= 2560) {
1903 if (width >= 1280) {
1914static float SDL_Libretro_MenuRenderScale(SDL_LibretroMenu* menu) {
1915 float displayScale = SDL_GetWindowDisplayScale(menu->lr->window);
1916 int scale = (displayScale > 0.0f) ? (
int)(displayScale + 0.5f) : 1;
1921 if (menu->uiScaleIndex > 0) {
1922 step = menu->uiScaleIndex;
1927 SDL_GetWindowSize(menu->lr->window, &width, &height);
1928 step = (int)SDL_Libretro_MenuAutoScale(width);
1930 return (
float)(step * scale);
1939static void SDL_Libretro_MenuBakeFont(SDL_LibretroMenu* menu) {
1940 if (menu->atlas != NULL) {
1941 nk_font_atlas_clear(menu->atlas);
1944 struct nk_sdl* sdl = (
struct nk_sdl*)menu->ctx->userdata.ptr;
1945 struct nk_font_config fontConfig = nk_font_config(0);
1946 fontConfig.oversample_h = 1;
1947 fontConfig.oversample_v = 1;
1948 fontConfig.pixel_snap = nk_true;
1949 menu->atlas = nk_sdl_font_stash_begin(menu->ctx);
1952 int atlasWidth = 0, atlasHeight = 0;
1953 const void* fontImage = nk_font_atlas_bake(menu->atlas, &atlasWidth, &atlasHeight, NK_FONT_ATLAS_RGBA32);
1954 if (fontImage != NULL) {
1955 Uint8* texels = (Uint8*)fontImage;
1956 for (
int i = 3; i < atlasWidth * atlasHeight * 4; i += 4) {
1957 texels[i] = texels[i] >= 128 ? 255 : 0;
1960 nk_sdl_device_upload_atlas(menu->ctx, fontImage, atlasWidth, atlasHeight);
1961 nk_font_atlas_end(menu->atlas, nk_handle_ptr(sdl->ogl.font_tex), &sdl->ogl.tex_null);
1962 if (sdl->ogl.font_tex != NULL) {
1963 SDL_SetTextureScaleMode(sdl->ogl.font_tex, SDL_SCALEMODE_NEAREST);
1966 nk_style_set_font(menu->ctx, &font->handle);
1974static void SDL_Libretro_MenuApplyPalette(
struct nk_context* ctx,
const SDL_LibretroMenuPalette* palette) {
1975 struct nk_color table[NK_COLOR_COUNT];
1976 table[NK_COLOR_TEXT] = palette->text;
1977 table[NK_COLOR_WINDOW] = palette->window;
1978 table[NK_COLOR_HEADER] = palette->frame;
1979 table[NK_COLOR_BORDER] = palette->frame;
1980 table[NK_COLOR_BUTTON] = palette->widget;
1981 table[NK_COLOR_BUTTON_HOVER] = palette->widgetHover;
1982 table[NK_COLOR_BUTTON_ACTIVE] = palette->widgetActive;
1983 table[NK_COLOR_TOGGLE] = palette->toggle;
1984 table[NK_COLOR_TOGGLE_HOVER] = palette->toggleHover;
1985 table[NK_COLOR_TOGGLE_CURSOR] = palette->toggleCursor;
1986 table[NK_COLOR_SELECT] = palette->widget;
1987 table[NK_COLOR_SELECT_ACTIVE] = palette->selectActive;
1988 table[NK_COLOR_SLIDER] = palette->slider;
1989 table[NK_COLOR_SLIDER_CURSOR] = palette->sliderCursor;
1990 table[NK_COLOR_SLIDER_CURSOR_HOVER] = palette->sliderCursorHover;
1991 table[NK_COLOR_SLIDER_CURSOR_ACTIVE] = palette->sliderCursorActive;
1992 table[NK_COLOR_PROPERTY] = palette->widget;
1993 table[NK_COLOR_EDIT] = palette->widget;
1994 table[NK_COLOR_EDIT_CURSOR] = palette->editCursor;
1995 table[NK_COLOR_COMBO] = palette->widget;
1996 table[NK_COLOR_CHART] = palette->widget;
1997 table[NK_COLOR_CHART_COLOR] = palette->chartColor;
1998 table[NK_COLOR_CHART_COLOR_HIGHLIGHT] = palette->chartHighlight;
1999 table[NK_COLOR_SCROLLBAR] = palette->scrollbar;
2000 table[NK_COLOR_SCROLLBAR_CURSOR] = palette->scrollbarCursor;
2001 table[NK_COLOR_SCROLLBAR_CURSOR_HOVER] = palette->scrollbarCursorHover;
2002 table[NK_COLOR_SCROLLBAR_CURSOR_ACTIVE] = palette->scrollbarCursorActive;
2003 table[NK_COLOR_TAB_HEADER] = palette->widget;
2004 table[NK_COLOR_KNOB] = palette->slider;
2005 table[NK_COLOR_KNOB_CURSOR] = palette->knobCursor;
2006 table[NK_COLOR_KNOB_CURSOR_HOVER] = palette->knobCursorHover;
2007 table[NK_COLOR_KNOB_CURSOR_ACTIVE] = palette->knobCursorActive;
2008 nk_style_from_table(ctx, table);
2014static void SDL_Libretro_MenuUIScaleChanged(nk_console* widget,
void* user_data) {
2017 menu->settingsDirty =
true;
2040 nk_style_default(menu->ctx);
2041 const SDL_LibretroMenuPalette* palette = SDL_Libretro_MenuGetPalette(style);
2042 if (palette != NULL) {
2043 SDL_Libretro_MenuApplyPalette(menu->ctx, palette);
2046 menu->style = style;
2047 menu->styleIndex = (int)style;
2048 menu->settingsDirty =
true;
2068static void SDL_Libretro_MenuStyleChanged(nk_console* widget,
void* user_data) {
2081static void SDL_Libretro_MenuSyncSettings(SDL_LibretroMenu* menu) {
2086 menu->fullscreenChecked = (nk_bool)((SDL_GetWindowFlags(lr->window) & SDL_WINDOW_FULLSCREEN) != 0);
2088 SDL_GetRenderVSync(lr->renderer, &vsync);
2089 menu->vsyncChecked = (nk_bool)(vsync != 0);
2094 menu->rewindBufferMB = rewindLimitMB > 1024 ? 1024 : (int)rewindLimitMB;
2097 menu->languageIndex = lr->language >= 0 ? lr->language + 1 : 0;
2104static void SDL_Libretro_MenuDiskClicked(nk_console* widget,
void* user_data) {
2111 bool changed =
false;
2118 nk_console_show_message(menu->console,
"Disk changed");
2121 nk_console_show_message(menu->console,
"Disk change failed");
2129static void SDL_Libretro_MenuFreeDiskLabels(SDL_LibretroMenu* menu) {
2130 if (menu->diskLabels == NULL) {
2133 for (
unsigned i = 0; i < menu->builtDiskCount; i++) {
2134 SDL_free(menu->diskLabels[i]);
2136 SDL_free(menu->diskLabels);
2137 menu->diskLabels = NULL;
2145static void SDL_Libretro_MenuBuildDisks(SDL_LibretroMenu* menu) {
2146 if (menu->disksButton == NULL) {
2152 SDL_Libretro_MenuGuardActiveParent(menu, menu->disksButton);
2153 nk_console_free_children(menu->disksButton);
2154 SDL_Libretro_MenuFreeDiskLabels(menu);
2157 menu->builtDiskCount = count;
2160 if (nk_console_active_parent(menu->console) == menu->disksButton) {
2161 nk_console_set_active_parent(menu->console);
2163 menu->disksButton->visible = nk_false;
2167 menu->diskLabels = (
char**)SDL_calloc(count,
sizeof(
char*));
2168 if (menu->diskLabels == NULL) {
2169 menu->disksButton->visible = nk_false;
2173 menu->disksButton->visible = nk_true;
2174 SDL_Libretro_MenuAddBackButton(menu->disksButton,
"Disks");
2176 for (
unsigned i = 0; i < count; i++) {
2179 SDL_snprintf(label,
sizeof(label),
"Disk %u", i + 1);
2181 menu->diskLabels[i] = SDL_strdup(label);
2182 nk_console* radio = nk_console_radio(menu->disksButton, menu->diskLabels[i] != NULL ? menu->diskLabels[i] :
"Disk", &menu->diskSelected);
2184 nk_console_add_event_handler(radio, NK_CONSOLE_EVENT_CLICKED, &SDL_Libretro_MenuDiskClicked, menu, NULL);
2193static void SDL_Libretro_MenuBuildSettings(SDL_LibretroMenu* menu) {
2194 nk_console* settings = SDL_Libretro_MenuAddSubmenu(menu->console,
"Settings", NK_SYMBOL_HAMBURGER);
2197 nk_console* general = SDL_Libretro_MenuAddSubmenu(settings,
"General", NK_SYMBOL_TRIANGLE_RIGHT);
2198 SDL_Libretro_MenuSyncSettingsBuffers(menu);
2199 nk_console_set_tooltip(
2200 SDL_Libretro_MenuAddSettingTextedit(general,
"Username", menu, menu->usernameBuffer,
sizeof(menu->usernameBuffer)),
2201 "Reported to cores that ask for a username");
2204 nk_console_set_tooltip(
2205 SDL_Libretro_MenuAddCheckbox(menu, general,
"Rewind", &menu->rewindChecked, NULL, &SDL_Libretro_MenuRewindChanged, NULL),
2206 "Capture state snapshots so gameplay can rewind");
2207 nk_console_set_tooltip(
2208 SDL_Libretro_MenuOnChanged(nk_console_property_int(general,
"Rewind Buffer (MB)", 0, &menu->rewindBufferMB, 1024, 8, 1.0f), &SDL_Libretro_MenuRewindBufferChanged, menu),
2209 "Memory the rewind buffer may use; 0 for no limit");
2210 nk_console_set_tooltip(
2211 SDL_Libretro_MenuOnChanged(nk_console_combobox(general,
"Language", SDL_LIBRETRO_MENU_LANGUAGE_NAMES,
'|', &menu->languageIndex), &SDL_Libretro_MenuLanguageChanged, menu),
2212 "Reported to cores; applies when the next core loads. Auto follows the system locale");
2215 nk_console* audioVideo = SDL_Libretro_MenuAddSubmenu(settings,
"Audio & Video", NK_SYMBOL_TRIANGLE_RIGHT);
2217 SDL_Libretro_MenuOnChanged(nk_console_knob_int(audioVideo,
"Volume", 0, &menu->volumePercent, 100, 5, 1.0f), &SDL_Libretro_MenuVolumeChanged, menu);
2218 nk_console_set_tooltip(
2219 SDL_Libretro_MenuAddCheckbox(menu, audioVideo,
"Mute", &menu->muteChecked, NULL, &SDL_Libretro_MenuMuteChanged, NULL),
2220 "Silence the audio; unmuting restores the volume");
2221 SDL_Libretro_MenuAddCheckbox(menu, audioVideo,
"Fullscreen", &menu->fullscreenChecked, NULL, &SDL_Libretro_MenuFullscreenChanged, NULL);
2222 SDL_Libretro_MenuAddCheckbox(menu, audioVideo,
"VSync", &menu->vsyncChecked, NULL, &SDL_Libretro_MenuVSyncChanged, NULL);
2223 nk_console_set_tooltip(
2224 SDL_Libretro_MenuOnChanged(nk_console_combobox(audioVideo,
"Filter",
"Nearest|Linear",
'|', &menu->filterIndex), &SDL_Libretro_MenuFilterChanged, menu),
2225 "Nearest keeps pixels sharp; Linear smooths the scaling");
2226 nk_console_set_tooltip(
2227 SDL_Libretro_MenuOnChanged(nk_console_combobox(audioVideo,
"Fit Mode",
"Aspect|Integer|Stretch",
'|', &menu->fitModeIndex), &SDL_Libretro_MenuFitModeChanged, menu),
2228 "Aspect keeps the ratio; Integer locks whole-number scales; Stretch fills the window");
2229 SDL_Libretro_MenuOnChanged(nk_console_combobox(audioVideo,
"Theme", SDL_LIBRETRO_MENU_STYLE_NAMES,
'|', &menu->styleIndex), &SDL_Libretro_MenuStyleChanged, menu);
2230 nk_console_set_tooltip(
2231 SDL_Libretro_MenuOnChanged(nk_console_combobox(audioVideo,
"UI Scale",
"Auto|1x|2x|3x|4x",
'|', &menu->uiScaleIndex), &SDL_Libretro_MenuUIScaleChanged, menu),
2232 "Menu size; Auto picks one from the resolution");
2235 menu->optionsButton = nk_console_button(settings,
"Core Options");
2236 nk_console_button_set_symbol(menu->optionsButton, NK_SYMBOL_TRIANGLE_RIGHT);
2237 menu->optionsButton->visible = nk_false;
2240 menu->controllersButton = nk_console_button(settings,
"Controllers");
2241 nk_console_button_set_symbol(menu->controllersButton, NK_SYMBOL_TRIANGLE_RIGHT);
2242 menu->controllersButton->visible = nk_false;
2245 nk_console* keyboard = SDL_Libretro_MenuAddSubmenu(settings,
"Keyboard", NK_SYMBOL_TRIANGLE_RIGHT);
2246 nk_console_set_tooltip(keyboard,
"Keyboard keys for Player 1's controller, and the mouse's port");
2247 SDL_Libretro_MenuSyncKeyBinds(menu);
2250 menu->mousePortList[0] =
'\0';
2253 SDL_snprintf(item,
sizeof(item), port == 0 ?
"Port %u" :
"|Port %u", port + 1);
2254 SDL_strlcat(menu->mousePortList, item,
sizeof(menu->mousePortList));
2256 nk_console_set_tooltip(
2257 SDL_Libretro_MenuOnChanged(nk_console_combobox(keyboard,
"Mouse Port", menu->mousePortList,
'|', &menu->mousePortIndex), &SDL_Libretro_MenuMousePortChanged, menu),
2258 "The controller port the mouse, pointer and lightgun drive");
2260 for (
int i = 0; i < SDL_LIBRETRO_MAX_JOYPAD_BUTTONS; i++) {
2261 menu->keyBinds[i].menu = menu;
2262 menu->keyBinds[i].button = i;
2263 SDL_Libretro_MenuOnChanged(
2264 nk_console_input_key(keyboard, SDL_Libretro_JoypadButtonNames[i], &menu->keyBinds[i].rune),
2265 &SDL_Libretro_MenuKeyBindChanged,
2266 &menu->keyBinds[i]);
2270 menu->gamepadButton = nk_console_button(settings,
"Gamepad");
2271 nk_console_button_set_symbol(menu->gamepadButton, NK_SYMBOL_TRIANGLE_RIGHT);
2272 nk_console_set_tooltip(menu->gamepadButton,
"Per-player gamepad buttons for the retro controllers");
2273 SDL_Libretro_MenuBuildGamepads(menu);
2276 nk_console* directories = SDL_Libretro_MenuAddSubmenu(settings,
"Directories", NK_SYMBOL_TRIANGLE_RIGHT);
2278 SDL_Libretro_MenuAddSettingDir(directories,
"Cores", menu, menu->coreDirBuffer,
sizeof(menu->coreDirBuffer));
2279 SDL_Libretro_MenuAddSettingDir(directories,
"Saves", menu, menu->saveDirBuffer,
sizeof(menu->saveDirBuffer));
2280 SDL_Libretro_MenuAddSettingDir(directories,
"System", menu, menu->systemDirBuffer,
sizeof(menu->systemDirBuffer));
2281 SDL_Libretro_MenuAddSettingDir(directories,
"Content", menu, menu->browseDirBuffer,
sizeof(menu->browseDirBuffer));
2288static void SDL_Libretro_MenuBuildWidgets(SDL_LibretroMenu* menu) {
2293 nk_console_add_event_handler(menu->console, NK_CONSOLE_EVENT_BACK, &SDL_Libretro_MenuBackClicked, menu, NULL);
2296 menu->osdProgressWidget = nk_console_progress(menu->console,
"Progress", &menu->osdProgressValue, 100);
2297 menu->osdProgressWidget->visible = nk_false;
2300 menu->resumeButton = SDL_Libretro_MenuAddButton(menu, menu->console,
"Resume", NK_SYMBOL_TRIANGLE_RIGHT, &SDL_Libretro_MenuResumeClicked, NULL);
2303#ifdef __EMSCRIPTEN__
2306 menu->loadGameButton = SDL_Libretro_MenuAddButton(menu, menu->console,
"Load Game", NK_SYMBOL_NONE, &SDL_Libretro_MenuWebLoadGameClicked, NULL);
2308 menu->loadGameButton = SDL_Libretro_MenuOnChanged(
2309 nk_console_file_action(menu->console,
"Load Game", menu->loadGamePath,
sizeof(menu->loadGamePath)),
2310 &SDL_Libretro_MenuGameFileChanged,
2312 if (lr->fileBrowserStartDirectory[0] !=
'\0') {
2313 nk_console_file_set_directory(menu->loadGameButton, lr->fileBrowserStartDirectory);
2315 SDL_Libretro_MenuUpdateLoadGameFilter(menu);
2319 menu->stateRow = nk_console_row_begin(menu->console);
2321 SDL_Libretro_MenuAddButton(menu, menu->stateRow,
"Save State", NK_SYMBOL_RECT_SOLID, &SDL_Libretro_MenuSaveStateClicked, NULL);
2322 SDL_Libretro_MenuAddButton(menu, menu->stateRow,
"Load State", NK_SYMBOL_RECT_OUTLINE, &SDL_Libretro_MenuLoadStateClicked, NULL);
2324 nk_console_row_end(menu->stateRow);
2330 menu->resetButton = SDL_Libretro_MenuAddButton(menu, menu->console,
"Reset", NK_SYMBOL_CIRCLE_SOLID, &SDL_Libretro_MenuResetClicked, NULL);
2333 menu->disksButton = SDL_Libretro_MenuAddSubmenu(menu->console,
"Disks", NK_SYMBOL_CIRCLE_OUTLINE);
2334 nk_console_set_tooltip(menu->disksButton,
"Swap disks for multi-disk games");
2335 menu->disksButton->visible = nk_false;
2337 SDL_Libretro_MenuBuildSettings(menu);
2340 menu->aboutButton = SDL_Libretro_MenuAddButton(menu, menu->console,
"About", NK_SYMBOL_TRIANGLE_RIGHT, &SDL_Libretro_MenuAboutOpened, NULL);
2343 menu->quitButton = SDL_Libretro_MenuAddButton(menu, menu->console,
"Quit", NK_SYMBOL_X, &SDL_Libretro_MenuQuitClicked, NULL);
2371 SDL_InvalidParamError(
"lr");
2374 if (lr->renderer == NULL || lr->window == NULL) {
2375 SDL_SetError(
"SDL_Libretro_CreateMenu() requires SDL_Libretro_SetRenderer() to be called first");
2385 menu->ctx = nk_sdl_init(lr->window, lr->renderer, nk_sdl_allocator());
2386 if (menu->ctx == NULL) {
2388 SDL_SetError(
"Failed to initialize Nuklear");
2394 SDL_Libretro_MenuBakeFont(menu);
2396 menu->console = nk_console_init(menu->ctx);
2397 if (menu->console == NULL) {
2398 nk_sdl_shutdown(menu->ctx);
2400 SDL_SetError(
"Failed to initialize nuklear_console");
2404 nk_gamepad_init(&menu->gamepads, menu->ctx, NULL);
2405 nk_console_set_gamepads(menu->console, &menu->gamepads);
2407 SDL_Libretro_MenuLoadState(menu);
2408 menu->renderScale = SDL_Libretro_MenuRenderScale(menu);
2409 SDL_Libretro_MenuSyncSettings(menu);
2410 SDL_Libretro_MenuBuildWidgets(menu);
2412 nk_input_begin(menu->ctx);
2438 if (menu->settingsDirty) {
2439 SDL_Libretro_MenuSaveState(menu);
2441 if (menu->ctx != NULL) {
2442 nk_input_end(menu->ctx);
2444 if (menu->console != NULL) {
2445 nk_console_free(menu->console);
2447 SDL_Libretro_MenuFreeOptionStates(menu);
2448 SDL_Libretro_MenuFreePortStates(menu);
2449 SDL_Libretro_MenuFreeDiskLabels(menu);
2450 nk_gamepad_free(&menu->gamepads);
2451 if (menu->ctx != NULL) {
2452 nk_sdl_shutdown(menu->ctx);
2471 return menu != NULL && menu->open;
2486 return menu != NULL ? menu->lr : NULL;
2501 menu->userData = userData;
2514 return menu != NULL ? menu->userData : NULL;
2533 if (menu == NULL || menu->open == open) {
2544 const bool* keys = SDL_GetKeyboardState(&numKeys);
2545 for (
int i = 0; i < SDL_SCANCODE_COUNT; i++) {
2546 menu->keysHeldAtOpen[i] = keys != NULL && i < numKeys && keys[i];
2549 for (
int i = 0; i < NK_GAMEPAD_MAX; i++) {
2550 menu->padButtonsHeldAtOpen[i] |= menu->gamepads.gamepads[i].buttons;
2553 if (!open && menu->lr != NULL && menu->lr->window != NULL) {
2554 SDL_StopTextInput(menu->lr->window);
2599 if (menu == NULL || menu->ctx == NULL || event == NULL) {
2604 nk_gamepad_sdl3_handle_event(&menu->gamepads, (SDL_Event*)event);
2609 if ((event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_KEY_UP) &&
2611 if (event->type == SDL_EVENT_KEY_UP) {
2616 if (event->type == SDL_EVENT_GAMEPAD_BUTTON_UP && event->gbutton.button == SDL_GAMEPAD_BUTTON_GUIDE) {
2627 if (event->type >= SDL_EVENT_USER) {
2633 if ((event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_KEY_UP) &&
2634 event->key.scancode > SDL_SCANCODE_UNKNOWN && event->key.scancode < SDL_SCANCODE_COUNT) {
2635 bool* held = &menu->keysHeldAtOpen[
event->key.scancode];
2636 if (event->type == SDL_EVENT_KEY_UP && *held) {
2640 if (event->type == SDL_EVENT_KEY_DOWN && !event->key.repeat) {
2646 SDL_SetRenderScale(menu->lr->renderer, menu->renderScale, menu->renderScale);
2647 SDL_Event converted = *event;
2648 SDL_ConvertEventToRenderCoordinates(menu->lr->renderer, &converted);
2649 nk_sdl_handle_event(menu->ctx, &converted);
2650 SDL_SetRenderScale(menu->lr->renderer, 1.0f, 1.0f);
2654 switch (event->type) {
2655 case SDL_EVENT_QUIT:
2656 case SDL_EVENT_DROP_FILE:
2657 case SDL_EVENT_GAMEPAD_ADDED:
2658 case SDL_EVENT_GAMEPAD_REMOVED:
2659 case SDL_EVENT_JOYSTICK_ADDED:
2660 case SDL_EVENT_JOYSTICK_REMOVED:
2665 if (event->type >= SDL_EVENT_WINDOW_FIRST && event->type <= SDL_EVENT_WINDOW_LAST) {
2682static void SDL_Libretro_MenuRebuildCoreMenus(SDL_LibretroMenu* menu,
bool justOpened) {
2685 menu->optionsStale =
true;
2689 bool coreChanged = menu->builtCoreGeneration != lr->coreGeneration;
2690 bool countChanged = menu->builtOptionCount != lr->core.optionCount;
2691 if (!justOpened && !coreChanged && !countChanged && !menu->optionsStale) {
2698 SDL_Libretro_MenuBuildOptions(menu);
2699 SDL_Libretro_MenuBuildControllers(menu);
2700 SDL_Libretro_MenuBuildDisks(menu);
2703 SDL_Libretro_MenuBuildGamepads(menu);
2704 SDL_Libretro_MenuUpdateLoadGameFilter(menu);
2705 menu->optionsStale =
false;
2706 menu->builtOptionCount = lr->core.optionCount;
2707 menu->builtCoreGeneration = lr->coreGeneration;
2726 if (menu == NULL || menu->console == NULL || path == NULL) {
2727 return SDL_InvalidParamError(
"menu");
2733 SDL_Libretro_MenuRebuildCoreMenus(menu,
true);
2734 SDL_Libretro_MenuSyncSettings(menu);
2735 SDL_Libretro_MenuSyncSettingsBuffers(menu);
2736 SDL_Libretro_MenuSyncKeyBinds(menu);
2737 SDL_Libretro_MenuSyncPadBinds(menu);
2738 menu->wasOpen =
true;
2740 return nk_console_navigate_to_path(menu->console, path) == nk_true;
2759 if (menu == NULL || menu->ctx == NULL) {
2764 nk_input_end(menu->ctx);
2765 nk_gamepad_update(&menu->gamepads);
2769 for (
int i = 0; i < NK_GAMEPAD_MAX; i++) {
2770 struct nk_gamepad* pad = &menu->gamepads.gamepads[i];
2771 unsigned int held = menu->padButtonsHeldAtOpen[i];
2772 menu->padButtonsHeldAtOpen[i] = held & pad->buttons;
2773 pad->buttons &= ~held;
2774 pad->buttons_prev &= ~held;
2782 bool justOpened = menu->open && !menu->wasOpen;
2783 menu->wasOpen = menu->open;
2788 SDL_Libretro_MenuRebuildCoreMenus(menu, justOpened);
2792 menu->resumeButton->visible = (nk_bool)gameReady;
2793 menu->stateRow->visible = (nk_bool)gameReady;
2794 menu->screenshotButton->visible = (nk_bool)gameReady;
2795 menu->resetButton->visible = (nk_bool)gameReady;
2799 if (osdProgress >= 0) {
2801 SDL_strlcpy(menu->osdProgressLabel, osdMessage != NULL ? osdMessage :
"",
sizeof(menu->osdProgressLabel));
2802 menu->osdProgressValue = (nk_size)(osdProgress > 100 ? 100 : osdProgress);
2803 nk_console_progress_update(menu->osdProgressWidget, menu->osdProgressLabel, &menu->osdProgressValue, 100);
2804 menu->osdProgressWidget->visible = nk_true;
2807 menu->osdProgressWidget->visible = nk_false;
2812 SDL_Libretro_MenuSyncSettings(menu);
2813 SDL_Libretro_MenuSyncSettingsBuffers(menu);
2814 SDL_Libretro_MenuSyncKeyBinds(menu);
2815 SDL_Libretro_MenuSyncPadBinds(menu);
2819 if (menu->settingsDirty) {
2820 SDL_Libretro_MenuSaveState(menu);
2823 menu->renderScale = SDL_Libretro_MenuRenderScale(menu);
2827 SDL_GetRenderOutputSize(lr->renderer, &width, &height);
2828 float logicalW = (float)width / menu->renderScale;
2829 float logicalH = (float)height / menu->renderScale;
2831 menu->uiBuilt =
true;
2832 nk_console_render_window(menu->console,
"SDL_libretro", nk_rect(0.0f, 0.0f, logicalW, logicalH), NK_WINDOW_SCROLL_AUTO_HIDE |
2834 ((nk_console_active_parent(menu->console) == menu->console) ? NK_WINDOW_TITLE : 0));
2849 if (menu == NULL || menu->ctx == NULL) {
2854 if (menu->uiBuilt) {
2855 SDL_SetRenderScale(menu->lr->renderer, menu->renderScale, menu->renderScale);
2856 nk_sdl_render(menu->ctx, NK_ANTI_ALIASING_ON);
2857 SDL_SetRenderScale(menu->lr->renderer, 1.0f, 1.0f);
2858 nk_console_sdl_update_text_input(menu->console, menu->lr->window);
2859 menu->uiBuilt =
false;
2861 nk_input_begin(menu->ctx);
2868static void SDL_Libretro_MenuKeepQuitLast(SDL_LibretroMenu* menu) {
2869 nk_console** children = menu->console->children;
2870 size_t count = cvector_size(children);
2871 if (menu->quitButton == NULL || count < 2 || children[count - 2] != menu->quitButton) {
2874 children[count - 2] = children[count - 1];
2875 children[count - 1] = menu->quitButton;
2895 if (menu == NULL || menu->console == NULL || label == NULL) {
2896 return SDL_InvalidParamError(
"menu");
2898 nk_console* button = SDL_Libretro_MenuAddButton(menu, menu->console, label, NK_SYMBOL_NONE, callback, userdata);
2899 SDL_Libretro_MenuKeepQuitLast(menu);
2900 return button != NULL;
2922 if (menu == NULL || menu->console == NULL || label == NULL) {
2923 return SDL_InvalidParamError(
"menu");
2925 nk_console* checkbox = SDL_Libretro_MenuAddCheckbox(menu, menu->console, label, NULL, value, callback, userdata);
2926 SDL_Libretro_MenuKeepQuitLast(menu);
2927 return checkbox != NULL;
#define SDL_LIBRETRO_MAX_USERS
The number of controller ports (gamepads) the frontend tracks.
bool SDL_Libretro_IsMenuOpen(const SDL_LibretroMenu *menu)
Checks whether the menu is currently shown.
bool SDL_Libretro_IsCoreReady(const SDL_Libretro *lr)
Indicates whether a core is loaded.
bool SDL_Libretro_SetSystemDirectory(SDL_Libretro *lr, const char *path)
Sets where cores look for system files (BIOS, firmware, etc).
struct SDL_LibretroMenu SDL_LibretroMenu
An in-app menu built on nuklear_console.
size_t SDL_Libretro_GetSavePath(const SDL_Libretro *lr, const char *extension, char *dst, size_t dstSize)
Build a path in the save directory for the currently loaded content.
bool SDL_Libretro_SetCoreDirectory(SDL_Libretro *lr, const char *path)
Sets the associated libretro core directory, where the default set of cores will be loaded from.
void SDL_Libretro_SetRewindMemoryLimit(SDL_Libretro *lr, size_t maxBytes)
Set the maximum number of bytes of encoded delta history to retain.
bool SDL_Libretro_UpdateOptionVisibility(SDL_Libretro *lr)
Ask the core to refresh which of its options should be displayed.
void(* SDL_LibretroMenuCallback)(SDL_LibretroMenu *menu, void *userdata)
A callback for menu entries added by the application.
const char * SDL_Libretro_GetUsername(SDL_Libretro *lr)
Get the username reported to cores.
#define SDL_EVENT_LIBRETRO_MENU_OPENED
The menu became visible; the game pauses.
SDL_ScaleMode SDL_Libretro_GetScaleMode(const SDL_Libretro *lr)
Gets the texture scale mode used when the libretro frame is scaled.
SDL_LibretroFitMode SDL_Libretro_GetFitMode(const SDL_Libretro *lr)
Gets how the frame is fit into the destination rectangle when rendered.
bool SDL_Libretro_SaveScreenshot(const SDL_Libretro *lr, const char *path)
Saves a PNG screenshot of the current frame.
int SDL_Libretro_GetRotation(const SDL_Libretro *lr)
Gets the screen rotation requested by the core.
void SDL_Libretro_UpdateMenu(SDL_LibretroMenu *menu)
Processes input and lays out the menu for this frame.
#define SDL_LIBRETRO_MINOR_VERSION
The minor version of SDL_Libretro.
void SDL_Libretro_UnloadCore(SDL_Libretro *lr)
Unloads the actively loaded core.
int SDL_Libretro_GetMessageProgress(SDL_Libretro *lr)
Gets the progress of the message returned by SDL_Libretro_GetMessage().
bool SDL_Libretro_EjectDisk(SDL_Libretro *lr)
Ejects the disk tray, allowing the disk image to be changed.
SDL_LibretroFitMode
When rendering the libretro context, determine how to display within the destination.
bool SDL_Libretro_SetPortDevice(SDL_Libretro *lr, unsigned port, unsigned device)
Assign a device type to a controller port.
bool SDL_Libretro_Reset(SDL_Libretro *lr)
Resets the loaded game, like pressing the console's reset button.
size_t SDL_Libretro_GetFileName(char *dst, size_t dstSize, const char *path, bool withExtension)
Copy the file name portion of a path into a caller-provided buffer.
#define SDL_EVENT_LIBRETRO_MENU_CLOSED
The menu was dismissed; the game resumes.
bool SDL_Libretro_AreOptionsDirty(SDL_Libretro *lr)
Retrieves whether the options have been changed since the last time they were checked.
bool SDL_Libretro_PhysFS_LoadGame(SDL_Libretro *lr, const char *gamePath)
Archive-aware replacement for SDL_Libretro_LoadGame().
unsigned SDL_Libretro_GetPortDevice(const SDL_Libretro *lr, unsigned port)
Get the device type assigned to a controller port.
void * SDL_Libretro_GetMenuUserData(const SDL_LibretroMenu *menu)
Gets the data attached with SDL_Libretro_SetMenuUserData().
const SDL_LibretroCategory * SDL_Libretro_GetCategoryByIndex(const SDL_Libretro *lr, unsigned index)
Retrieve an option category by index.
SDL_LibretroMenuStyle
Color themes for the menu.
bool SDL_Libretro_SetRewindEnabled(SDL_Libretro *lr, bool enabled, unsigned bufferFrames, unsigned captureInterval)
Enable or disable the rewind system.
const char * SDL_Libretro_GetCoreVersion(const SDL_Libretro *lr)
Retrieves the version of the libretro core that's actively loaded.
void SDL_Libretro_RenderMenu(SDL_LibretroMenu *menu)
Draws the menu laid out by SDL_Libretro_UpdateMenu() to the renderer.
void SDL_Libretro_DestroyMenu(SDL_LibretroMenu *menu)
Destroys a menu created with SDL_Libretro_CreateMenu().
const char * SDL_Libretro_GetValidExtensions(const SDL_Libretro *lr)
Gets the default set of valid extensions associated with the core, seperated by a "|".
void SDL_Libretro_SetMenuOpen(SDL_LibretroMenu *menu, bool open)
Opens or closes the menu.
unsigned SDL_Libretro_GetCategoryCount(const SDL_Libretro *lr)
Get the number of option categories registered by the core.
bool SDL_Libretro_AddMenuCheckbox(SDL_LibretroMenu *menu, const char *label, bool *value, SDL_LibretroMenuCallback callback, void *userdata)
Adds a checkbox to the main menu, just above Quit.
unsigned SDL_Libretro_GetDiskIndex(const SDL_Libretro *lr)
Returns the index of the currently inserted disk image.
bool SDL_Libretro_SetDiskIndex(SDL_Libretro *lr, unsigned index)
Sets the disk image index to insert into the emulated drive.
const char * SDL_Libretro_GetMessage(SDL_Libretro *lr)
Gets the most relevant libretro message to display from the queue.
double SDL_Libretro_GetFPS(const SDL_Libretro *lr)
Gets the frame rate the core expects to run at.
bool SDL_Libretro_LoadState(SDL_Libretro *lr, const char *file)
Loads the libretro state from the given file.
bool SDL_Libretro_SetFitMode(SDL_Libretro *lr, SDL_LibretroFitMode mode)
Sets how the frame is fit into the destination rectangle when rendered.
const SDL_LibretroOption * SDL_Libretro_GetOptionByIndex(const SDL_Libretro *lr, unsigned index)
Retrieve a core option by its registration index.
void SDL_Libretro_SetVolume(SDL_Libretro *lr, float volume)
Set the audio volume when playing sounds.
struct SDL_Libretro SDL_Libretro
A libretro frontend context: one loaded core, its game, and the settings that carry across cores.
bool SDL_Libretro_GetDiskLabel(const SDL_Libretro *lr, unsigned index, char *label, size_t len)
Retrieves the human-readable label of a disk image.
bool SDL_Libretro_LoadGame(SDL_Libretro *lr, const char *gamePath)
Loads a game at the given path.
float SDL_Libretro_GetAspectRatio(const SDL_Libretro *lr)
Gets the aspect ratio for the libretro context.
SDL_Libretro * SDL_Libretro_GetMenuLibretro(const SDL_LibretroMenu *menu)
Gets the libretro context the menu was created for.
void SDL_Libretro_SetGamepadMapping(SDL_Libretro *lr, unsigned port, int retroButton, SDL_GamepadButton button)
Maps an SDL gamepad button to a RETRO_DEVICE_ID_JOYPAD_* button on the given controller port.
float SDL_Libretro_GetVolume(const SDL_Libretro *lr)
Get the audio volume.
bool SDL_Libretro_OpenMenuPath(SDL_LibretroMenu *menu, const char *path)
Opens the menu and navigates to a page by its slash-separated label path, e.g.
void SDL_Libretro_SetMousePort(SDL_Libretro *lr, unsigned port)
Selects the one controller port that the mouse drives.
bool SDL_Libretro_SetMenuStyle(SDL_LibretroMenu *menu, SDL_LibretroMenuStyle style)
Applies a color theme to the menu.
SDL_LibretroMenu * SDL_Libretro_CreateMenu(SDL_Libretro *lr)
Creates the in-app menu for a libretro context.
bool SDL_Libretro_GetRewindEnabled(const SDL_Libretro *lr)
Check whether the rewind system is enabled (independent of current direction).
#define SDL_LIBRETRO_MAJOR_VERSION
The major version of SDL_Libretro.
const char * SDL_Libretro_GetSystemDirectory(SDL_Libretro *lr)
Get the system directory.
bool SDL_Libretro_SetOptionValue(SDL_Libretro *lr, const char *key, const char *value)
Set a core option's value; fails if the value isn't one the core declared.
void SDL_Libretro_ResetAllOptions(SDL_Libretro *lr)
Reset every core option to its default value.
bool SDL_Libretro_SetSaveDirectory(SDL_Libretro *lr, const char *path)
Sets where content will be saved.
unsigned SDL_Libretro_GetDiskCount(const SDL_Libretro *lr)
Returns the number of disk images available to the loaded core.
bool SDL_Libretro_SaveState(SDL_Libretro *lr, const char *file)
Saves the current libretro state to a file.
size_t SDL_Libretro_GetRewindMemoryLimit(const SDL_Libretro *lr)
Get the current rewind memory budget in bytes (0 if unbounded).
void SDL_Libretro_GetSize(const SDL_Libretro *lr, int *w, int *h)
Gets the width and height of the core's current video frame.
unsigned SDL_Libretro_GetOptionCount(const SDL_Libretro *lr)
Get the number of core options that have been set.
void SDL_Libretro_SetMenuUserData(SDL_LibretroMenu *menu, void *userData)
Attaches generic application data to the menu.
bool SDL_Libretro_IsGameReady(const SDL_Libretro *lr)
Indicates whether a game is loaded and running.
bool SDL_Libretro_InsertDisk(SDL_Libretro *lr)
Closes the disk tray after a disk change.
bool SDL_Libretro_SetUsername(SDL_Libretro *lr, const char *username)
Sets the username reported to cores through RETRO_ENVIRONMENT_GET_USERNAME.
bool SDL_Libretro_AddMenuButton(SDL_LibretroMenu *menu, const char *label, SDL_LibretroMenuCallback callback, void *userdata)
Adds a button to the main menu, just above Quit.
SDL_LibretroMenuStyle SDL_Libretro_GetMenuStyle(const SDL_LibretroMenu *menu)
Gets the menu's active color theme.
void SDL_Libretro_MenuScreenshotClicked(SDL_LibretroMenu *menu, void *userdata)
A ready-made menu callback that saves a PNG screenshot of the current frame, reporting the result in ...
bool SDL_Libretro_SetScaleMode(SDL_Libretro *lr, SDL_ScaleMode mode)
Sets the texture scale mode used when the libretro frame is scaled.
void SDL_Libretro_SetAnalogToDigital(SDL_Libretro *lr, unsigned port, SDL_LibretroAnalogToDigital stick)
Selects which analog stick, if any, also registers D-Pad presses on the given controller port,...
const char * SDL_Libretro_GetCoreDirectory(SDL_Libretro *lr)
Get the core directory.
const char * SDL_Libretro_GetSaveDirectory(SDL_Libretro *lr)
Get the save directory.
bool SDL_Libretro_HandleMenuEvent(SDL_LibretroMenu *menu, const SDL_Event *event)
Routes an event to the menu; call this for every event, before SDL_Libretro_HandleEvent().
void SDL_Libretro_ToggleMenu(SDL_LibretroMenu *menu)
Opens the menu when it is closed, or closes it when it is open.
SDL_LibretroAnalogToDigital
Which analog stick, if any, also registers D-Pad presses for a controller port.
unsigned SDL_Libretro_GetMousePort(const SDL_Libretro *lr)
Get the controller port that the mouse drives.
void SDL_Libretro_SetLanguage(SDL_Libretro *lr, int language)
Override the language reported to cores through RETRO_ENVIRONMENT_GET_LANGUAGE.
#define SDL_LIBRETRO_MICRO_VERSION
The micro/patch version of SDL_Libretro.
const char * SDL_Libretro_GetCoreName(const SDL_Libretro *lr)
Retrieve the name of the libretro core that's actively loaded.
void SDL_Libretro_SetKeyboardMapping(SDL_Libretro *lr, int retroButton, SDL_Scancode scancode)
Maps a keyboard key to a RETRO_DEVICE_ID_JOYPAD_* button for Player 1 (controller port 0).
@ SDL_LIBRETRO_MENU_STYLE_COUNT
The number of menu styles.
A group of related core options.
const char * key
Unique identifier referenced by SDL_LibretroOption::category.
const char * desc
Human-readable name.
const char * info
Description / help text; may be empty.
A core option and its current state.
bool visible
Whether the frontend should display it.
SDL_LibretroOptionValue * values
The selectable values; dynamically allocated.
const char * desc
Human-readable name.
const char * value
The current value.
const char * category
Key of the category it belongs to; empty if none.
unsigned valuesCount
The number of populated entries in values.
const char * key
Unique identifier the core queries.
const char * info
Description / help text; may be empty.
const char * value
The value the core stores.
const char * label
Human-readable name; may be NULL, in which case display value itself.