From 7dda9f0b0086bc95445ac741aac74e0b074d6dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Andr=C3=A9=20Santoni?= Date: Thu, 8 May 2014 01:12:51 +0700 Subject: [PATCH] Lakka menu display and icons --- Makefile | 6 +- driver.c | 7 +- driver.h | 1 + frontend/menu/backend/menu_lakka_backend.c | 4293 ++++++++++++++++++++ frontend/menu/disp/lakka.c | 1253 +++--- frontend/menu/disp/lakka.h | 38 + frontend/menu/disp/png_texture_load.c | 172 + frontend/menu/disp/png_texture_load.h | 21 + frontend/menu/disp/tween.c | 75 + frontend/menu/disp/tween.h | 37 + gfx/gl.c | 10 +- media/lakka/fceu-game.png | Bin 0 -> 1551 bytes media/lakka/fceu.png | Bin 0 -> 2312 bytes media/lakka/fceumm-game.png | Bin 0 -> 1551 bytes media/lakka/fceumm.png | Bin 0 -> 2312 bytes media/lakka/gambatte-game.png | Bin 0 -> 2344 bytes media/lakka/gambatte.png | Bin 0 -> 3158 bytes media/lakka/genplus-game.png | Bin 0 -> 1098 bytes media/lakka/genplus.png | Bin 0 -> 6971 bytes media/lakka/imame4all-game.png | Bin 0 -> 9541 bytes media/lakka/imame4all.png | Bin 0 -> 5819 bytes media/lakka/loadstate.png | Bin 0 -> 1293 bytes media/lakka/mame078-game.png | Bin 0 -> 9541 bytes media/lakka/mame078.png | Bin 0 -> 5819 bytes media/lakka/mednafen-psx-game.png | Bin 0 -> 9847 bytes media/lakka/mednafen-psx.png | Bin 0 -> 7500 bytes media/lakka/nx.png | Bin 0 -> 10526 bytes media/lakka/nxengine.png | Bin 0 -> 10526 bytes media/lakka/picodrive-game.png | Bin 0 -> 1098 bytes media/lakka/picodrive.png | Bin 0 -> 6971 bytes media/lakka/pocketsnes-game.png | Bin 0 -> 1894 bytes media/lakka/pocketsnes.png | Bin 0 -> 7187 bytes media/lakka/reload.png | Bin 0 -> 1697 bytes media/lakka/resume.png | Bin 0 -> 900 bytes media/lakka/run.png | Bin 0 -> 827 bytes media/lakka/savestate.png | Bin 0 -> 1236 bytes media/lakka/screenshot.png | Bin 0 -> 1624 bytes media/lakka/setting.png | Bin 0 -> 4770 bytes media/lakka/settings.png | Bin 0 -> 6543 bytes media/lakka/snes9x-next-game.png | Bin 0 -> 1894 bytes media/lakka/snes9x-next.png | Bin 0 -> 7187 bytes qb/config.params.sh | 1 + 42 files changed, 5396 insertions(+), 518 deletions(-) create mode 100644 frontend/menu/backend/menu_lakka_backend.c create mode 100644 frontend/menu/disp/lakka.h create mode 100644 frontend/menu/disp/png_texture_load.c create mode 100644 frontend/menu/disp/png_texture_load.h create mode 100644 frontend/menu/disp/tween.c create mode 100644 frontend/menu/disp/tween.h create mode 100644 media/lakka/fceu-game.png create mode 100644 media/lakka/fceu.png create mode 100644 media/lakka/fceumm-game.png create mode 100644 media/lakka/fceumm.png create mode 100644 media/lakka/gambatte-game.png create mode 100644 media/lakka/gambatte.png create mode 100644 media/lakka/genplus-game.png create mode 100644 media/lakka/genplus.png create mode 100644 media/lakka/imame4all-game.png create mode 100644 media/lakka/imame4all.png create mode 100644 media/lakka/loadstate.png create mode 100644 media/lakka/mame078-game.png create mode 100644 media/lakka/mame078.png create mode 100644 media/lakka/mednafen-psx-game.png create mode 100644 media/lakka/mednafen-psx.png create mode 100644 media/lakka/nx.png create mode 100644 media/lakka/nxengine.png create mode 100644 media/lakka/picodrive-game.png create mode 100644 media/lakka/picodrive.png create mode 100644 media/lakka/pocketsnes-game.png create mode 100644 media/lakka/pocketsnes.png create mode 100644 media/lakka/reload.png create mode 100644 media/lakka/resume.png create mode 100644 media/lakka/run.png create mode 100644 media/lakka/savestate.png create mode 100644 media/lakka/screenshot.png create mode 100644 media/lakka/setting.png create mode 100644 media/lakka/settings.png create mode 100644 media/lakka/snes9x-next-game.png create mode 100644 media/lakka/snes9x-next.png diff --git a/Makefile b/Makefile index 644de6e901..c62fd6b45d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ include config.mk +HAVE_LAKKA = 1 +DEFINES += -DHAVE_LAKKA TARGET = retroarch tools/retroarch-joyconfig tools/retrolaunch/retrolaunch @@ -67,7 +69,6 @@ endif DEFINES = -DHAVE_CONFIG_H -DHAVE_SCREENSHOTS -DRARCH_INTERNAL -DHAVE_CC_RESAMPLER -#HAVE_LAKKA = 1 ifeq ($(GLOBAL_CONFIG_DIR),) GLOBAL_CONFIG_DIR = /etc @@ -101,8 +102,9 @@ ifeq ($(HAVE_RGUI), 1) DEFINES += -DHAVE_MENU HAVE_MENU_COMMON = 1 ifeq ($(HAVE_LAKKA), 1) - OBJ += frontend/menu/disp/lakka.o + OBJ += frontend/menu/disp/png_texture_load.o frontend/menu/disp/tween.o frontend/menu/backend/menu_lakka_backend.o frontend/menu/disp/lakka.o DEFINES += -DHAVE_LAKKA + LIBS += -lpng endif endif diff --git a/driver.c b/driver.c index a62e622edb..29d53a5702 100644 --- a/driver.c +++ b/driver.c @@ -1681,12 +1681,13 @@ static const menu_ctx_driver_t *menu_ctx_drivers[] = { #if defined(HAVE_RMENU_XUI) &menu_ctx_rmenu_xui, #endif -#if defined(HAVE_RGUI) - &menu_ctx_rgui, -#endif #if defined(HAVE_LAKKA) &menu_ctx_lakka, #endif +#if defined(HAVE_RGUI) + &menu_ctx_rgui, +#endif + NULL // zero length array is not valid }; diff --git a/driver.h b/driver.h index 0ac93028dd..ec5a1e94fb 100644 --- a/driver.h +++ b/driver.h @@ -685,6 +685,7 @@ extern const menu_ctx_driver_t menu_ctx_rgui; extern const menu_ctx_driver_t menu_ctx_lakka; extern const menu_ctx_driver_backend_t menu_ctx_backend_common; +extern const menu_ctx_driver_backend_t menu_ctx_backend_lakka; #ifdef HAVE_FILTERS_BUILTIN extern const struct softfilter_implementation *blargg_ntsc_snes_rf_get_implementation(softfilter_simd_mask_t simd); diff --git a/frontend/menu/backend/menu_lakka_backend.c b/frontend/menu/backend/menu_lakka_backend.c new file mode 100644 index 0000000000..3bc75dcf91 --- /dev/null +++ b/frontend/menu/backend/menu_lakka_backend.c @@ -0,0 +1,4293 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2014 - Hans-Kristian Arntzen + * Copyright (C) 2011-2014 - Daniel De Matteis + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include "menu_common_backend.h" +#include "../menu_navigation.h" +#include "../menu_input_line_cb.h" + +#include "../../../gfx/gfx_common.h" +#include "../../../driver.h" +#include "../../../file_ext.h" +#include "../../../input/input_common.h" +#include "../../../config.def.h" +#include "../../../input/keyboard_line.h" + +#ifdef HAVE_CONFIG_H +#include "../../../config.h" +#endif + +#if defined(__CELLOS_LV2__) +#include + +#if (CELL_SDK_VERSION > 0x340000) +#include +#endif +#endif + +static void menu_lakka_entries_init(void *data, unsigned menu_type) +{ + rgui_handle_t *rgui = (rgui_handle_t*)data; + unsigned i, last; + char tmp[256]; + switch (menu_type) + { +#ifdef HAVE_SHADER_MANAGER + case RGUI_SETTINGS_SHADER_OPTIONS: + file_list_clear(rgui->selection_buf); + file_list_push(rgui->selection_buf, "Apply Shader Changes", + RGUI_SETTINGS_SHADER_APPLY, 0); + file_list_push(rgui->selection_buf, "Default Filter", RGUI_SETTINGS_SHADER_FILTER, 0); + file_list_push(rgui->selection_buf, "Load Shader Preset", + RGUI_SETTINGS_SHADER_PRESET, 0); + file_list_push(rgui->selection_buf, "Save As Shader Preset", + RGUI_SETTINGS_SHADER_PRESET_SAVE, 0); + file_list_push(rgui->selection_buf, "Shader Passes", + RGUI_SETTINGS_SHADER_PASSES, 0); + + for (i = 0; i < rgui->shader.passes; i++) + { + char buf[64]; + + snprintf(buf, sizeof(buf), "Shader #%u", i); + file_list_push(rgui->selection_buf, buf, + RGUI_SETTINGS_SHADER_0 + 3 * i, 0); + + snprintf(buf, sizeof(buf), "Shader #%u Filter", i); + file_list_push(rgui->selection_buf, buf, + RGUI_SETTINGS_SHADER_0_FILTER + 3 * i, 0); + + snprintf(buf, sizeof(buf), "Shader #%u Scale", i); + file_list_push(rgui->selection_buf, buf, + RGUI_SETTINGS_SHADER_0_SCALE + 3 * i, 0); + } + break; +#endif + case RGUI_SETTINGS_GENERAL_OPTIONS: + file_list_clear(rgui->selection_buf); + file_list_push(rgui->selection_buf, "Configuration Save On Exit", RGUI_SETTINGS_CONFIG_SAVE_ON_EXIT, 0); + file_list_push(rgui->selection_buf, "Configuration Per-Core", RGUI_SETTINGS_PER_CORE_CONFIG, 0); +#ifdef HAVE_SCREENSHOTS + file_list_push(rgui->selection_buf, "GPU Screenshots", RGUI_SETTINGS_GPU_SCREENSHOT, 0); +#endif + file_list_push(rgui->selection_buf, "Load Dummy On Core Shutdown", RGUI_SETTINGS_LOAD_DUMMY_ON_CORE_SHUTDOWN, 0); + file_list_push(rgui->selection_buf, "Show Framerate", RGUI_SETTINGS_DEBUG_TEXT, 0); + file_list_push(rgui->selection_buf, "Rewind", RGUI_SETTINGS_REWIND_ENABLE, 0); + file_list_push(rgui->selection_buf, "Rewind Granularity", RGUI_SETTINGS_REWIND_GRANULARITY, 0); + file_list_push(rgui->selection_buf, "SRAM Block Overwrite", RGUI_SETTINGS_BLOCK_SRAM_OVERWRITE, 0); +#if defined(HAVE_THREADS) + file_list_push(rgui->selection_buf, "SRAM Autosave", RGUI_SETTINGS_SRAM_AUTOSAVE, 0); +#endif + file_list_push(rgui->selection_buf, "Window Compositing", RGUI_SETTINGS_WINDOW_COMPOSITING_ENABLE, 0); + file_list_push(rgui->selection_buf, "Window Unfocus Pause", RGUI_SETTINGS_PAUSE_IF_WINDOW_FOCUS_LOST, 0); + file_list_push(rgui->selection_buf, "Savestate Autosave On Exit", RGUI_SETTINGS_SAVESTATE_AUTO_SAVE, 0); + file_list_push(rgui->selection_buf, "Savestate Autoload", RGUI_SETTINGS_SAVESTATE_AUTO_LOAD, 0); + break; + case RGUI_SETTINGS_VIDEO_OPTIONS: + file_list_clear(rgui->selection_buf); +#if defined(GEKKO) || defined(__CELLOS_LV2__) + file_list_push(rgui->selection_buf, "Screen Resolution", RGUI_SETTINGS_VIDEO_RESOLUTION, 0); +#endif + file_list_push(rgui->selection_buf, "Soft Filter", RGUI_SETTINGS_VIDEO_SOFTFILTER, 0); +#if defined(__CELLOS_LV2__) + file_list_push(rgui->selection_buf, "PAL60 Mode", RGUI_SETTINGS_VIDEO_PAL60, 0); +#endif +#ifndef HAVE_SHADER_MANAGER + file_list_push(rgui->selection_buf, "Default Filter", RGUI_SETTINGS_VIDEO_FILTER, 0); +#endif +#ifdef HW_RVL + file_list_push(rgui->selection_buf, "VI Trap filtering", RGUI_SETTINGS_VIDEO_SOFT_FILTER, 0); +#endif +#if defined(HW_RVL) || defined(_XBOX360) + file_list_push(rgui->selection_buf, "Gamma", RGUI_SETTINGS_VIDEO_GAMMA, 0); +#endif +#ifdef _XBOX1 + file_list_push(rgui->selection_buf, "Soft filtering", RGUI_SETTINGS_SOFT_DISPLAY_FILTER, 0); + file_list_push(rgui->selection_buf, "Flicker filtering", RGUI_SETTINGS_FLICKER_FILTER, 0); +#endif + file_list_push(rgui->selection_buf, "Integer Scale", RGUI_SETTINGS_VIDEO_INTEGER_SCALE, 0); + file_list_push(rgui->selection_buf, "Aspect Ratio", RGUI_SETTINGS_VIDEO_ASPECT_RATIO, 0); + file_list_push(rgui->selection_buf, "Custom Ratio", RGUI_SETTINGS_CUSTOM_VIEWPORT, 0); +#if !defined(RARCH_CONSOLE) && !defined(RARCH_MOBILE) + file_list_push(rgui->selection_buf, "Toggle Fullscreen", RGUI_SETTINGS_TOGGLE_FULLSCREEN, 0); +#endif + file_list_push(rgui->selection_buf, "Rotation", RGUI_SETTINGS_VIDEO_ROTATION, 0); + file_list_push(rgui->selection_buf, "VSync", RGUI_SETTINGS_VIDEO_VSYNC, 0); + file_list_push(rgui->selection_buf, "Hard GPU Sync", RGUI_SETTINGS_VIDEO_HARD_SYNC, 0); + file_list_push(rgui->selection_buf, "Hard GPU Sync Frames", RGUI_SETTINGS_VIDEO_HARD_SYNC_FRAMES, 0); +#if !defined(RARCH_MOBILE) + file_list_push(rgui->selection_buf, "Black Frame Insertion", RGUI_SETTINGS_VIDEO_BLACK_FRAME_INSERTION, 0); +#endif + file_list_push(rgui->selection_buf, "VSync Swap Interval", RGUI_SETTINGS_VIDEO_SWAP_INTERVAL, 0); +#if defined(HAVE_THREADS) && !defined(GEKKO) + file_list_push(rgui->selection_buf, "Threaded Driver", RGUI_SETTINGS_VIDEO_THREADED, 0); +#endif +#if !defined(RARCH_CONSOLE) && !defined(RARCH_MOBILE) + file_list_push(rgui->selection_buf, "Windowed Scale (X)", RGUI_SETTINGS_VIDEO_WINDOW_SCALE_X, 0); + file_list_push(rgui->selection_buf, "Windowed Scale (Y)", RGUI_SETTINGS_VIDEO_WINDOW_SCALE_Y, 0); +#endif + file_list_push(rgui->selection_buf, "Crop Overscan (reload)", RGUI_SETTINGS_VIDEO_CROP_OVERSCAN, 0); + file_list_push(rgui->selection_buf, "Monitor Index", RGUI_SETTINGS_VIDEO_MONITOR_INDEX, 0); + file_list_push(rgui->selection_buf, "Estimated Monitor FPS", RGUI_SETTINGS_VIDEO_REFRESH_RATE_AUTO, 0); + break; + case RGUI_SETTINGS_FONT_OPTIONS: + file_list_clear(rgui->selection_buf); + file_list_push(rgui->selection_buf, "OSD Font Enable", RGUI_SETTINGS_FONT_ENABLE, 0); + file_list_push(rgui->selection_buf, "OSD Font Scale to Window", RGUI_SETTINGS_FONT_SCALE, 0); + file_list_push(rgui->selection_buf, "OSD Font Size", RGUI_SETTINGS_FONT_SIZE, 0); + break; + case RGUI_SETTINGS_CORE_OPTIONS: + file_list_clear(rgui->selection_buf); + + if (g_extern.system.core_options) + { + size_t i, opts; + + opts = core_option_size(g_extern.system.core_options); + for (i = 0; i < opts; i++) + file_list_push(rgui->selection_buf, + core_option_get_desc(g_extern.system.core_options, i), RGUI_SETTINGS_CORE_OPTION_START + i, 0); + } + else + file_list_push(rgui->selection_buf, "No options available.", RGUI_SETTINGS_CORE_OPTION_NONE, 0); + break; + case RGUI_SETTINGS_CORE_INFO: + file_list_clear(rgui->selection_buf); + if (rgui->core_info_current.data) + { + snprintf(tmp, sizeof(tmp), "Core name: %s", + rgui->core_info_current.display_name ? rgui->core_info_current.display_name : ""); + file_list_push(rgui->selection_buf, tmp, RGUI_SETTINGS_CORE_INFO_NONE, 0); + + if (rgui->core_info_current.authors_list) + { + strlcpy(tmp, "Authors: ", sizeof(tmp)); + string_list_join_concat(tmp, sizeof(tmp), rgui->core_info_current.authors_list, ", "); + file_list_push(rgui->selection_buf, tmp, RGUI_SETTINGS_CORE_INFO_NONE, 0); + } + + if (rgui->core_info_current.permissions_list) + { + strlcpy(tmp, "Permissions: ", sizeof(tmp)); + string_list_join_concat(tmp, sizeof(tmp), rgui->core_info_current.permissions_list, ", "); + file_list_push(rgui->selection_buf, tmp, RGUI_SETTINGS_CORE_INFO_NONE, 0); + } + + if (rgui->core_info_current.supported_extensions_list) + { + strlcpy(tmp, "Supported extensions: ", sizeof(tmp)); + string_list_join_concat(tmp, sizeof(tmp), rgui->core_info_current.supported_extensions_list, ", "); + file_list_push(rgui->selection_buf, tmp, RGUI_SETTINGS_CORE_INFO_NONE, 0); + } + + if (rgui->core_info_current.firmware_count > 0) + { + core_info_list_update_missing_firmware(rgui->core_info, rgui->core_info_current.path, + g_settings.system_directory); + + file_list_push(rgui->selection_buf, "Firmware: ", RGUI_SETTINGS_CORE_INFO_NONE, 0); + for (i = 0; i < rgui->core_info_current.firmware_count; i++) + { + if (rgui->core_info_current.firmware[i].desc) + { + snprintf(tmp, sizeof(tmp), " name: %s", + rgui->core_info_current.firmware[i].desc ? rgui->core_info_current.firmware[i].desc : ""); + file_list_push(rgui->selection_buf, tmp, RGUI_SETTINGS_CORE_INFO_NONE, 0); + + snprintf(tmp, sizeof(tmp), " status: %s, %s", + rgui->core_info_current.firmware[i].missing ? "missing" : "present", + rgui->core_info_current.firmware[i].optional ? "optional" : "required"); + file_list_push(rgui->selection_buf, tmp, RGUI_SETTINGS_CORE_INFO_NONE, 0); + } + } + } + + if (rgui->core_info_current.notes) + { + snprintf(tmp, sizeof(tmp), "Core notes: "); + file_list_push(rgui->selection_buf, tmp, RGUI_SETTINGS_CORE_INFO_NONE, 0); + + for (i = 0; i < rgui->core_info_current.note_list->size; i++) + { + snprintf(tmp, sizeof(tmp), " %s", rgui->core_info_current.note_list->elems[i].data); + file_list_push(rgui->selection_buf, tmp, RGUI_SETTINGS_CORE_INFO_NONE, 0); + } + } + } + else + file_list_push(rgui->selection_buf, "No information available.", RGUI_SETTINGS_CORE_OPTION_NONE, 0); + break; + case RGUI_SETTINGS_OPTIONS: + file_list_clear(rgui->selection_buf); + file_list_push(rgui->selection_buf, "General Options", RGUI_SETTINGS_GENERAL_OPTIONS, 0); + file_list_push(rgui->selection_buf, "Video Options", RGUI_SETTINGS_VIDEO_OPTIONS, 0); +#ifdef HAVE_SHADER_MANAGER + file_list_push(rgui->selection_buf, "Shader Options", RGUI_SETTINGS_SHADER_OPTIONS, 0); +#endif + file_list_push(rgui->selection_buf, "Font Options", RGUI_SETTINGS_FONT_OPTIONS, 0); + file_list_push(rgui->selection_buf, "Audio Options", RGUI_SETTINGS_AUDIO_OPTIONS, 0); + file_list_push(rgui->selection_buf, "Input Options", RGUI_SETTINGS_INPUT_OPTIONS, 0); +#ifdef HAVE_OVERLAY + file_list_push(rgui->selection_buf, "Overlay Options", RGUI_SETTINGS_OVERLAY_OPTIONS, 0); +#endif +#ifdef HAVE_NETPLAY + file_list_push(rgui->selection_buf, "Netplay Options", RGUI_SETTINGS_NETPLAY_OPTIONS, 0); +#endif + file_list_push(rgui->selection_buf, "Path Options", RGUI_SETTINGS_PATH_OPTIONS, 0); + if (g_extern.main_is_init && !g_extern.libretro_dummy) + { + if (g_extern.system.disk_control.get_num_images) + file_list_push(rgui->selection_buf, "Disk Options", RGUI_SETTINGS_DISK_OPTIONS, 0); + } + file_list_push(rgui->selection_buf, "Privacy Options", RGUI_SETTINGS_PRIVACY_OPTIONS, 0); + break; + case RGUI_SETTINGS_PRIVACY_OPTIONS: + file_list_clear(rgui->selection_buf); +#ifdef HAVE_CAMERA + file_list_push(rgui->selection_buf, "Allow Camera", RGUI_SETTINGS_PRIVACY_CAMERA_ALLOW, 0); +#endif +#ifdef HAVE_LOCATION + file_list_push(rgui->selection_buf, "Allow Location Services", RGUI_SETTINGS_PRIVACY_LOCATION_ALLOW, 0); +#endif + break; + case RGUI_SETTINGS_DISK_OPTIONS: + file_list_clear(rgui->selection_buf); + file_list_push(rgui->selection_buf, "Disk Index", RGUI_SETTINGS_DISK_INDEX, 0); + file_list_push(rgui->selection_buf, "Disk Image Append", RGUI_SETTINGS_DISK_APPEND, 0); + break; + case RGUI_SETTINGS_OVERLAY_OPTIONS: + file_list_clear(rgui->selection_buf); + file_list_push(rgui->selection_buf, "Overlay Preset", RGUI_SETTINGS_OVERLAY_PRESET, 0); + file_list_push(rgui->selection_buf, "Overlay Opacity", RGUI_SETTINGS_OVERLAY_OPACITY, 0); + file_list_push(rgui->selection_buf, "Overlay Scale", RGUI_SETTINGS_OVERLAY_SCALE, 0); + break; +#ifdef HAVE_NETPLAY + case RGUI_SETTINGS_NETPLAY_OPTIONS: + file_list_clear(rgui->selection_buf); + file_list_push(rgui->selection_buf, "Netplay Enable", RGUI_SETTINGS_NETPLAY_ENABLE, 0); + file_list_push(rgui->selection_buf, "Netplay Mode", RGUI_SETTINGS_NETPLAY_MODE, 0); + file_list_push(rgui->selection_buf, "Spectator Mode Enable", RGUI_SETTINGS_NETPLAY_SPECTATOR_MODE_ENABLE, 0); + file_list_push(rgui->selection_buf, "Host IP Address", RGUI_SETTINGS_NETPLAY_HOST_IP_ADDRESS, 0); + file_list_push(rgui->selection_buf, "TCP/UDP Port", RGUI_SETTINGS_NETPLAY_TCP_UDP_PORT, 0); + file_list_push(rgui->selection_buf, "Delay Frames", RGUI_SETTINGS_NETPLAY_DELAY_FRAMES, 0); + file_list_push(rgui->selection_buf, "Nickname", RGUI_SETTINGS_NETPLAY_NICKNAME, 0); + break; +#endif + case RGUI_SETTINGS_PATH_OPTIONS: + file_list_clear(rgui->selection_buf); + file_list_push(rgui->selection_buf, "Content Directory", RGUI_BROWSER_DIR_PATH, 0); +#ifdef HAVE_DYNAMIC + file_list_push(rgui->selection_buf, "Config Directory", RGUI_CONFIG_DIR_PATH, 0); +#endif + file_list_push(rgui->selection_buf, "Core Directory", RGUI_LIBRETRO_DIR_PATH, 0); + file_list_push(rgui->selection_buf, "Core Info Directory", RGUI_LIBRETRO_INFO_DIR_PATH, 0); +#ifdef HAVE_DYLIB + file_list_push(rgui->selection_buf, "Video Filter Directory", RGUI_FILTER_DIR_PATH, 0); + file_list_push(rgui->selection_buf, "DSP Filter Directory", RGUI_DSP_FILTER_DIR_PATH, 0); +#endif +#ifdef HAVE_SHADER_MANAGER + file_list_push(rgui->selection_buf, "Shader Directory", RGUI_SHADER_DIR_PATH, 0); +#endif + file_list_push(rgui->selection_buf, "Savestate Directory", RGUI_SAVESTATE_DIR_PATH, 0); + file_list_push(rgui->selection_buf, "Savefile Directory", RGUI_SAVEFILE_DIR_PATH, 0); +#ifdef HAVE_OVERLAY + file_list_push(rgui->selection_buf, "Overlay Directory", RGUI_OVERLAY_DIR_PATH, 0); +#endif + file_list_push(rgui->selection_buf, "System Directory", RGUI_SYSTEM_DIR_PATH, 0); +#ifdef HAVE_SCREENSHOTS + file_list_push(rgui->selection_buf, "Screenshot Directory", RGUI_SCREENSHOT_DIR_PATH, 0); +#endif + break; + case RGUI_SETTINGS_INPUT_OPTIONS: + file_list_clear(rgui->selection_buf); + file_list_push(rgui->selection_buf, "Player", RGUI_SETTINGS_BIND_PLAYER, 0); + file_list_push(rgui->selection_buf, "Device", RGUI_SETTINGS_BIND_DEVICE, 0); + file_list_push(rgui->selection_buf, "Device Type", RGUI_SETTINGS_BIND_DEVICE_TYPE, 0); + file_list_push(rgui->selection_buf, "Analog D-pad Mode", RGUI_SETTINGS_BIND_ANALOG_MODE, 0); + file_list_push(rgui->selection_buf, "Input Axis Threshold", RGUI_SETTINGS_INPUT_AXIS_THRESHOLD, 0); + file_list_push(rgui->selection_buf, "Autodetect enable", RGUI_SETTINGS_DEVICE_AUTODETECT_ENABLE, 0); + + file_list_push(rgui->selection_buf, "Bind Mode", RGUI_SETTINGS_CUSTOM_BIND_MODE, 0); + file_list_push(rgui->selection_buf, "Configure All (RetroPad)", RGUI_SETTINGS_CUSTOM_BIND_ALL, 0); + file_list_push(rgui->selection_buf, "Default All (RetroPad)", RGUI_SETTINGS_CUSTOM_BIND_DEFAULT_ALL, 0); +#ifdef HAVE_OSK + file_list_push(rgui->selection_buf, "Onscreen Keyboard Enable", RGUI_SETTINGS_ONSCREEN_KEYBOARD_ENABLE, 0); +#endif + last = (driver.input && driver.input->set_keybinds && !driver.input->get_joypad_driver) ? RGUI_SETTINGS_BIND_R3 : RGUI_SETTINGS_BIND_MENU_TOGGLE; + for (i = RGUI_SETTINGS_BIND_BEGIN; i <= last; i++) + file_list_push(rgui->selection_buf, input_config_bind_map[i - RGUI_SETTINGS_BIND_BEGIN].desc, i, 0); + break; + case RGUI_SETTINGS_AUDIO_OPTIONS: + file_list_clear(rgui->selection_buf); + file_list_push(rgui->selection_buf, "DSP Filter", RGUI_SETTINGS_AUDIO_DSP_FILTER, 0); + file_list_push(rgui->selection_buf, "Mute Audio", RGUI_SETTINGS_AUDIO_MUTE, 0); + file_list_push(rgui->selection_buf, "Rate Control Delta", RGUI_SETTINGS_AUDIO_CONTROL_RATE_DELTA, 0); +#ifdef __CELLOS_LV2__ + file_list_push(rgui->selection_buf, "System BGM Control", RGUI_SETTINGS_CUSTOM_BGM_CONTROL_ENABLE, 0); +#endif +#ifdef _XBOX1 + file_list_push(rgui->selection_buf, "Volume Effect", RGUI_SETTINGS_AUDIO_DSP_EFFECT, 0); +#endif + file_list_push(rgui->selection_buf, "Volume Level", RGUI_SETTINGS_AUDIO_VOLUME, 0); + break; + case RGUI_SETTINGS_DRIVERS: + file_list_clear(rgui->selection_buf); + file_list_push(rgui->selection_buf, "Video Driver", RGUI_SETTINGS_DRIVER_VIDEO, 0); + file_list_push(rgui->selection_buf, "Audio Driver", RGUI_SETTINGS_DRIVER_AUDIO, 0); + file_list_push(rgui->selection_buf, "Audio Device", RGUI_SETTINGS_DRIVER_AUDIO_DEVICE, 0); + file_list_push(rgui->selection_buf, "Audio Resampler", RGUI_SETTINGS_DRIVER_AUDIO_RESAMPLER, 0); + file_list_push(rgui->selection_buf, "Input Driver", RGUI_SETTINGS_DRIVER_INPUT, 0); +#ifdef HAVE_CAMERA + file_list_push(rgui->selection_buf, "Camera Driver", RGUI_SETTINGS_DRIVER_CAMERA, 0); +#endif +#ifdef HAVE_LOCATION + file_list_push(rgui->selection_buf, "Location Driver", RGUI_SETTINGS_DRIVER_LOCATION, 0); +#endif +#ifdef HAVE_MENU + file_list_push(rgui->selection_buf, "Menu Driver", RGUI_SETTINGS_DRIVER_MENU, 0); +#endif + break; + case RGUI_SETTINGS: + file_list_clear(rgui->selection_buf); + +#if defined(HAVE_DYNAMIC) || defined(HAVE_LIBRETRO_MANAGEMENT) + file_list_push(rgui->selection_buf, "Core", RGUI_SETTINGS_CORE, 0); +#endif + if (rgui->history) + file_list_push(rgui->selection_buf, "Load Content (History)", RGUI_SETTINGS_OPEN_HISTORY, 0); + + if (rgui->core_info && core_info_list_num_info_files(rgui->core_info)) + file_list_push(rgui->selection_buf, "Load Content (Detect Core)", RGUI_SETTINGS_OPEN_FILEBROWSER_DEFERRED_CORE, 0); + + if (rgui->info.library_name || g_extern.system.info.library_name) + { + char load_game_core_msg[64]; + snprintf(load_game_core_msg, sizeof(load_game_core_msg), "Load Content (%s)", + rgui->info.library_name ? rgui->info.library_name : g_extern.system.info.library_name); + file_list_push(rgui->selection_buf, load_game_core_msg, RGUI_SETTINGS_OPEN_FILEBROWSER, 0); + } + + file_list_push(rgui->selection_buf, "Core Options", RGUI_SETTINGS_CORE_OPTIONS, 0); + file_list_push(rgui->selection_buf, "Core Information", RGUI_SETTINGS_CORE_INFO, 0); + file_list_push(rgui->selection_buf, "Settings", RGUI_SETTINGS_OPTIONS, 0); + file_list_push(rgui->selection_buf, "Drivers", RGUI_SETTINGS_DRIVERS, 0); + + if (g_extern.main_is_init && !g_extern.libretro_dummy) + { + file_list_push(rgui->selection_buf, "Save State", RGUI_SETTINGS_SAVESTATE_SAVE, 0); + file_list_push(rgui->selection_buf, "Load State", RGUI_SETTINGS_SAVESTATE_LOAD, 0); +#ifdef HAVE_SCREENSHOTS + file_list_push(rgui->selection_buf, "Take Screenshot", RGUI_SETTINGS_SCREENSHOT, 0); +#endif + file_list_push(rgui->selection_buf, "Resume Content", RGUI_SETTINGS_RESUME_GAME, 0); + file_list_push(rgui->selection_buf, "Restart Content", RGUI_SETTINGS_RESTART_GAME, 0); + + } +#ifndef HAVE_DYNAMIC + file_list_push(rgui->selection_buf, "Restart RetroArch", RGUI_SETTINGS_RESTART_EMULATOR, 0); +#endif + file_list_push(rgui->selection_buf, "RetroArch Config", RGUI_SETTINGS_CONFIG, 0); + file_list_push(rgui->selection_buf, "Save New Config", RGUI_SETTINGS_SAVE_CONFIG, 0); + file_list_push(rgui->selection_buf, "Help", RGUI_START_SCREEN, 0); + file_list_push(rgui->selection_buf, "Quit RetroArch", RGUI_SETTINGS_QUIT_RARCH, 0); + break; + } + + if (driver.menu_ctx && driver.menu_ctx->populate_entries) + driver.menu_ctx->populate_entries(rgui, menu_type); + +} + +static int menu_start_screen_iterate(void *data, unsigned action) +{ + unsigned i; + char msg[1024]; + rgui_handle_t *rgui = (rgui_handle_t*)data; + + if (driver.video_data && driver.menu_ctx && driver.menu_ctx->render) + driver.menu_ctx->render(rgui); + + char desc[6][64]; + static const unsigned binds[] = { + RETRO_DEVICE_ID_JOYPAD_UP, + RETRO_DEVICE_ID_JOYPAD_DOWN, + RETRO_DEVICE_ID_JOYPAD_A, + RETRO_DEVICE_ID_JOYPAD_B, + RARCH_MENU_TOGGLE, + RARCH_QUIT_KEY, + }; + + for (i = 0; i < ARRAY_SIZE(binds); i++) + { + if (driver.input && driver.input->set_keybinds) + { + struct platform_bind key_label; + strlcpy(key_label.desc, "Unknown", sizeof(key_label.desc)); + key_label.joykey = g_settings.input.binds[0][binds[i]].joykey; + driver.input->set_keybinds(&key_label, 0, 0, 0, 1ULL << KEYBINDS_ACTION_GET_BIND_LABEL); + strlcpy(desc[i], key_label.desc, sizeof(desc[i])); + } + else + { + const struct retro_keybind *bind = &g_settings.input.binds[0][binds[i]]; + input_get_bind_string(desc[i], bind, sizeof(desc[i])); + } + } + + snprintf(msg, sizeof(msg), + "-- Welcome to RetroArch / RGUI --\n" + " \n" // strtok_r doesn't split empty strings. + + "Basic RGUI controls:\n" + " Scroll (Up): %-20s\n" + " Scroll (Down): %-20s\n" + " Accept/OK: %-20s\n" + " Back: %-20s\n" + "Enter/Exit RGUI: %-20s\n" + " Exit RetroArch: %-20s\n" + " \n" + + "To run content:\n" + "Load a libretro core (Core).\n" + "Load a content file (Load Content). \n" + " \n" + + "See Path Options to set directories\n" + "for faster access to files.\n" + " \n" + + "Press Accept/OK to continue.", + desc[0], desc[1], desc[2], desc[3], desc[4], desc[5]); + + if (driver.video_data && driver.menu_ctx && driver.menu_ctx->render_messagebox) + driver.menu_ctx->render_messagebox(rgui, msg); + + if (action == RGUI_ACTION_OK) + file_list_pop(rgui->menu_stack, &rgui->selection_ptr); + return 0; +} + +static unsigned menu_lakka_type_is(unsigned type) +{ + unsigned ret = 0; + bool type_found; + + type_found = + type == RGUI_SETTINGS || + type == RGUI_SETTINGS_GENERAL_OPTIONS || + type == RGUI_SETTINGS_CORE_OPTIONS || + type == RGUI_SETTINGS_CORE_INFO || + type == RGUI_SETTINGS_VIDEO_OPTIONS || + type == RGUI_SETTINGS_FONT_OPTIONS || + type == RGUI_SETTINGS_SHADER_OPTIONS || + type == RGUI_SETTINGS_AUDIO_OPTIONS || + type == RGUI_SETTINGS_DISK_OPTIONS || + type == RGUI_SETTINGS_PATH_OPTIONS || + type == RGUI_SETTINGS_PRIVACY_OPTIONS || + type == RGUI_SETTINGS_OVERLAY_OPTIONS || + type == RGUI_SETTINGS_NETPLAY_OPTIONS || + type == RGUI_SETTINGS_OPTIONS || + type == RGUI_SETTINGS_DRIVERS || + (type == RGUI_SETTINGS_INPUT_OPTIONS); + + if (type_found) + { + ret = RGUI_SETTINGS; + return ret; + } + + type_found = (type >= RGUI_SETTINGS_SHADER_0 && + type <= RGUI_SETTINGS_SHADER_LAST && + ((type - RGUI_SETTINGS_SHADER_0) % 3) == 0) || + type == RGUI_SETTINGS_SHADER_PRESET; + + if (type_found) + { + ret = RGUI_SETTINGS_SHADER_OPTIONS; + return ret; + } + + type_found = type == RGUI_BROWSER_DIR_PATH || + type == RGUI_SHADER_DIR_PATH || + type == RGUI_FILTER_DIR_PATH || + type == RGUI_DSP_FILTER_DIR_PATH || + type == RGUI_SAVESTATE_DIR_PATH || + type == RGUI_LIBRETRO_DIR_PATH || + type == RGUI_LIBRETRO_INFO_DIR_PATH || + type == RGUI_CONFIG_DIR_PATH || + type == RGUI_SAVEFILE_DIR_PATH || + type == RGUI_OVERLAY_DIR_PATH || + type == RGUI_SCREENSHOT_DIR_PATH || + type == RGUI_SYSTEM_DIR_PATH; + + if (type_found) + { + ret = RGUI_FILE_DIRECTORY; + return ret; + } + + return ret; +} + +static int menu_settings_iterate(void *data, unsigned action) +{ + rgui_handle_t *rgui = (rgui_handle_t*)data; + rgui->frame_buf_pitch = rgui->width * 2; + unsigned type = 0; + const char *label = NULL; + if (action != RGUI_ACTION_REFRESH) + file_list_get_at_offset(rgui->selection_buf, rgui->selection_ptr, &label, &type); + + if (type == RGUI_SETTINGS_CORE) + label = rgui->libretro_dir; + else if (type == RGUI_SETTINGS_CONFIG) + label = g_settings.rgui_config_directory; + else if (type == RGUI_SETTINGS_DISK_APPEND) + label = g_settings.rgui_content_directory; + + const char *dir = NULL; + unsigned menu_type = 0; + file_list_get_last(rgui->menu_stack, &dir, &menu_type); + + if (rgui->need_refresh) + action = RGUI_ACTION_NOOP; + + switch (action) + { + case RGUI_ACTION_UP: + if (rgui->selection_ptr > 0) + menu_decrement_navigation(rgui); + else + menu_set_navigation(rgui, rgui->selection_buf->size - 1); + break; + + case RGUI_ACTION_DOWN: + if (rgui->selection_ptr + 1 < rgui->selection_buf->size) + menu_increment_navigation(rgui); + else + menu_clear_navigation(rgui); + break; + + case RGUI_ACTION_CANCEL: + if (rgui->menu_stack->size > 1) + { + file_list_pop(rgui->menu_stack, &rgui->selection_ptr); + rgui->need_refresh = true; + } + break; + + case RGUI_ACTION_LEFT: + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + case RGUI_ACTION_START: + if ((type == RGUI_SETTINGS_OPEN_FILEBROWSER || type == RGUI_SETTINGS_OPEN_FILEBROWSER_DEFERRED_CORE) + && action == RGUI_ACTION_OK) + { + rgui->defer_core = type == RGUI_SETTINGS_OPEN_FILEBROWSER_DEFERRED_CORE; + file_list_push(rgui->menu_stack, g_settings.rgui_content_directory, RGUI_FILE_DIRECTORY, rgui->selection_ptr); + menu_clear_navigation(rgui); + rgui->need_refresh = true; + } + else if ((type == RGUI_SETTINGS_OPEN_HISTORY || menu_lakka_type_is(type) == RGUI_FILE_DIRECTORY) && action == RGUI_ACTION_OK) + { + file_list_push(rgui->menu_stack, "", type, rgui->selection_ptr); + menu_clear_navigation(rgui); + rgui->need_refresh = true; + } + else if ((menu_lakka_type_is(type) == RGUI_SETTINGS || type == RGUI_SETTINGS_CORE || type == RGUI_SETTINGS_CONFIG || type == RGUI_SETTINGS_DISK_APPEND) && action == RGUI_ACTION_OK) + { + file_list_push(rgui->menu_stack, label, type, rgui->selection_ptr); + menu_clear_navigation(rgui); + rgui->need_refresh = true; + } + else if (type == RGUI_SETTINGS_CUSTOM_VIEWPORT && action == RGUI_ACTION_OK) + { + file_list_push(rgui->menu_stack, "", type, rgui->selection_ptr); + + // Start with something sane. + rarch_viewport_t *custom = &g_extern.console.screen.viewports.custom_vp; + + if (driver.video_data && driver.video && driver.video->viewport_info) + driver.video->viewport_info(driver.video_data, custom); + aspectratio_lut[ASPECT_RATIO_CUSTOM].value = (float)custom->width / custom->height; + + g_settings.video.aspect_ratio_idx = ASPECT_RATIO_CUSTOM; + if (driver.video_data && driver.video_poke && driver.video_poke->set_aspect_ratio) + driver.video_poke->set_aspect_ratio(driver.video_data, + g_settings.video.aspect_ratio_idx); + } + else + { + int ret = 0; + + if (driver.menu_ctx && driver.menu_ctx->backend && driver.menu_ctx->backend->setting_toggle) + ret = driver.menu_ctx->backend->setting_toggle(rgui, type, action, menu_type); + if (ret) + return ret; + } + break; + + case RGUI_ACTION_REFRESH: + menu_clear_navigation(rgui); + rgui->need_refresh = true; + break; + + case RGUI_ACTION_MESSAGE: + rgui->msg_force = true; + break; + + default: + break; + } + + file_list_get_last(rgui->menu_stack, &dir, &menu_type); + + if (rgui->need_refresh && !(menu_type == RGUI_FILE_DIRECTORY || + menu_lakka_type_is(menu_type) == RGUI_SETTINGS_SHADER_OPTIONS|| + menu_lakka_type_is(menu_type) == RGUI_FILE_DIRECTORY || + menu_type == RGUI_SETTINGS_VIDEO_SOFTFILTER || + menu_type == RGUI_SETTINGS_AUDIO_DSP_FILTER || + menu_type == RGUI_SETTINGS_OVERLAY_PRESET || + menu_type == RGUI_SETTINGS_CORE || + menu_type == RGUI_SETTINGS_CONFIG || + menu_type == RGUI_SETTINGS_DISK_APPEND || + menu_type == RGUI_SETTINGS_OPEN_HISTORY)) + { + rgui->need_refresh = false; + if ( + menu_type == RGUI_SETTINGS_INPUT_OPTIONS + || menu_type == RGUI_SETTINGS_PATH_OPTIONS + || menu_type == RGUI_SETTINGS_OVERLAY_OPTIONS + || menu_type == RGUI_SETTINGS_NETPLAY_OPTIONS + || menu_type == RGUI_SETTINGS_OPTIONS + || menu_type == RGUI_SETTINGS_DRIVERS + || menu_type == RGUI_SETTINGS_CORE_INFO + || menu_type == RGUI_SETTINGS_CORE_OPTIONS + || menu_type == RGUI_SETTINGS_AUDIO_OPTIONS + || menu_type == RGUI_SETTINGS_DISK_OPTIONS + || menu_type == RGUI_SETTINGS_PRIVACY_OPTIONS + || menu_type == RGUI_SETTINGS_GENERAL_OPTIONS + || menu_type == RGUI_SETTINGS_VIDEO_OPTIONS + || menu_type == RGUI_SETTINGS_FONT_OPTIONS + || menu_type == RGUI_SETTINGS_SHADER_OPTIONS + ) + menu_lakka_entries_init(rgui, menu_type); + else + menu_lakka_entries_init(rgui, RGUI_SETTINGS); + } + + if (driver.video_data && driver.menu_ctx && driver.menu_ctx->render) + driver.menu_ctx->render(rgui); + + // Have to defer it so we let settings refresh. + if (rgui->push_start_screen) + { + rgui->push_start_screen = false; + file_list_push(rgui->menu_stack, "", RGUI_START_SCREEN, 0); + } + + return 0; +} + +static int menu_viewport_iterate(void *data, unsigned action) +{ + rgui_handle_t *rgui = (rgui_handle_t*)data; + rarch_viewport_t *custom = &g_extern.console.screen.viewports.custom_vp; + + unsigned menu_type = 0; + file_list_get_last(rgui->menu_stack, NULL, &menu_type); + + struct retro_game_geometry *geom = &g_extern.system.av_info.geometry; + int stride_x = g_settings.video.scale_integer ? + geom->base_width : 1; + int stride_y = g_settings.video.scale_integer ? + geom->base_height : 1; + + switch (action) + { + case RGUI_ACTION_UP: + if (menu_type == RGUI_SETTINGS_CUSTOM_VIEWPORT) + { + custom->y -= stride_y; + custom->height += stride_y; + } + else if (custom->height >= (unsigned)stride_y) + custom->height -= stride_y; + + if (driver.video_data && driver.video_poke && driver.video_poke->apply_state_changes) + driver.video_poke->apply_state_changes(driver.video_data); + break; + + case RGUI_ACTION_DOWN: + if (menu_type == RGUI_SETTINGS_CUSTOM_VIEWPORT) + { + custom->y += stride_y; + if (custom->height >= (unsigned)stride_y) + custom->height -= stride_y; + } + else + custom->height += stride_y; + + if (driver.video_data && driver.video_poke && driver.video_poke->apply_state_changes) + driver.video_poke->apply_state_changes(driver.video_data); + break; + + case RGUI_ACTION_LEFT: + if (menu_type == RGUI_SETTINGS_CUSTOM_VIEWPORT) + { + custom->x -= stride_x; + custom->width += stride_x; + } + else if (custom->width >= (unsigned)stride_x) + custom->width -= stride_x; + + if (driver.video_data && driver.video_poke && driver.video_poke->apply_state_changes) + driver.video_poke->apply_state_changes(driver.video_data); + break; + + case RGUI_ACTION_RIGHT: + if (menu_type == RGUI_SETTINGS_CUSTOM_VIEWPORT) + { + custom->x += stride_x; + if (custom->width >= (unsigned)stride_x) + custom->width -= stride_x; + } + else + custom->width += stride_x; + + if (driver.video_data && driver.video_poke && driver.video_poke->apply_state_changes) + driver.video_poke->apply_state_changes(driver.video_data); + break; + + case RGUI_ACTION_CANCEL: + file_list_pop(rgui->menu_stack, &rgui->selection_ptr); + if (menu_type == RGUI_SETTINGS_CUSTOM_VIEWPORT_2) + { + file_list_push(rgui->menu_stack, "", + RGUI_SETTINGS_CUSTOM_VIEWPORT, + rgui->selection_ptr); + } + break; + + case RGUI_ACTION_OK: + file_list_pop(rgui->menu_stack, &rgui->selection_ptr); + if (menu_type == RGUI_SETTINGS_CUSTOM_VIEWPORT + && !g_settings.video.scale_integer) + { + file_list_push(rgui->menu_stack, "", + RGUI_SETTINGS_CUSTOM_VIEWPORT_2, + rgui->selection_ptr); + } + break; + + case RGUI_ACTION_START: + if (!g_settings.video.scale_integer) + { + rarch_viewport_t vp; + + if (driver.video_data && driver.video && driver.video->viewport_info) + driver.video->viewport_info(driver.video_data, &vp); + + if (menu_type == RGUI_SETTINGS_CUSTOM_VIEWPORT) + { + custom->width += custom->x; + custom->height += custom->y; + custom->x = 0; + custom->y = 0; + } + else + { + custom->width = vp.full_width - custom->x; + custom->height = vp.full_height - custom->y; + } + + if (driver.video_data && driver.video_poke && driver.video_poke->apply_state_changes) + driver.video_poke->apply_state_changes(driver.video_data); + } + break; + + case RGUI_ACTION_MESSAGE: + rgui->msg_force = true; + break; + + default: + break; + } + + file_list_get_last(rgui->menu_stack, NULL, &menu_type); + + if (driver.video_data && driver.menu_ctx && driver.menu_ctx->render) + driver.menu_ctx->render(rgui); + + const char *base_msg = NULL; + char msg[64]; + + if (g_settings.video.scale_integer) + { + custom->x = 0; + custom->y = 0; + custom->width = ((custom->width + geom->base_width - 1) / geom->base_width) * geom->base_width; + custom->height = ((custom->height + geom->base_height - 1) / geom->base_height) * geom->base_height; + + base_msg = "Set scale"; + snprintf(msg, sizeof(msg), "%s (%4ux%4u, %u x %u scale)", + base_msg, + custom->width, custom->height, + custom->width / geom->base_width, + custom->height / geom->base_height); + } + else + { + if (menu_type == RGUI_SETTINGS_CUSTOM_VIEWPORT) + base_msg = "Set Upper-Left Corner"; + else if (menu_type == RGUI_SETTINGS_CUSTOM_VIEWPORT_2) + base_msg = "Set Bottom-Right Corner"; + + snprintf(msg, sizeof(msg), "%s (%d, %d : %4ux%4u)", + base_msg, custom->x, custom->y, custom->width, custom->height); + } + + if (driver.video_data && driver.menu_ctx && driver.menu_ctx->render_messagebox) + driver.menu_ctx->render_messagebox(rgui, msg); + + if (!custom->width) + custom->width = stride_x; + if (!custom->height) + custom->height = stride_y; + + aspectratio_lut[ASPECT_RATIO_CUSTOM].value = + (float)custom->width / custom->height; + + if (driver.video_data && driver.video_poke && driver.video_poke->apply_state_changes) + driver.video_poke->apply_state_changes(driver.video_data); + + return 0; +} + +static void menu_parse_and_resolve(void *data, unsigned menu_type) +{ + const core_info_t *info = NULL; + const char *dir; + size_t i, list_size; + file_list_t *list; + rgui_handle_t *rgui; + + rgui = (rgui_handle_t*)data; + dir = NULL; + + file_list_clear(rgui->selection_buf); + + // parsing switch + switch (menu_type) + { + case RGUI_SETTINGS_OPEN_HISTORY: + /* History parse */ + list_size = rom_history_size(rgui->history); + + for (i = 0; i < list_size; i++) + { + const char *path, *core_path, *core_name; + char fill_buf[PATH_MAX]; + + path = NULL; + core_path = NULL; + core_name = NULL; + + rom_history_get_index(rgui->history, i, + &path, &core_path, &core_name); + + if (path) + { + char path_short[PATH_MAX]; + fill_pathname(path_short, path_basename(path), "", sizeof(path_short)); + + snprintf(fill_buf, sizeof(fill_buf), "%s (%s)", + path_short, core_name); + } + else + strlcpy(fill_buf, core_name, sizeof(fill_buf)); + + file_list_push(rgui->selection_buf, fill_buf, RGUI_FILE_PLAIN, 0); + } + break; + case RGUI_SETTINGS_DEFERRED_CORE: + break; + default: + { + /* Directory parse */ + file_list_get_last(rgui->menu_stack, &dir, &menu_type); + + if (!*dir) + { +#if defined(GEKKO) +#ifdef HW_RVL + file_list_push(rgui->selection_buf, "sd:/", menu_type, 0); + file_list_push(rgui->selection_buf, "usb:/", menu_type, 0); +#endif + file_list_push(rgui->selection_buf, "carda:/", menu_type, 0); + file_list_push(rgui->selection_buf, "cardb:/", menu_type, 0); +#elif defined(_XBOX1) + file_list_push(rgui->selection_buf, "C:", menu_type, 0); + file_list_push(rgui->selection_buf, "D:", menu_type, 0); + file_list_push(rgui->selection_buf, "E:", menu_type, 0); + file_list_push(rgui->selection_buf, "F:", menu_type, 0); + file_list_push(rgui->selection_buf, "G:", menu_type, 0); +#elif defined(_XBOX360) + file_list_push(rgui->selection_buf, "game:", menu_type, 0); +#elif defined(_WIN32) + unsigned drives = GetLogicalDrives(); + char drive[] = " :\\"; + for (i = 0; i < 32; i++) + { + drive[0] = 'A' + i; + if (drives & (1 << i)) + file_list_push(rgui->selection_buf, drive, menu_type, 0); + } +#elif defined(__CELLOS_LV2__) + file_list_push(rgui->selection_buf, "/app_home/", menu_type, 0); + file_list_push(rgui->selection_buf, "/dev_hdd0/", menu_type, 0); + file_list_push(rgui->selection_buf, "/dev_hdd1/", menu_type, 0); + file_list_push(rgui->selection_buf, "/host_root/", menu_type, 0); + file_list_push(rgui->selection_buf, "/dev_usb000/", menu_type, 0); + file_list_push(rgui->selection_buf, "/dev_usb001/", menu_type, 0); + file_list_push(rgui->selection_buf, "/dev_usb002/", menu_type, 0); + file_list_push(rgui->selection_buf, "/dev_usb003/", menu_type, 0); + file_list_push(rgui->selection_buf, "/dev_usb004/", menu_type, 0); + file_list_push(rgui->selection_buf, "/dev_usb005/", menu_type, 0); + file_list_push(rgui->selection_buf, "/dev_usb006/", menu_type, 0); +#elif defined(PSP) + file_list_push(rgui->selection_buf, "ms0:/", menu_type, 0); + file_list_push(rgui->selection_buf, "ef0:/", menu_type, 0); + file_list_push(rgui->selection_buf, "host0:/", menu_type, 0); +#else + file_list_push(rgui->selection_buf, "/", menu_type, 0); +#endif + return; + } +#if defined(GEKKO) && defined(HW_RVL) + LWP_MutexLock(gx_device_mutex); + int dev = gx_get_device_from_path(dir); + + if (dev != -1 && !gx_devices[dev].mounted && gx_devices[dev].interface->isInserted()) + fatMountSimple(gx_devices[dev].name, gx_devices[dev].interface); + + LWP_MutexUnlock(gx_device_mutex); +#endif + + const char *exts; + char ext_buf[1024]; + if (menu_type == RGUI_SETTINGS_CORE) + exts = EXT_EXECUTABLES; + else if (menu_type == RGUI_SETTINGS_CONFIG) + exts = "cfg"; + else if (menu_type == RGUI_SETTINGS_SHADER_PRESET) + exts = "cgp|glslp"; + else if (menu_lakka_type_is(menu_type) == RGUI_SETTINGS_SHADER_OPTIONS) + exts = "cg|glsl"; + else if (menu_type == RGUI_SETTINGS_VIDEO_SOFTFILTER) + exts = EXT_EXECUTABLES; + else if (menu_type == RGUI_SETTINGS_AUDIO_DSP_FILTER) + exts = EXT_EXECUTABLES; + else if (menu_type == RGUI_SETTINGS_OVERLAY_PRESET) + exts = "cfg"; + else if (menu_lakka_type_is(menu_type) == RGUI_FILE_DIRECTORY) + exts = ""; // we ignore files anyway + else if (rgui->defer_core) + exts = rgui->core_info ? core_info_list_get_all_extensions(rgui->core_info) : ""; + else if (rgui->info.valid_extensions) + { + exts = ext_buf; + if (*rgui->info.valid_extensions) + snprintf(ext_buf, sizeof(ext_buf), "%s|zip", rgui->info.valid_extensions); + else + *ext_buf = '\0'; + } + else + exts = g_extern.system.valid_extensions; + + struct string_list *list = dir_list_new(dir, exts, true); + if (!list) + return; + + dir_list_sort(list, true); + + if (menu_lakka_type_is(menu_type) == RGUI_FILE_DIRECTORY) + file_list_push(rgui->selection_buf, "", RGUI_FILE_USE_DIRECTORY, 0); + + for (i = 0; i < list->size; i++) + { + bool is_dir = list->elems[i].attr.b; + + if ((menu_lakka_type_is(menu_type) == RGUI_FILE_DIRECTORY) && !is_dir) + continue; + + // Need to preserve slash first time. + const char *path = list->elems[i].data; + if (*dir) + path = path_basename(path); + +#ifdef HAVE_LIBRETRO_MANAGEMENT + if (menu_type == RGUI_SETTINGS_CORE && (is_dir || strcasecmp(path, SALAMANDER_FILE) == 0)) + continue; +#endif + + // Push menu_type further down in the chain. + // Needed for shader manager currently. + file_list_push(rgui->selection_buf, path, + is_dir ? menu_type : RGUI_FILE_PLAIN, 0); + } + + if (driver.menu_ctx && driver.menu_ctx->backend->entries_init) + driver.menu_ctx->backend->entries_init(rgui, menu_type); + + string_list_free(list); + } + } + + // resolving switch + switch (menu_type) + { + case RGUI_SETTINGS_CORE: + dir = NULL; + list = (file_list_t*)rgui->selection_buf; + file_list_get_last(rgui->menu_stack, &dir, &menu_type); + list_size = list->size; + for (i = 0; i < list_size; i++) + { + const char *path; + unsigned type = 0; + file_list_get_at_offset(list, i, &path, &type); + if (type != RGUI_FILE_PLAIN) + continue; + + char core_path[PATH_MAX]; + fill_pathname_join(core_path, dir, path, sizeof(core_path)); + + char display_name[256]; + if (rgui->core_info && + core_info_list_get_display_name(rgui->core_info, + core_path, display_name, sizeof(display_name))) + file_list_set_alt_at_offset(list, i, display_name); + } + file_list_sort_on_alt(rgui->selection_buf); + break; + case RGUI_SETTINGS_DEFERRED_CORE: + core_info_list_get_supported_cores(rgui->core_info, rgui->deferred_path, &info, &list_size); + for (i = 0; i < list_size; i++) + { + file_list_push(rgui->selection_buf, info[i].path, RGUI_FILE_PLAIN, 0); + file_list_set_alt_at_offset(rgui->selection_buf, i, info[i].display_name); + } + file_list_sort_on_alt(rgui->selection_buf); + break; + default: + (void)0; + } + + rgui->scroll_indices_size = 0; + if (menu_type != RGUI_SETTINGS_OPEN_HISTORY) + menu_build_scroll_indices(rgui, rgui->selection_buf); + + // Before a refresh, we could have deleted a file on disk, causing + // selection_ptr to suddendly be out of range. Ensure it doesn't overflow. + if (rgui->selection_ptr >= rgui->selection_buf->size && rgui->selection_buf->size) + menu_set_navigation(rgui, rgui->selection_buf->size - 1); + else if (!rgui->selection_buf->size) + menu_clear_navigation(rgui); +} + +// This only makes sense for PC so far. +// Consoles use set_keybind callbacks instead. +static int menu_custom_bind_iterate(void *data, unsigned action) +{ + rgui_handle_t *rgui = (rgui_handle_t*)data; + (void)action; // Have to ignore action here. Only bind that should work here is Quit RetroArch or something like that. + + if (driver.video_data && driver.menu_ctx && driver.menu_ctx->render) + driver.menu_ctx->render(rgui); + + char msg[256]; + snprintf(msg, sizeof(msg), "[%s]\npress joypad\n(RETURN to skip)", input_config_bind_map[rgui->binds.begin - RGUI_SETTINGS_BIND_BEGIN].desc); + + if (driver.video_data && driver.menu_ctx && driver.menu_ctx->render_messagebox) + driver.menu_ctx->render_messagebox(rgui, msg); + + struct rgui_bind_state binds = rgui->binds; + menu_poll_bind_state(&binds); + + if ((binds.skip && !rgui->binds.skip) || menu_poll_find_trigger(&rgui->binds, &binds)) + { + binds.begin++; + if (binds.begin <= binds.last) + binds.target++; + else + file_list_pop(rgui->menu_stack, &rgui->selection_ptr); + + // Avoid new binds triggering things right away. + rgui->trigger_state = 0; + rgui->old_input_state = -1ULL; + } + rgui->binds = binds; + return 0; +} + +static int menu_custom_bind_iterate_keyboard(void *data, unsigned action) +{ + rgui_handle_t *rgui = (rgui_handle_t*)data; + (void)action; // Have to ignore action here. + + if (driver.video_data && driver.menu_ctx && driver.menu_ctx->render) + driver.menu_ctx->render(rgui); + + int64_t current = rarch_get_time_usec(); + int timeout = (rgui->binds.timeout_end - current) / 1000000; + + char msg[256]; + snprintf(msg, sizeof(msg), "[%s]\npress keyboard\n(timeout %d seconds)", + input_config_bind_map[rgui->binds.begin - RGUI_SETTINGS_BIND_BEGIN].desc, timeout); + + if (driver.video_data && driver.menu_ctx && driver.menu_ctx->render_messagebox) + driver.menu_ctx->render_messagebox(rgui, msg); + + bool timed_out = false; + if (timeout <= 0) + { + rgui->binds.begin++; + rgui->binds.target->key = RETROK_UNKNOWN; // Could be unsafe, but whatever. + rgui->binds.target++; + rgui->binds.timeout_end = rarch_get_time_usec() + RGUI_KEYBOARD_BIND_TIMEOUT_SECONDS * 1000000; + timed_out = true; + } + + // binds.begin is updated in keyboard_press callback. + if (rgui->binds.begin > rgui->binds.last) + { + file_list_pop(rgui->menu_stack, &rgui->selection_ptr); + + // Avoid new binds triggering things right away. + rgui->trigger_state = 0; + rgui->old_input_state = -1ULL; + + // We won't be getting any key events, so just cancel early. + if (timed_out) + input_keyboard_wait_keys_cancel(); + } + return 0; +} + +static int menu_lakka_iterate(void *data, unsigned action) +{ + rgui_handle_t *rgui = (rgui_handle_t*)data; + + const char *dir = 0; + unsigned menu_type = 0; + file_list_get_last(rgui->menu_stack, &dir, &menu_type); + int ret = 0; + + if (driver.video_data && driver.menu_ctx && driver.menu_ctx->set_texture) + driver.menu_ctx->set_texture(rgui, false); + +#ifdef HAVE_OSK + // process pending osk init callback + if (g_extern.osk.cb_init) + { + if (g_extern.osk.cb_init(driver.osk_data)) + g_extern.osk.cb_init = NULL; + } + + // process pending osk callback + if (g_extern.osk.cb_callback) + { + if (g_extern.osk.cb_callback(driver.osk_data)) + g_extern.osk.cb_callback = NULL; + } +#endif + + if (menu_type == RGUI_START_SCREEN) + return menu_start_screen_iterate(rgui, action); + else if (menu_lakka_type_is(menu_type) == RGUI_SETTINGS) + return menu_settings_iterate(rgui, action); + else if (menu_type == RGUI_SETTINGS_CUSTOM_VIEWPORT || menu_type == RGUI_SETTINGS_CUSTOM_VIEWPORT_2) + return menu_viewport_iterate(rgui, action); + else if (menu_type == RGUI_SETTINGS_CUSTOM_BIND) + return menu_custom_bind_iterate(rgui, action); + else if (menu_type == RGUI_SETTINGS_CUSTOM_BIND_KEYBOARD) + return menu_custom_bind_iterate_keyboard(rgui, action); + + if (rgui->need_refresh && action != RGUI_ACTION_MESSAGE) + action = RGUI_ACTION_NOOP; + + unsigned scroll_speed = (max(rgui->scroll_accel, 2) - 2) / 4 + 1; + unsigned fast_scroll_speed = 4 + 4 * scroll_speed; + + switch (action) + { + case RGUI_ACTION_UP: + if (rgui->selection_ptr >= scroll_speed) + menu_set_navigation(rgui, rgui->selection_ptr - scroll_speed); + else + menu_set_navigation(rgui, rgui->selection_buf->size - 1); + break; + + case RGUI_ACTION_DOWN: + if (rgui->selection_ptr + scroll_speed < rgui->selection_buf->size) + menu_set_navigation(rgui, rgui->selection_ptr + scroll_speed); + else + menu_clear_navigation(rgui); + break; + + case RGUI_ACTION_LEFT: + if (rgui->selection_ptr > fast_scroll_speed) + menu_set_navigation(rgui, rgui->selection_ptr - fast_scroll_speed); + else + menu_clear_navigation(rgui); + break; + + case RGUI_ACTION_RIGHT: + if (rgui->selection_ptr + fast_scroll_speed < rgui->selection_buf->size) + menu_set_navigation(rgui, rgui->selection_ptr + fast_scroll_speed); + else + menu_set_navigation_last(rgui); + break; + + case RGUI_ACTION_SCROLL_UP: + menu_descend_alphabet(rgui, &rgui->selection_ptr); + break; + case RGUI_ACTION_SCROLL_DOWN: + menu_ascend_alphabet(rgui, &rgui->selection_ptr); + break; + + case RGUI_ACTION_CANCEL: + if (rgui->menu_stack->size > 1) + { + file_list_pop(rgui->menu_stack, &rgui->selection_ptr); + rgui->need_refresh = true; + } + break; + + case RGUI_ACTION_OK: + { + if (rgui->selection_buf->size == 0) + return 0; + + const char *path = 0; + unsigned type = 0; + file_list_get_at_offset(rgui->selection_buf, rgui->selection_ptr, &path, &type); + + if ( + menu_lakka_type_is(type) == RGUI_SETTINGS_SHADER_OPTIONS || + menu_lakka_type_is(type) == RGUI_FILE_DIRECTORY || + type == RGUI_SETTINGS_OVERLAY_PRESET || + type == RGUI_SETTINGS_VIDEO_SOFTFILTER || + type == RGUI_SETTINGS_AUDIO_DSP_FILTER || + type == RGUI_SETTINGS_CORE || + type == RGUI_SETTINGS_CONFIG || + type == RGUI_SETTINGS_DISK_APPEND || + type == RGUI_FILE_DIRECTORY) + { + char cat_path[PATH_MAX]; + fill_pathname_join(cat_path, dir, path, sizeof(cat_path)); + + file_list_push(rgui->menu_stack, cat_path, type, rgui->selection_ptr); + menu_clear_navigation(rgui); + rgui->need_refresh = true; + } + else + { +#ifdef HAVE_SHADER_MANAGER + if (menu_lakka_type_is(menu_type) == RGUI_SETTINGS_SHADER_OPTIONS) + { + if (menu_type == RGUI_SETTINGS_SHADER_PRESET) + { + char shader_path[PATH_MAX]; + fill_pathname_join(shader_path, dir, path, sizeof(shader_path)); + if (driver.menu_ctx && driver.menu_ctx->backend && driver.menu_ctx->backend->shader_manager_set_preset) + driver.menu_ctx->backend->shader_manager_set_preset(&rgui->shader, gfx_shader_parse_type(shader_path, RARCH_SHADER_NONE), + shader_path); + } + else + { + unsigned pass = (menu_type - RGUI_SETTINGS_SHADER_0) / 3; + fill_pathname_join(rgui->shader.pass[pass].source.cg, + dir, path, sizeof(rgui->shader.pass[pass].source.cg)); + } + + // Pop stack until we hit shader manager again. + menu_flush_stack_type(rgui, RGUI_SETTINGS_SHADER_OPTIONS); + } + else +#endif + if (menu_type == RGUI_SETTINGS_DEFERRED_CORE) + { + // FIXME: Add for consoles. + strlcpy(g_settings.libretro, path, sizeof(g_settings.libretro)); + strlcpy(g_extern.fullpath, rgui->deferred_path, sizeof(g_extern.fullpath)); + load_menu_game_new_core(); + rgui->msg_force = true; + ret = -1; + menu_flush_stack_type(rgui, RGUI_SETTINGS); + } + else if (menu_type == RGUI_SETTINGS_CORE) + { +#if defined(HAVE_DYNAMIC) + fill_pathname_join(g_settings.libretro, dir, path, sizeof(g_settings.libretro)); + menu_update_system_info(rgui, &rgui->load_no_rom); + + // No ROM needed for this core, load game immediately. + if (rgui->load_no_rom) + { + g_extern.lifecycle_state |= (1ULL << MODE_LOAD_GAME); + *g_extern.fullpath = '\0'; + rgui->msg_force = true; + ret = -1; + } + + // Core selection on non-console just updates directory listing. + // Will take affect on new ROM load. +#elif defined(RARCH_CONSOLE) + rarch_environment_cb(RETRO_ENVIRONMENT_SET_LIBRETRO_PATH, (void*)path); + +#if defined(GEKKO) && defined(HW_RVL) + fill_pathname_join(g_extern.fullpath, default_paths.core_dir, + SALAMANDER_FILE, sizeof(g_extern.fullpath)); +#else + fill_pathname_join(g_settings.libretro, dir, path, sizeof(g_settings.libretro)); +#endif + g_extern.lifecycle_state &= ~(1ULL << MODE_GAME); + g_extern.lifecycle_state |= (1ULL << MODE_EXITSPAWN); + ret = -1; +#endif + + menu_flush_stack_type(rgui, RGUI_SETTINGS); + } + else if (menu_type == RGUI_SETTINGS_CONFIG) + { + char config[PATH_MAX]; + fill_pathname_join(config, dir, path, sizeof(config)); + menu_flush_stack_type(rgui, RGUI_SETTINGS); + rgui->msg_force = true; + if (menu_replace_config(config)) + { + menu_clear_navigation(rgui); + ret = -1; + } + } +#ifdef HAVE_OVERLAY + else if (menu_type == RGUI_SETTINGS_OVERLAY_PRESET) + { + fill_pathname_join(g_settings.input.overlay, dir, path, sizeof(g_settings.input.overlay)); + + if (driver.overlay) + input_overlay_free(driver.overlay); + driver.overlay = input_overlay_new(g_settings.input.overlay); + if (!driver.overlay) + RARCH_ERR("Failed to load overlay.\n"); + + menu_flush_stack_type(rgui, RGUI_SETTINGS_OPTIONS); + } +#endif + else if (menu_type == RGUI_SETTINGS_DISK_APPEND) + { + char image[PATH_MAX]; + fill_pathname_join(image, dir, path, sizeof(image)); + rarch_disk_control_append_image(image); + + g_extern.lifecycle_state |= 1ULL << MODE_GAME; + + menu_flush_stack_type(rgui, RGUI_SETTINGS); + ret = -1; + } + else if (menu_type == RGUI_SETTINGS_OPEN_HISTORY) + { + load_menu_game_history(rgui->selection_ptr); + menu_flush_stack_type(rgui, RGUI_SETTINGS); + ret = -1; + } + else if (menu_type == RGUI_BROWSER_DIR_PATH) + { + strlcpy(g_settings.rgui_content_directory, dir, sizeof(g_settings.rgui_content_directory)); + menu_flush_stack_type(rgui, RGUI_SETTINGS_PATH_OPTIONS); + } +#ifdef HAVE_SCREENSHOTS + else if (menu_type == RGUI_SCREENSHOT_DIR_PATH) + { + strlcpy(g_settings.screenshot_directory, dir, sizeof(g_settings.screenshot_directory)); + menu_flush_stack_type(rgui, RGUI_SETTINGS_PATH_OPTIONS); + } +#endif + else if (menu_type == RGUI_SAVEFILE_DIR_PATH) + { + strlcpy(g_extern.savefile_dir, dir, sizeof(g_extern.savefile_dir)); + menu_flush_stack_type(rgui, RGUI_SETTINGS_PATH_OPTIONS); + } +#ifdef HAVE_OVERLAY + else if (menu_type == RGUI_OVERLAY_DIR_PATH) + { + strlcpy(g_extern.overlay_dir, dir, sizeof(g_extern.overlay_dir)); + menu_flush_stack_type(rgui, RGUI_SETTINGS_PATH_OPTIONS); + } +#endif + else if (menu_type == RGUI_SETTINGS_VIDEO_SOFTFILTER) + { + fill_pathname_join(g_settings.video.filter_path, dir, path, sizeof(g_settings.video.filter_path)); +#ifdef HAVE_DYLIB + rarch_set_fullscreen(g_settings.video.fullscreen); +#endif + menu_flush_stack_type(rgui, RGUI_SETTINGS_VIDEO_OPTIONS); + } + else if (menu_type == RGUI_SETTINGS_AUDIO_DSP_FILTER) + { +#ifdef HAVE_DYLIB + fill_pathname_join(g_settings.audio.dsp_plugin, dir, path, sizeof(g_settings.audio.dsp_plugin)); +#endif + rarch_deinit_dsp_filter(); + rarch_init_dsp_filter(); + menu_flush_stack_type(rgui, RGUI_SETTINGS_AUDIO_OPTIONS); + } + else if (menu_type == RGUI_SAVESTATE_DIR_PATH) + { + strlcpy(g_extern.savestate_dir, dir, sizeof(g_extern.savestate_dir)); + menu_flush_stack_type(rgui, RGUI_SETTINGS_PATH_OPTIONS); + } + else if (menu_type == RGUI_LIBRETRO_DIR_PATH) + { + strlcpy(rgui->libretro_dir, dir, sizeof(rgui->libretro_dir)); + menu_init_core_info(rgui); + menu_flush_stack_type(rgui, RGUI_SETTINGS_PATH_OPTIONS); + } +#ifdef HAVE_DYNAMIC + else if (menu_type == RGUI_CONFIG_DIR_PATH) + { + strlcpy(g_settings.rgui_config_directory, dir, sizeof(g_settings.rgui_config_directory)); + menu_flush_stack_type(rgui, RGUI_SETTINGS_PATH_OPTIONS); + } +#endif + else if (menu_type == RGUI_LIBRETRO_INFO_DIR_PATH) + { + strlcpy(g_settings.libretro_info_path, dir, sizeof(g_settings.libretro_info_path)); + menu_init_core_info(rgui); + menu_flush_stack_type(rgui, RGUI_SETTINGS_PATH_OPTIONS); + } + else if (menu_type == RGUI_SHADER_DIR_PATH) + { + strlcpy(g_settings.video.shader_dir, dir, sizeof(g_settings.video.shader_dir)); + menu_flush_stack_type(rgui, RGUI_SETTINGS_PATH_OPTIONS); + } + else if (menu_type == RGUI_FILTER_DIR_PATH) + { + strlcpy(g_settings.video.filter_dir, dir, sizeof(g_settings.video.filter_dir)); + menu_flush_stack_type(rgui, RGUI_SETTINGS_PATH_OPTIONS); + } + else if (menu_type == RGUI_DSP_FILTER_DIR_PATH) + { + strlcpy(g_settings.audio.filter_dir, dir, sizeof(g_settings.audio.filter_dir)); + menu_flush_stack_type(rgui, RGUI_SETTINGS_PATH_OPTIONS); + } + else if (menu_type == RGUI_SYSTEM_DIR_PATH) + { + strlcpy(g_settings.system_directory, dir, sizeof(g_settings.system_directory)); + menu_flush_stack_type(rgui, RGUI_SETTINGS_PATH_OPTIONS); + } + else + { + if (rgui->defer_core) + { + fill_pathname_join(rgui->deferred_path, dir, path, sizeof(rgui->deferred_path)); + + const core_info_t *info = NULL; + size_t supported = 0; + if (rgui->core_info) + core_info_list_get_supported_cores(rgui->core_info, rgui->deferred_path, &info, &supported); + + if (supported == 1) // Can make a decision right now. + { + strlcpy(g_extern.fullpath, rgui->deferred_path, sizeof(g_extern.fullpath)); + strlcpy(g_settings.libretro, info->path, sizeof(g_settings.libretro)); + +#ifdef HAVE_DYNAMIC + menu_update_system_info(rgui, &rgui->load_no_rom); + g_extern.lifecycle_state |= (1ULL << MODE_LOAD_GAME); +#else + rarch_environment_cb(RETRO_ENVIRONMENT_SET_LIBRETRO_PATH, (void*)g_settings.libretro); + rarch_environment_cb(RETRO_ENVIRONMENT_EXEC, (void*)g_extern.fullpath); +#endif + + menu_flush_stack_type(rgui, RGUI_SETTINGS); + rgui->msg_force = true; + ret = -1; + } + else // Present a selection. + { + file_list_push(rgui->menu_stack, rgui->libretro_dir, RGUI_SETTINGS_DEFERRED_CORE, rgui->selection_ptr); + menu_clear_navigation(rgui); + rgui->need_refresh = true; + } + } + else + { + fill_pathname_join(g_extern.fullpath, dir, path, sizeof(g_extern.fullpath)); + g_extern.lifecycle_state |= (1ULL << MODE_LOAD_GAME); + + menu_flush_stack_type(rgui, RGUI_SETTINGS); + rgui->msg_force = true; + ret = -1; + } + } + } + break; + } + + case RGUI_ACTION_REFRESH: + menu_clear_navigation(rgui); + rgui->need_refresh = true; + break; + + case RGUI_ACTION_MESSAGE: + rgui->msg_force = true; + break; + + default: + break; + } + + + // refresh values in case the stack changed + file_list_get_last(rgui->menu_stack, &dir, &menu_type); + + if (rgui->need_refresh && (menu_type == RGUI_FILE_DIRECTORY || + menu_lakka_type_is(menu_type) == RGUI_SETTINGS_SHADER_OPTIONS || + menu_lakka_type_is(menu_type) == RGUI_FILE_DIRECTORY || + menu_type == RGUI_SETTINGS_OVERLAY_PRESET || + menu_type == RGUI_SETTINGS_VIDEO_SOFTFILTER || + menu_type == RGUI_SETTINGS_AUDIO_DSP_FILTER || + menu_type == RGUI_SETTINGS_DEFERRED_CORE || + menu_type == RGUI_SETTINGS_CORE || + menu_type == RGUI_SETTINGS_CONFIG || + menu_type == RGUI_SETTINGS_OPEN_HISTORY || + menu_type == RGUI_SETTINGS_DISK_APPEND)) + { + rgui->need_refresh = false; + menu_parse_and_resolve(rgui, menu_type); + } + + if (driver.menu_ctx && driver.menu_ctx->iterate) + driver.menu_ctx->iterate(rgui, action); + + if (driver.video_data && driver.menu_ctx && driver.menu_ctx->render) + driver.menu_ctx->render(rgui); + + return ret; +} + +static void menu_lakka_shader_manager_init(void *data) +{ + (void)data; + +#ifdef HAVE_SHADER_MANAGER + rgui_handle_t *rgui = (rgui_handle_t*)data; + memset(&rgui->shader, 0, sizeof(rgui->shader)); + config_file_t *conf = NULL; + + const char *config_path = NULL; + if (*g_extern.core_specific_config_path && g_settings.core_specific_config) + config_path = g_extern.core_specific_config_path; + else if (*g_extern.config_path) + config_path = g_extern.config_path; + + // In a multi-config setting, we can't have conflicts on rgui.cgp/rgui.glslp. + if (config_path) + { + fill_pathname_base(rgui->default_glslp, config_path, sizeof(rgui->default_glslp)); + path_remove_extension(rgui->default_glslp); + strlcat(rgui->default_glslp, ".glslp", sizeof(rgui->default_glslp)); + fill_pathname_base(rgui->default_cgp, config_path, sizeof(rgui->default_cgp)); + path_remove_extension(rgui->default_cgp); + strlcat(rgui->default_cgp, ".cgp", sizeof(rgui->default_cgp)); + } + else + { + strlcpy(rgui->default_glslp, "rgui.glslp", sizeof(rgui->default_glslp)); + strlcpy(rgui->default_cgp, "rgui.cgp", sizeof(rgui->default_cgp)); + } + + char cgp_path[PATH_MAX]; + + const char *ext = path_get_extension(g_settings.video.shader_path); + if (strcmp(ext, "glslp") == 0 || strcmp(ext, "cgp") == 0) + { + conf = config_file_new(g_settings.video.shader_path); + if (conf) + { + if (gfx_shader_read_conf_cgp(conf, &rgui->shader)) + gfx_shader_resolve_relative(&rgui->shader, g_settings.video.shader_path); + config_file_free(conf); + } + } + else if (strcmp(ext, "glsl") == 0 || strcmp(ext, "cg") == 0) + { + strlcpy(rgui->shader.pass[0].source.cg, g_settings.video.shader_path, + sizeof(rgui->shader.pass[0].source.cg)); + rgui->shader.passes = 1; + } + else + { + const char *shader_dir = *g_settings.video.shader_dir ? + g_settings.video.shader_dir : g_settings.system_directory; + + fill_pathname_join(cgp_path, shader_dir, "rgui.glslp", sizeof(cgp_path)); + conf = config_file_new(cgp_path); + + if (!conf) + { + fill_pathname_join(cgp_path, shader_dir, "rgui.cgp", sizeof(cgp_path)); + conf = config_file_new(cgp_path); + } + + if (conf) + { + if (gfx_shader_read_conf_cgp(conf, &rgui->shader)) + gfx_shader_resolve_relative(&rgui->shader, cgp_path); + config_file_free(conf); + } + } +#endif +} + +static void menu_lakka_shader_manager_set_preset(void *data, unsigned type, const char *path) +{ +#ifdef HAVE_SHADER_MANAGER + struct gfx_shader *shader = (struct gfx_shader*)data; + + RARCH_LOG("Setting RGUI shader: %s.\n", path ? path : "N/A (stock)"); + + if (video_set_shader_func((enum rarch_shader_type)type, path)) + { + // Makes sure that we use RGUI CGP shader on driver reinit. + // Only do this when the cgp actually works to avoid potential errors. + strlcpy(g_settings.video.shader_path, path ? path : "", + sizeof(g_settings.video.shader_path)); + g_settings.video.shader_enable = true; + + if (path && shader) + { + // Load stored CGP into RGUI menu on success. + // Used when a preset is directly loaded. + // No point in updating when the CGP was created from RGUI itself. + config_file_t *conf = config_file_new(path); + if (conf) + { + gfx_shader_read_conf_cgp(conf, shader); + gfx_shader_resolve_relative(shader, path); + config_file_free(conf); + } + + rgui->need_refresh = true; + } + } + else + { + RARCH_ERR("Setting RGUI CGP failed.\n"); + g_settings.video.shader_enable = false; + } +#endif +} + +static void menu_lakka_shader_manager_get_str(void *data, char *type_str, size_t type_str_size, unsigned type) +{ + (void)data; + (void)type_str; + (void)type_str_size; + (void)type; + +#ifdef HAVE_SHADER_MANAGER + struct gfx_shader *shader = (struct gfx_shader*)data; + if (type == RGUI_SETTINGS_SHADER_APPLY) + *type_str = '\0'; + else if (type == RGUI_SETTINGS_SHADER_PASSES) + snprintf(type_str, type_str_size, "%u", shader->passes); + else + { + unsigned pass = (type - RGUI_SETTINGS_SHADER_0) / 3; + switch ((type - RGUI_SETTINGS_SHADER_0) % 3) + { + case 0: + if (*shader->pass[pass].source.cg) + fill_pathname_base(type_str, + shader->pass[pass].source.cg, type_str_size); + else + strlcpy(type_str, "N/A", type_str_size); + break; + + case 1: + switch (shader->pass[pass].filter) + { + case RARCH_FILTER_LINEAR: + strlcpy(type_str, "Linear", type_str_size); + break; + + case RARCH_FILTER_NEAREST: + strlcpy(type_str, "Nearest", type_str_size); + break; + + case RARCH_FILTER_UNSPEC: + strlcpy(type_str, "Don't care", type_str_size); + break; + } + break; + + case 2: + { + unsigned scale = shader->pass[pass].fbo.scale_x; + if (!scale) + strlcpy(type_str, "Don't care", type_str_size); + else + snprintf(type_str, type_str_size, "%ux", scale); + break; + } + } + } +#endif +} + +static void menu_lakka_shader_manager_save_preset(void *data, const char *basename, bool apply) +{ +#ifdef HAVE_SHADER_MANAGER + char buffer[PATH_MAX]; + unsigned d, type; + rgui_handle_t *rgui; + + rgui = (rgui_handle_t*)data; + + if (driver.menu_ctx && driver.menu_ctx->backend && driver.menu_ctx->backend->shader_manager_get_type) + type = driver.menu_ctx->backend->shader_manager_get_type(&rgui->shader); + else + type = RARCH_SHADER_NONE; + + if (type == RARCH_SHADER_NONE) + return; + + const char *conf_path = NULL; + + if (basename) + { + strlcpy(buffer, basename, sizeof(buffer)); + // Append extension automatically as appropriate. + if (!strstr(basename, ".cgp") && !strstr(basename, ".glslp")) + { + if (type == RARCH_SHADER_GLSL) + strlcat(buffer, ".glslp", sizeof(buffer)); + else if (type == RARCH_SHADER_CG) + strlcat(buffer, ".cgp", sizeof(buffer)); + } + conf_path = buffer; + } + else + conf_path = type == RARCH_SHADER_GLSL ? rgui->default_glslp : rgui->default_cgp; + + char config_directory[PATH_MAX]; + if (*g_extern.config_path) + fill_pathname_basedir(config_directory, g_extern.config_path, sizeof(config_directory)); + else + *config_directory = '\0'; + + char cgp_path[PATH_MAX]; + const char *dirs[] = { + g_settings.video.shader_dir, + g_settings.rgui_config_directory, + config_directory, + }; + + config_file_t *conf = config_file_new(NULL); + if (!conf) + return; + gfx_shader_write_conf_cgp(conf, &rgui->shader); + + bool ret = false; + + for (d = 0; d < ARRAY_SIZE(dirs); d++) + { + if (!*dirs[d]) + continue; + + fill_pathname_join(cgp_path, dirs[d], conf_path, sizeof(cgp_path)); + if (config_file_write(conf, cgp_path)) + { + RARCH_LOG("Saved shader preset to %s.\n", cgp_path); + if (apply) + { + if (driver.menu_ctx && driver.menu_ctx->backend && driver.menu_ctx->backend->shader_manager_set_preset) + driver.menu_ctx->backend->shader_manager_set_preset(NULL, type, cgp_path); + } + ret = true; + break; + } + else + RARCH_LOG("Failed writing shader preset to %s.\n", cgp_path); + } + + config_file_free(conf); + if (!ret) + RARCH_ERR("Failed to save shader preset. Make sure config directory and/or shader dir are writable.\n"); +#endif +} + +static unsigned menu_lakka_shader_manager_get_type(void *data) +{ + unsigned i, type; + (void)data; + + i = 0; + type = 0; + + (void)i; + +#ifdef HAVE_SHADER_MANAGER + const struct gfx_shader *shader = (const struct gfx_shader*)data; + + // All shader types must be the same, or we cannot use it. + type = RARCH_SHADER_NONE; + + for (i = 0; i < shader->passes; i++) + { + enum rarch_shader_type pass_type = gfx_shader_parse_type(shader->pass[i].source.cg, + RARCH_SHADER_NONE); + + switch (pass_type) + { + case RARCH_SHADER_CG: + case RARCH_SHADER_GLSL: + if (type == RARCH_SHADER_NONE) + type = pass_type; + else if (type != pass_type) + return RARCH_SHADER_NONE; + break; + + default: + return RARCH_SHADER_NONE; + } + } +#endif + + return type; +} + +static int menu_lakka_shader_manager_setting_toggle(void *data, unsigned setting, unsigned action) +{ + (void)data; + (void)setting; + (void)action; + +#ifdef HAVE_SHADER_MANAGER + unsigned dist_shader, dist_filter, dist_scale; + rgui_handle_t *rgui; + + rgui = (rgui_handle_t*)data; + dist_shader = setting - RGUI_SETTINGS_SHADER_0; + dist_filter = setting - RGUI_SETTINGS_SHADER_0_FILTER; + dist_scale = setting - RGUI_SETTINGS_SHADER_0_SCALE; + + if (setting == RGUI_SETTINGS_SHADER_FILTER) + { + switch (action) + { + case RGUI_ACTION_START: + g_settings.video.smooth = true; + break; + + case RGUI_ACTION_LEFT: + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + g_settings.video.smooth = !g_settings.video.smooth; + break; + + default: + break; + } + } + else if ((setting == RGUI_SETTINGS_SHADER_APPLY || setting == RGUI_SETTINGS_SHADER_PASSES) && + (driver.menu_ctx && driver.menu_ctx->backend && driver.menu_ctx->backend->setting_set)) + driver.menu_ctx->backend->setting_set(rgui, setting, action); + else if ((dist_shader % 3) == 0 || setting == RGUI_SETTINGS_SHADER_PRESET) + { + dist_shader /= 3; + struct gfx_shader_pass *pass = setting == RGUI_SETTINGS_SHADER_PRESET ? + &rgui->shader.pass[dist_shader] : NULL; + switch (action) + { + case RGUI_ACTION_OK: + file_list_push(rgui->menu_stack, g_settings.video.shader_dir, setting, rgui->selection_ptr); + menu_clear_navigation(rgui); + rgui->need_refresh = true; + break; + + case RGUI_ACTION_START: + if (pass) + *pass->source.cg = '\0'; + break; + + default: + break; + } + } + else if ((dist_filter % 3) == 0) + { + dist_filter /= 3; + struct gfx_shader_pass *pass = &rgui->shader.pass[dist_filter]; + switch (action) + { + case RGUI_ACTION_START: + rgui->shader.pass[dist_filter].filter = RARCH_FILTER_UNSPEC; + break; + + case RGUI_ACTION_LEFT: + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + { + unsigned delta = action == RGUI_ACTION_LEFT ? 2 : 1; + pass->filter = (enum gfx_filter_type)((pass->filter + delta) % 3); + break; + } + + default: + break; + } + } + else if ((dist_scale % 3) == 0) + { + dist_scale /= 3; + struct gfx_shader_pass *pass = &rgui->shader.pass[dist_scale]; + switch (action) + { + case RGUI_ACTION_START: + pass->fbo.scale_x = pass->fbo.scale_y = 0; + pass->fbo.valid = false; + break; + + case RGUI_ACTION_LEFT: + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + { + unsigned current_scale = pass->fbo.scale_x; + unsigned delta = action == RGUI_ACTION_LEFT ? 5 : 1; + current_scale = (current_scale + delta) % 6; + pass->fbo.valid = current_scale; + pass->fbo.scale_x = pass->fbo.scale_y = current_scale; + break; + } + + default: + break; + } + } +#endif + + return 0; +} + +static int menu_lakka_setting_toggle(void *data, unsigned setting, unsigned action, unsigned menu_type) +{ + rgui_handle_t *rgui = (rgui_handle_t*)data; +#ifdef HAVE_SHADER_MANAGER + if ((setting >= RGUI_SETTINGS_SHADER_FILTER) && (setting <= RGUI_SETTINGS_SHADER_LAST)) + { + if (driver.menu_ctx && driver.menu_ctx->backend && driver.menu_ctx->backend->shader_manager_setting_toggle) + return driver.menu_ctx->backend->shader_manager_setting_toggle(rgui, setting, action); + else + return 0; + } +#endif + if ((setting >= RGUI_SETTINGS_CORE_OPTION_START) && + (driver.menu_ctx && driver.menu_ctx->backend && driver.menu_ctx->backend->core_setting_toggle) + ) + return driver.menu_ctx->backend->core_setting_toggle(setting, action); + + if (driver.menu_ctx && driver.menu_ctx->backend && driver.menu_ctx->backend->setting_set) + return driver.menu_ctx->backend->setting_set(rgui, setting, action); + + return 0; +} + +static int menu_lakka_core_setting_toggle(unsigned setting, unsigned action) +{ + unsigned index; + index = setting - RGUI_SETTINGS_CORE_OPTION_START; + + switch (action) + { + case RGUI_ACTION_LEFT: + core_option_prev(g_extern.system.core_options, index); + break; + + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + core_option_next(g_extern.system.core_options, index); + break; + + case RGUI_ACTION_START: + core_option_set_default(g_extern.system.core_options, index); + break; + + default: + break; + } + + return 0; +} + +#ifdef GEKKO +#define MAX_GAMMA_SETTING 2 + +static unsigned rgui_gx_resolutions[GX_RESOLUTIONS_LAST][2] = { + { 512, 192 }, + { 598, 200 }, + { 640, 200 }, + { 384, 224 }, + { 448, 224 }, + { 480, 224 }, + { 512, 224 }, + { 576, 224 }, + { 608, 224 }, + { 640, 224 }, + { 340, 232 }, + { 512, 232 }, + { 512, 236 }, + { 336, 240 }, + { 384, 240 }, + { 512, 240 }, + { 530, 240 }, + { 640, 240 }, + { 512, 384 }, + { 598, 400 }, + { 640, 400 }, + { 384, 448 }, + { 448, 448 }, + { 480, 448 }, + { 512, 448 }, + { 576, 448 }, + { 608, 448 }, + { 640, 448 }, + { 340, 464 }, + { 512, 464 }, + { 512, 472 }, + { 384, 480 }, + { 512, 480 }, + { 530, 480 }, + { 640, 480 }, +}; + +static unsigned rgui_current_gx_resolution = GX_RESOLUTIONS_640_480; +#else +#define MAX_GAMMA_SETTING 1 +#endif + + +#ifdef HAVE_OSK +static bool osk_callback_enter_audio_device(void *data) +{ + if (g_extern.lifecycle_state & (1ULL << MODE_OSK_ENTRY_SUCCESS) + && driver.osk && driver.osk->get_text_buf) + { + RARCH_LOG("OSK - Applying input data.\n"); + char tmp_str[256]; + wchar_t *text_buf = (wchar_t*)driver.osk->get_text_buf(driver.osk_data); + int num = wcstombs(tmp_str, text_buf, sizeof(tmp_str)); + tmp_str[num] = 0; + strlcpy(g_settings.audio.device, tmp_str, sizeof(g_settings.audio.device)); + goto do_exit; + } + else if (g_extern.lifecycle_state & (1ULL << MODE_OSK_ENTRY_FAIL)) + goto do_exit; + + return false; + +do_exit: + g_extern.lifecycle_state &= ~((1ULL << MODE_OSK_ENTRY_SUCCESS) | + (1ULL << MODE_OSK_ENTRY_FAIL)); + return true; +} + +static bool osk_callback_enter_audio_device_init(void *data) +{ + if (!driver.osk) + return false; + + if (driver.osk->write_initial_msg) + driver.osk->write_initial_msg(driver.osk_data, L"192.168.1.1"); + if (driver.osk->write_msg) + driver.osk->write_msg(driver.osk_data, L"Enter Audio Device / IP address for audio driver."); + if (driver.osk->start) + driver.osk->start(driver.osk_data); + + return true; +} + +static bool osk_callback_enter_filename(void *data) +{ + if (!driver.osk) + return false; + + if (g_extern.lifecycle_state & (1ULL << MODE_OSK_ENTRY_SUCCESS)) + { + RARCH_LOG("OSK - Applying input data.\n"); + char tmp_str[256]; + char filepath[PATH_MAX]; + int num = wcstombs(tmp_str, driver.osk->get_text_buf(driver.osk_data), sizeof(tmp_str)); + tmp_str[num] = 0; + + fill_pathname_join(filepath, g_settings.video.shader_dir, tmp_str, sizeof(filepath)); + strlcat(filepath, ".cgp", sizeof(filepath)); + RARCH_LOG("[osk_callback_enter_filename]: filepath is: %s.\n", filepath); + config_file_t *conf = config_file_new(NULL); + if (!conf) + return false; + gfx_shader_write_conf_cgp(conf, &rgui->shader); + config_file_write(conf, filepath); + config_file_free(conf); + goto do_exit; + } + else if (g_extern.lifecycle_state & (1ULL << MODE_OSK_ENTRY_FAIL)) + goto do_exit; + + return false; +do_exit: + g_extern.lifecycle_state &= ~((1ULL << MODE_OSK_ENTRY_SUCCESS) | + (1ULL << MODE_OSK_ENTRY_FAIL)); + return true; +} + +static bool osk_callback_enter_filename_init(void *data) +{ + if (!driver.osk) + return false; + + if (driver.osk->write_initial_msg) + driver.osk->write_initial_msg(driver.osk_data, L"Save Preset"); + if (driver.osk->write_msg) + driver.osk->write_msg(driver.osk_data, L"Enter filename for preset."); + if (driver.osk->start) + driver.osk->start(driver.osk_data); + + return true; +} + +#endif + +#ifndef RARCH_DEFAULT_PORT +#define RARCH_DEFAULT_PORT 55435 +#endif + +static int menu_lakka_setting_set(void *data, unsigned setting, unsigned action) +{ + rgui_handle_t *rgui = (rgui_handle_t*)data; + unsigned port = rgui->current_pad; + + switch (setting) + { + case RGUI_START_SCREEN: + if (action == RGUI_ACTION_OK) + file_list_push(rgui->menu_stack, "", RGUI_START_SCREEN, 0); + break; + case RGUI_SETTINGS_REWIND_ENABLE: + if (action == RGUI_ACTION_OK || + action == RGUI_ACTION_LEFT || + action == RGUI_ACTION_RIGHT) + { + g_settings.rewind_enable = !g_settings.rewind_enable; + if (g_settings.rewind_enable) + rarch_init_rewind(); + else + rarch_deinit_rewind(); + } + else if (action == RGUI_ACTION_START) + { + g_settings.rewind_enable = false; + rarch_deinit_rewind(); + } + break; +#ifdef HAVE_SCREENSHOTS + case RGUI_SETTINGS_GPU_SCREENSHOT: + if (action == RGUI_ACTION_OK || + action == RGUI_ACTION_LEFT || + action == RGUI_ACTION_RIGHT) + g_settings.video.gpu_screenshot = !g_settings.video.gpu_screenshot; + else if (action == RGUI_ACTION_START) + g_settings.video.gpu_screenshot = true; + break; +#endif + case RGUI_SETTINGS_REWIND_GRANULARITY: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_RIGHT) + g_settings.rewind_granularity++; + else if (action == RGUI_ACTION_LEFT) + { + if (g_settings.rewind_granularity > 1) + g_settings.rewind_granularity--; + } + else if (action == RGUI_ACTION_START) + g_settings.rewind_granularity = 1; + break; + case RGUI_SETTINGS_CONFIG_SAVE_ON_EXIT: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_RIGHT + || action == RGUI_ACTION_LEFT) + g_extern.config_save_on_exit = !g_extern.config_save_on_exit; + else if (action == RGUI_ACTION_START) + g_extern.config_save_on_exit = true; + break; + case RGUI_SETTINGS_SAVESTATE_AUTO_SAVE: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_RIGHT + || action == RGUI_ACTION_LEFT) + g_settings.savestate_auto_save = !g_settings.savestate_auto_save; + else if (action == RGUI_ACTION_START) + g_settings.savestate_auto_save = false; + break; + case RGUI_SETTINGS_SAVESTATE_AUTO_LOAD: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_RIGHT + || action == RGUI_ACTION_LEFT) + g_settings.savestate_auto_load = !g_settings.savestate_auto_load; + else if (action == RGUI_ACTION_START) + g_settings.savestate_auto_load = true; + break; + case RGUI_SETTINGS_BLOCK_SRAM_OVERWRITE: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_RIGHT + || action == RGUI_ACTION_LEFT) + g_settings.block_sram_overwrite = !g_settings.block_sram_overwrite; + else if (action == RGUI_ACTION_START) + g_settings.block_sram_overwrite = false; + break; + case RGUI_SETTINGS_PER_CORE_CONFIG: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_RIGHT + || action == RGUI_ACTION_LEFT) + g_settings.core_specific_config = !g_settings.core_specific_config; + else if (action == RGUI_ACTION_START) + g_settings.core_specific_config = default_core_specific_config; + break; +#if defined(HAVE_THREADS) + case RGUI_SETTINGS_SRAM_AUTOSAVE: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_RIGHT) + { + rarch_deinit_autosave(); + g_settings.autosave_interval += 10; + if (g_settings.autosave_interval) + rarch_init_autosave(); + } + else if (action == RGUI_ACTION_LEFT) + { + if (g_settings.autosave_interval) + { + rarch_deinit_autosave(); + g_settings.autosave_interval -= min(10, g_settings.autosave_interval); + if (g_settings.autosave_interval) + rarch_init_autosave(); + } + } + else if (action == RGUI_ACTION_START) + { + rarch_deinit_autosave(); + g_settings.autosave_interval = 0; + } + break; +#endif + case RGUI_SETTINGS_SAVESTATE_SAVE: + case RGUI_SETTINGS_SAVESTATE_LOAD: + if (action == RGUI_ACTION_OK) + { + if (setting == RGUI_SETTINGS_SAVESTATE_SAVE) + rarch_save_state(); + else + { + // Disallow savestate load when we absoluetely cannot change game state. +#ifdef HAVE_BSV_MOVIE + if (g_extern.bsv.movie) + break; +#endif +#ifdef HAVE_NETPLAY + if (g_extern.netplay) + break; +#endif + rarch_load_state(); + } + g_extern.lifecycle_state |= (1ULL << MODE_GAME); + return -1; + } + else if (action == RGUI_ACTION_START) + g_extern.state_slot = 0; + else if (action == RGUI_ACTION_LEFT) + { + // Slot -1 is (auto) slot. + if (g_extern.state_slot >= 0) + g_extern.state_slot--; + } + else if (action == RGUI_ACTION_RIGHT) + g_extern.state_slot++; + break; +#ifdef HAVE_SCREENSHOTS + case RGUI_SETTINGS_SCREENSHOT: + if (action == RGUI_ACTION_OK) + rarch_take_screenshot(); + break; +#endif + case RGUI_SETTINGS_RESTART_GAME: + if (action == RGUI_ACTION_OK) + { + rarch_game_reset(); + g_extern.lifecycle_state |= (1ULL << MODE_GAME); + return -1; + } + break; + case RGUI_SETTINGS_AUDIO_MUTE: + if (action == RGUI_ACTION_START) + g_extern.audio_data.mute = false; + else + g_extern.audio_data.mute = !g_extern.audio_data.mute; + break; + case RGUI_SETTINGS_AUDIO_CONTROL_RATE_DELTA: + if (action == RGUI_ACTION_START) + { + g_settings.audio.rate_control_delta = rate_control_delta; + g_settings.audio.rate_control = rate_control; + } + else if (action == RGUI_ACTION_LEFT) + { + if (g_settings.audio.rate_control_delta > 0.0) + g_settings.audio.rate_control_delta -= 0.001; + + if (g_settings.audio.rate_control_delta < 0.0005) + { + g_settings.audio.rate_control = false; + g_settings.audio.rate_control_delta = 0.0; + } + else + g_settings.audio.rate_control = true; + } + else if (action == RGUI_ACTION_RIGHT) + { + if (g_settings.audio.rate_control_delta < 0.2) + g_settings.audio.rate_control_delta += 0.001; + g_settings.audio.rate_control = true; + } + break; + case RGUI_SETTINGS_AUDIO_VOLUME: + { + float db_delta = 0.0f; + if (action == RGUI_ACTION_START) + { + g_extern.audio_data.volume_db = 0.0f; + g_extern.audio_data.volume_gain = 1.0f; + } + else if (action == RGUI_ACTION_LEFT) + db_delta -= 1.0f; + else if (action == RGUI_ACTION_RIGHT) + db_delta += 1.0f; + + if (db_delta != 0.0f) + { + g_extern.audio_data.volume_db += db_delta; + g_extern.audio_data.volume_db = max(g_extern.audio_data.volume_db, -80.0f); + g_extern.audio_data.volume_db = min(g_extern.audio_data.volume_db, 12.0f); + g_extern.audio_data.volume_gain = db_to_gain(g_extern.audio_data.volume_db); + } + break; + } + case RGUI_SETTINGS_DEBUG_TEXT: + if (action == RGUI_ACTION_START) + g_settings.fps_show = false; + else if (action == RGUI_ACTION_LEFT || action == RGUI_ACTION_RIGHT) + g_settings.fps_show = !g_settings.fps_show; + break; + case RGUI_SETTINGS_DISK_INDEX: + { + const struct retro_disk_control_callback *control = &g_extern.system.disk_control; + + unsigned num_disks = control->get_num_images(); + unsigned current = control->get_image_index(); + + int step = 0; + if (action == RGUI_ACTION_RIGHT || action == RGUI_ACTION_OK) + step = 1; + else if (action == RGUI_ACTION_LEFT) + step = -1; + + if (step) + { + unsigned next_index = (current + num_disks + 1 + step) % (num_disks + 1); + rarch_disk_control_set_eject(true, false); + rarch_disk_control_set_index(next_index); + rarch_disk_control_set_eject(false, false); + } + + break; + } + case RGUI_SETTINGS_RESTART_EMULATOR: + if (action == RGUI_ACTION_OK) + { +#if defined(GEKKO) && defined(HW_RVL) + fill_pathname_join(g_extern.fullpath, default_paths.core_dir, SALAMANDER_FILE, + sizeof(g_extern.fullpath)); +#endif + g_extern.lifecycle_state &= ~(1ULL << MODE_GAME); + g_extern.lifecycle_state |= (1ULL << MODE_EXITSPAWN); + return -1; + } + break; + case RGUI_SETTINGS_RESUME_GAME: + if (action == RGUI_ACTION_OK) + { + g_extern.lifecycle_state |= (1ULL << MODE_GAME); + return -1; + } + break; + case RGUI_SETTINGS_QUIT_RARCH: + if (action == RGUI_ACTION_OK) + { + g_extern.lifecycle_state &= ~(1ULL << MODE_GAME); + return -1; + } + break; + case RGUI_SETTINGS_SAVE_CONFIG: + if (action == RGUI_ACTION_OK) + menu_save_new_config(); + break; +#ifdef HAVE_OVERLAY + case RGUI_SETTINGS_OVERLAY_PRESET: + switch (action) + { + case RGUI_ACTION_OK: + file_list_push(rgui->menu_stack, g_extern.overlay_dir, setting, rgui->selection_ptr); + menu_clear_navigation(rgui); + rgui->need_refresh = true; + break; + +#ifndef __QNX__ // FIXME: Why ifndef QNX? + case RGUI_ACTION_START: + if (driver.overlay) + input_overlay_free(driver.overlay); + driver.overlay = NULL; + *g_settings.input.overlay = '\0'; + break; +#endif + + default: + break; + } + break; +#endif + case RGUI_SETTINGS_VIDEO_SOFTFILTER: + switch (action) + { +#ifdef HAVE_FILTERS_BUILTIN + case RGUI_ACTION_LEFT: + if (g_settings.video.filter_idx > 0) + g_settings.video.filter_idx--; + break; + case RGUI_ACTION_RIGHT: + if ((g_settings.video.filter_idx + 1) != softfilter_get_last_idx()) + g_settings.video.filter_idx++; + break; +#endif + case RGUI_ACTION_OK: +#if defined(HAVE_FILTERS_BUILTIN) + rarch_set_fullscreen(g_settings.video.fullscreen); +#elif defined(HAVE_DYLIB) + file_list_push(rgui->menu_stack, g_settings.video.filter_dir, setting, rgui->selection_ptr); + menu_clear_navigation(rgui); +#endif + rgui->need_refresh = true; + break; + case RGUI_ACTION_START: +#if defined(HAVE_FILTERS_BUILTIN) + g_settings.video.filter_idx = 0; +#else + strlcpy(g_settings.video.filter_path, "", sizeof(g_settings.video.filter_path)); +#endif + rarch_set_fullscreen(g_settings.video.fullscreen); + break; + } + break; + case RGUI_SETTINGS_AUDIO_DSP_FILTER: + switch (action) + { +#ifdef HAVE_FILTERS_BUILTIN + case RGUI_ACTION_LEFT: + if (g_settings.audio.filter_idx > 0) + g_settings.audio.filter_idx--; + break; + case RGUI_ACTION_RIGHT: + if ((g_settings.audio.filter_idx + 1) != dspfilter_get_last_idx()) + g_settings.audio.filter_idx++; + break; +#endif + case RGUI_ACTION_OK: +#if defined(HAVE_FILTERS_BUILTIN) + rarch_deinit_dsp_filter(); + rarch_init_dsp_filter(); +#elif defined(HAVE_DYLIB) + file_list_push(rgui->menu_stack, g_settings.audio.filter_dir, setting, rgui->selection_ptr); + menu_clear_navigation(rgui); +#endif + rgui->need_refresh = true; + break; + case RGUI_ACTION_START: +#if defined(HAVE_FILTERS_BUILTIN) + g_settings.audio.filter_idx = 0; +#elif defined(HAVE_DYLIB) + strlcpy(g_settings.audio.dsp_plugin, "", sizeof(g_settings.audio.dsp_plugin)); +#endif + rarch_deinit_dsp_filter(); + rarch_init_dsp_filter(); + break; + } + break; +#ifdef HAVE_OVERLAY + case RGUI_SETTINGS_OVERLAY_OPACITY: + { + bool changed = true; + switch (action) + { + case RGUI_ACTION_LEFT: + g_settings.input.overlay_opacity -= 0.01f; + + if (g_settings.input.overlay_opacity < 0.0f) + g_settings.input.overlay_opacity = 0.0f; + break; + + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + g_settings.input.overlay_opacity += 0.01f; + + if (g_settings.input.overlay_opacity > 1.0f) + g_settings.input.overlay_opacity = 1.0f; + break; + + case RGUI_ACTION_START: + g_settings.input.overlay_opacity = 0.7f; + break; + + default: + changed = false; + break; + } + + if (changed && driver.overlay) + input_overlay_set_alpha_mod(driver.overlay, + g_settings.input.overlay_opacity); + break; + } + + case RGUI_SETTINGS_OVERLAY_SCALE: + { + bool changed = true; + switch (action) + { + case RGUI_ACTION_LEFT: + g_settings.input.overlay_scale -= 0.01f; + + if (g_settings.input.overlay_scale < 0.01f) // Avoid potential divide by zero. + g_settings.input.overlay_scale = 0.01f; + break; + + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + g_settings.input.overlay_scale += 0.01f; + + if (g_settings.input.overlay_scale > 2.0f) + g_settings.input.overlay_scale = 2.0f; + break; + + case RGUI_ACTION_START: + g_settings.input.overlay_scale = 1.0f; + break; + + default: + changed = false; + break; + } + + if (changed && driver.overlay) + input_overlay_set_scale_factor(driver.overlay, + g_settings.input.overlay_scale); + break; + } +#endif + // controllers + case RGUI_SETTINGS_BIND_PLAYER: + if (action == RGUI_ACTION_START) + rgui->current_pad = 0; + else if (action == RGUI_ACTION_LEFT) + { + if (rgui->current_pad != 0) + rgui->current_pad--; + } + else if (action == RGUI_ACTION_RIGHT) + { + if (rgui->current_pad < MAX_PLAYERS - 1) + rgui->current_pad++; + } +#ifdef HAVE_RGUI + if (port != rgui->current_pad) + rgui->need_refresh = true; +#endif + port = rgui->current_pad; + break; + case RGUI_SETTINGS_BIND_DEVICE: + // If set_keybinds is supported, we do it more fancy, and scroll through + // a list of supported devices directly. + if (driver.input->set_keybinds && driver.input->devices_size) + { + unsigned device_last = driver.input->devices_size(driver.input_data); + g_settings.input.device[port] += device_last; + if (action == RGUI_ACTION_START) + g_settings.input.device[port] = 0; + else if (action == RGUI_ACTION_LEFT) + g_settings.input.device[port]--; + else if (action == RGUI_ACTION_RIGHT) + g_settings.input.device[port]++; + + // device_last can be 0, avoid modulo. + if (g_settings.input.device[port] >= device_last) + g_settings.input.device[port] -= device_last; + // needs to be checked twice, in case we go right past the end of the list + if (g_settings.input.device[port] >= device_last) + g_settings.input.device[port] -= device_last; + + unsigned keybind_action = (1ULL << KEYBINDS_ACTION_SET_DEFAULT_BINDS); + + driver.input->set_keybinds(driver.input_data, g_settings.input.device[port], port, 0, + keybind_action); + } + else + { + // When only straight g_settings.input.joypad_map[] style + // mapping is supported. + int *p = &g_settings.input.joypad_map[port]; + if (action == RGUI_ACTION_START) + *p = port; + else if (action == RGUI_ACTION_LEFT) + (*p)--; + else if (action == RGUI_ACTION_RIGHT) + (*p)++; + + if (*p < -1) + *p = -1; + else if (*p >= MAX_PLAYERS) + *p = MAX_PLAYERS - 1; + } + break; + case RGUI_SETTINGS_BIND_ANALOG_MODE: + switch (action) + { + case RGUI_ACTION_START: + g_settings.input.analog_dpad_mode[port] = 0; + break; + + case RGUI_ACTION_OK: + case RGUI_ACTION_RIGHT: + g_settings.input.analog_dpad_mode[port] = (g_settings.input.analog_dpad_mode[port] + 1) % ANALOG_DPAD_LAST; + break; + + case RGUI_ACTION_LEFT: + g_settings.input.analog_dpad_mode[port] = (g_settings.input.analog_dpad_mode[port] + ANALOG_DPAD_LAST - 1) % ANALOG_DPAD_LAST; + break; + + default: + break; + } + break; + case RGUI_SETTINGS_INPUT_AXIS_THRESHOLD: + switch (action) + { + case RGUI_ACTION_START: + g_settings.input.axis_threshold = 0.5; + break; + + case RGUI_ACTION_OK: + case RGUI_ACTION_RIGHT: + g_settings.input.axis_threshold += 0.01; + break; + case RGUI_ACTION_LEFT: + g_settings.input.axis_threshold -= 0.01; + break; + + default: + break; + } + g_settings.input.axis_threshold = max(min(g_settings.input.axis_threshold, 0.95f), 0.05f); + break; + case RGUI_SETTINGS_BIND_DEVICE_TYPE: + { + unsigned current_device, current_index, i; + unsigned types = 0; + unsigned devices[128]; + + devices[types++] = RETRO_DEVICE_NONE; + devices[types++] = RETRO_DEVICE_JOYPAD; + // Only push RETRO_DEVICE_ANALOG as default if we use an older core which doesn't use SET_CONTROLLER_INFO. + if (!g_extern.system.num_ports) + devices[types++] = RETRO_DEVICE_ANALOG; + + const struct retro_controller_info *desc = port < g_extern.system.num_ports ? &g_extern.system.ports[port] : NULL; + if (desc) + { + for (i = 0; i < desc->num_types; i++) + { + unsigned id = desc->types[i].id; + if (types < ARRAY_SIZE(devices) && id != RETRO_DEVICE_NONE && id != RETRO_DEVICE_JOYPAD) + devices[types++] = id; + } + } + + current_device = g_settings.input.libretro_device[port]; + current_index = 0; + for (i = 0; i < types; i++) + { + if (current_device == devices[i]) + { + current_index = i; + break; + } + } + + bool updated = true; + switch (action) + { + case RGUI_ACTION_START: + current_device = RETRO_DEVICE_JOYPAD; + break; + + case RGUI_ACTION_LEFT: + current_device = devices[(current_index + types - 1) % types]; + break; + + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + current_device = devices[(current_index + 1) % types]; + break; + + default: + updated = false; + } + + if (updated) + { + g_settings.input.libretro_device[port] = current_device; + pretro_set_controller_port_device(port, current_device); + } + + break; + } + case RGUI_SETTINGS_DEVICE_AUTODETECT_ENABLE: + if (action == RGUI_ACTION_OK) + g_settings.input.autodetect_enable = !g_settings.input.autodetect_enable; + break; + case RGUI_SETTINGS_CUSTOM_BIND_MODE: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_LEFT || action == RGUI_ACTION_RIGHT) + rgui->bind_mode_keyboard = !rgui->bind_mode_keyboard; + break; + case RGUI_SETTINGS_CUSTOM_BIND_ALL: + if (action == RGUI_ACTION_OK) + { + if (rgui->bind_mode_keyboard) + { + rgui->binds.target = &g_settings.input.binds[port][0]; + rgui->binds.begin = RGUI_SETTINGS_BIND_BEGIN; + rgui->binds.last = RGUI_SETTINGS_BIND_LAST; + file_list_push(rgui->menu_stack, "", RGUI_SETTINGS_CUSTOM_BIND_KEYBOARD, rgui->selection_ptr); + rgui->binds.timeout_end = rarch_get_time_usec() + RGUI_KEYBOARD_BIND_TIMEOUT_SECONDS * 1000000; + input_keyboard_wait_keys(rgui, menu_custom_bind_keyboard_cb); + } + else + { + rgui->binds.target = &g_settings.input.binds[port][0]; + rgui->binds.begin = RGUI_SETTINGS_BIND_BEGIN; + rgui->binds.last = RGUI_SETTINGS_BIND_LAST; + file_list_push(rgui->menu_stack, "", RGUI_SETTINGS_CUSTOM_BIND, rgui->selection_ptr); + menu_poll_bind_get_rested_axes(&rgui->binds); + menu_poll_bind_state(&rgui->binds); + } + } + break; + case RGUI_SETTINGS_CUSTOM_BIND_DEFAULT_ALL: + if (action == RGUI_ACTION_OK) + { + unsigned i; + struct retro_keybind *target = &g_settings.input.binds[port][0]; + const struct retro_keybind *def_binds = port ? retro_keybinds_rest : retro_keybinds_1; + rgui->binds.begin = RGUI_SETTINGS_BIND_BEGIN; + rgui->binds.last = RGUI_SETTINGS_BIND_LAST; + for (i = RGUI_SETTINGS_BIND_BEGIN; i <= RGUI_SETTINGS_BIND_LAST; i++, target++) + { + if (rgui->bind_mode_keyboard) + target->key = def_binds[i - RGUI_SETTINGS_BIND_BEGIN].key; + else + { + target->joykey = NO_BTN; + target->joyaxis = AXIS_NONE; + } + } + } + break; + case RGUI_SETTINGS_BIND_UP: + case RGUI_SETTINGS_BIND_DOWN: + case RGUI_SETTINGS_BIND_LEFT: + case RGUI_SETTINGS_BIND_RIGHT: + case RGUI_SETTINGS_BIND_A: + case RGUI_SETTINGS_BIND_B: + case RGUI_SETTINGS_BIND_X: + case RGUI_SETTINGS_BIND_Y: + case RGUI_SETTINGS_BIND_START: + case RGUI_SETTINGS_BIND_SELECT: + case RGUI_SETTINGS_BIND_L: + case RGUI_SETTINGS_BIND_R: + case RGUI_SETTINGS_BIND_L2: + case RGUI_SETTINGS_BIND_R2: + case RGUI_SETTINGS_BIND_L3: + case RGUI_SETTINGS_BIND_R3: + case RGUI_SETTINGS_BIND_TURBO_ENABLE: + case RGUI_SETTINGS_BIND_ANALOG_LEFT_X_PLUS: + case RGUI_SETTINGS_BIND_ANALOG_LEFT_X_MINUS: + case RGUI_SETTINGS_BIND_ANALOG_LEFT_Y_PLUS: + case RGUI_SETTINGS_BIND_ANALOG_LEFT_Y_MINUS: + case RGUI_SETTINGS_BIND_ANALOG_RIGHT_X_PLUS: + case RGUI_SETTINGS_BIND_ANALOG_RIGHT_X_MINUS: + case RGUI_SETTINGS_BIND_ANALOG_RIGHT_Y_PLUS: + case RGUI_SETTINGS_BIND_ANALOG_RIGHT_Y_MINUS: + case RGUI_SETTINGS_BIND_FAST_FORWARD_KEY: + case RGUI_SETTINGS_BIND_FAST_FORWARD_HOLD_KEY: + case RGUI_SETTINGS_BIND_LOAD_STATE_KEY: + case RGUI_SETTINGS_BIND_SAVE_STATE_KEY: + case RGUI_SETTINGS_BIND_FULLSCREEN_TOGGLE_KEY: + case RGUI_SETTINGS_BIND_QUIT_KEY: + case RGUI_SETTINGS_BIND_STATE_SLOT_PLUS: + case RGUI_SETTINGS_BIND_STATE_SLOT_MINUS: + case RGUI_SETTINGS_BIND_REWIND: + case RGUI_SETTINGS_BIND_MOVIE_RECORD_TOGGLE: + case RGUI_SETTINGS_BIND_PAUSE_TOGGLE: + case RGUI_SETTINGS_BIND_FRAMEADVANCE: + case RGUI_SETTINGS_BIND_RESET: + case RGUI_SETTINGS_BIND_SHADER_NEXT: + case RGUI_SETTINGS_BIND_SHADER_PREV: + case RGUI_SETTINGS_BIND_CHEAT_INDEX_PLUS: + case RGUI_SETTINGS_BIND_CHEAT_INDEX_MINUS: + case RGUI_SETTINGS_BIND_CHEAT_TOGGLE: + case RGUI_SETTINGS_BIND_SCREENSHOT: + case RGUI_SETTINGS_BIND_DSP_CONFIG: + case RGUI_SETTINGS_BIND_MUTE: + case RGUI_SETTINGS_BIND_NETPLAY_FLIP: + case RGUI_SETTINGS_BIND_SLOWMOTION: + case RGUI_SETTINGS_BIND_ENABLE_HOTKEY: + case RGUI_SETTINGS_BIND_VOLUME_UP: + case RGUI_SETTINGS_BIND_VOLUME_DOWN: + case RGUI_SETTINGS_BIND_OVERLAY_NEXT: + case RGUI_SETTINGS_BIND_DISK_EJECT_TOGGLE: + case RGUI_SETTINGS_BIND_DISK_NEXT: + case RGUI_SETTINGS_BIND_GRAB_MOUSE_TOGGLE: + case RGUI_SETTINGS_BIND_MENU_TOGGLE: + if (driver.input->set_keybinds && !driver.input->get_joypad_driver) + { + unsigned keybind_action = KEYBINDS_ACTION_NONE; + + if (action == RGUI_ACTION_START) + keybind_action = (1ULL << KEYBINDS_ACTION_SET_DEFAULT_BIND); + + // FIXME: The array indices here look totally wrong ... Fixed it so it looks kind of sane for now. + if (keybind_action != KEYBINDS_ACTION_NONE) + driver.input->set_keybinds(driver.input_data, g_settings.input.device[port], port, + setting - RGUI_SETTINGS_BIND_BEGIN, keybind_action); + } + else + { + struct retro_keybind *bind = &g_settings.input.binds[port][setting - RGUI_SETTINGS_BIND_BEGIN]; + if (action == RGUI_ACTION_OK) + { + rgui->binds.begin = setting; + rgui->binds.last = setting; + rgui->binds.target = bind; + rgui->binds.player = port; + file_list_push(rgui->menu_stack, "", + rgui->bind_mode_keyboard ? RGUI_SETTINGS_CUSTOM_BIND_KEYBOARD : RGUI_SETTINGS_CUSTOM_BIND, rgui->selection_ptr); + + if (rgui->bind_mode_keyboard) + { + rgui->binds.timeout_end = rarch_get_time_usec() + RGUI_KEYBOARD_BIND_TIMEOUT_SECONDS * 1000000; + input_keyboard_wait_keys(rgui, menu_custom_bind_keyboard_cb); + } + else + { + menu_poll_bind_get_rested_axes(&rgui->binds); + menu_poll_bind_state(&rgui->binds); + } + } + else if (action == RGUI_ACTION_START) + { + if (rgui->bind_mode_keyboard) + { + const struct retro_keybind *def_binds = port ? retro_keybinds_rest : retro_keybinds_1; + bind->key = def_binds[setting - RGUI_SETTINGS_BIND_BEGIN].key; + } + else + { + bind->joykey = NO_BTN; + bind->joyaxis = AXIS_NONE; + } + } + } + break; + case RGUI_BROWSER_DIR_PATH: + if (action == RGUI_ACTION_START) + *g_settings.rgui_content_directory = '\0'; + break; +#ifdef HAVE_SCREENSHOTS + case RGUI_SCREENSHOT_DIR_PATH: + if (action == RGUI_ACTION_START) + *g_settings.screenshot_directory = '\0'; + break; +#endif + case RGUI_SAVEFILE_DIR_PATH: + if (action == RGUI_ACTION_START) + *g_extern.savefile_dir = '\0'; + break; +#ifdef HAVE_OVERLAY + case RGUI_OVERLAY_DIR_PATH: + if (action == RGUI_ACTION_START) + *g_extern.overlay_dir = '\0'; + break; +#endif + case RGUI_SAVESTATE_DIR_PATH: + if (action == RGUI_ACTION_START) + *g_extern.savestate_dir = '\0'; + break; + case RGUI_LIBRETRO_DIR_PATH: + if (action == RGUI_ACTION_START) + { + *rgui->libretro_dir = '\0'; + menu_init_core_info(rgui); + } + break; + case RGUI_LIBRETRO_INFO_DIR_PATH: + if (action == RGUI_ACTION_START) + { + *g_settings.libretro_info_path = '\0'; + menu_init_core_info(rgui); + } + break; + case RGUI_CONFIG_DIR_PATH: + if (action == RGUI_ACTION_START) + *g_settings.rgui_config_directory = '\0'; + break; + case RGUI_FILTER_DIR_PATH: + if (action == RGUI_ACTION_START) + *g_settings.video.filter_dir = '\0'; + break; + case RGUI_DSP_FILTER_DIR_PATH: + if (action == RGUI_ACTION_START) + *g_settings.audio.filter_dir = '\0'; + break; + case RGUI_SHADER_DIR_PATH: + if (action == RGUI_ACTION_START) + *g_settings.video.shader_dir = '\0'; + break; + case RGUI_SYSTEM_DIR_PATH: + if (action == RGUI_ACTION_START) + *g_settings.system_directory = '\0'; + break; + case RGUI_SETTINGS_VIDEO_ROTATION: + if (action == RGUI_ACTION_START) + { + g_settings.video.rotation = ORIENTATION_NORMAL; + video_set_rotation_func((g_settings.video.rotation + g_extern.system.rotation) % 4); + } + else if (action == RGUI_ACTION_LEFT) + { + if (g_settings.video.rotation > 0) + g_settings.video.rotation--; + video_set_rotation_func((g_settings.video.rotation + g_extern.system.rotation) % 4); + } + else if (action == RGUI_ACTION_RIGHT) + { + if (g_settings.video.rotation < LAST_ORIENTATION) + g_settings.video.rotation++; + video_set_rotation_func((g_settings.video.rotation + g_extern.system.rotation) % 4); + } + break; + + case RGUI_SETTINGS_VIDEO_FILTER: + if (action == RGUI_ACTION_START) + g_settings.video.smooth = video_smooth; + else + g_settings.video.smooth = !g_settings.video.smooth; + + if (driver.video_data && driver.video_poke && driver.video_poke->set_filtering) + driver.video_poke->set_filtering(driver.video_data, 1, g_settings.video.smooth); + break; + + case RGUI_SETTINGS_DRIVER_VIDEO: + if (action == RGUI_ACTION_LEFT) + find_prev_video_driver(); + else if (action == RGUI_ACTION_RIGHT) + find_next_video_driver(); + break; + case RGUI_SETTINGS_DRIVER_AUDIO: + if (action == RGUI_ACTION_LEFT) + find_prev_audio_driver(); + else if (action == RGUI_ACTION_RIGHT) + find_next_audio_driver(); + break; + case RGUI_SETTINGS_DRIVER_AUDIO_DEVICE: + if (action == RGUI_ACTION_OK) + { +#ifdef HAVE_OSK + if (g_settings.osk.enable) + { + g_extern.osk.cb_init = osk_callback_enter_audio_device_init; + g_extern.osk.cb_callback = osk_callback_enter_audio_device; + } + else +#endif + menu_key_start_line(rgui, "Audio Device Name / IP: ", audio_device_callback); + } + else if (action == RGUI_ACTION_START) + *g_settings.audio.device = '\0'; + break; + case RGUI_SETTINGS_DRIVER_AUDIO_RESAMPLER: + if (action == RGUI_ACTION_LEFT) + find_prev_resampler_driver(); + else if (action == RGUI_ACTION_RIGHT) + find_next_resampler_driver(); + break; + case RGUI_SETTINGS_DRIVER_INPUT: + if (action == RGUI_ACTION_LEFT) + find_prev_input_driver(); + else if (action == RGUI_ACTION_RIGHT) + find_next_input_driver(); + break; +#ifdef HAVE_CAMERA + case RGUI_SETTINGS_DRIVER_CAMERA: + if (action == RGUI_ACTION_LEFT) + find_prev_camera_driver(); + else if (action == RGUI_ACTION_RIGHT) + find_next_camera_driver(); + break; +#endif +#ifdef HAVE_LOCATION + case RGUI_SETTINGS_DRIVER_LOCATION: + if (action == RGUI_ACTION_LEFT) + find_prev_location_driver(); + else if (action == RGUI_ACTION_RIGHT) + find_next_location_driver(); + break; +#endif +#ifdef HAVE_MENU + case RGUI_SETTINGS_DRIVER_MENU: + if (action == RGUI_ACTION_LEFT) + find_prev_menu_driver(); + else if (action == RGUI_ACTION_RIGHT) + find_next_menu_driver(); + break; +#endif + case RGUI_SETTINGS_VIDEO_GAMMA: + if (action == RGUI_ACTION_START) + { + g_extern.console.screen.gamma_correction = 0; + if (driver.video_data && driver.video_poke && driver.video_poke->apply_state_changes) + driver.video_poke->apply_state_changes(driver.video_data); + } + else if (action == RGUI_ACTION_LEFT) + { + if (g_extern.console.screen.gamma_correction > 0) + { + g_extern.console.screen.gamma_correction--; + if (driver.video_data && driver.video_poke && driver.video_poke->apply_state_changes) + driver.video_poke->apply_state_changes(driver.video_data); + } + } + else if (action == RGUI_ACTION_RIGHT) + { + if (g_extern.console.screen.gamma_correction < MAX_GAMMA_SETTING) + { + g_extern.console.screen.gamma_correction++; + if (driver.video_data && driver.video_poke && driver.video_poke->apply_state_changes) + driver.video_poke->apply_state_changes(driver.video_data); + } + } + break; + + case RGUI_SETTINGS_VIDEO_INTEGER_SCALE: + if (action == RGUI_ACTION_START) + g_settings.video.scale_integer = scale_integer; + else if (action == RGUI_ACTION_LEFT || + action == RGUI_ACTION_RIGHT || + action == RGUI_ACTION_OK) + g_settings.video.scale_integer = !g_settings.video.scale_integer; + + if (driver.video_data && driver.video_poke && driver.video_poke->apply_state_changes) + driver.video_poke->apply_state_changes(driver.video_data); + break; + + case RGUI_SETTINGS_VIDEO_ASPECT_RATIO: + if (action == RGUI_ACTION_START) + g_settings.video.aspect_ratio_idx = aspect_ratio_idx; + else if (action == RGUI_ACTION_LEFT) + { + if (g_settings.video.aspect_ratio_idx > 0) + g_settings.video.aspect_ratio_idx--; + } + else if (action == RGUI_ACTION_RIGHT) + { + if (g_settings.video.aspect_ratio_idx < LAST_ASPECT_RATIO) + g_settings.video.aspect_ratio_idx++; + } + + if (driver.video_data && driver.video_poke && driver.video_poke->set_aspect_ratio) + driver.video_poke->set_aspect_ratio(driver.video_data, g_settings.video.aspect_ratio_idx); + break; + + case RGUI_SETTINGS_TOGGLE_FULLSCREEN: + if (action == RGUI_ACTION_OK) + rarch_set_fullscreen(!g_settings.video.fullscreen); + break; + +#if defined(GEKKO) + case RGUI_SETTINGS_VIDEO_RESOLUTION: + if (action == RGUI_ACTION_LEFT) + { + if (rgui_current_gx_resolution > 0) + { + rgui_current_gx_resolution--; + if (driver.video_data) + gx_set_video_mode(driver.video_data, rgui_gx_resolutions[rgui_current_gx_resolution][0], rgui_gx_resolutions[rgui_current_gx_resolution][1]); + } + } + else if (action == RGUI_ACTION_RIGHT) + { + if (rgui_current_gx_resolution < GX_RESOLUTIONS_LAST - 1) + { +#ifdef HW_RVL + if ((rgui_current_gx_resolution + 1) > GX_RESOLUTIONS_640_480) + if (CONF_GetVideo() != CONF_VIDEO_PAL) + return 0; +#endif + + rgui_current_gx_resolution++; + if (driver.video_data) + gx_set_video_mode(driver.video_data, rgui_gx_resolutions[rgui_current_gx_resolution][0], + rgui_gx_resolutions[rgui_current_gx_resolution][1]); + } + } + break; +#elif defined(__CELLOS_LV2__) + case RGUI_SETTINGS_VIDEO_RESOLUTION: + if (action == RGUI_ACTION_LEFT) + { + if (g_extern.console.screen.resolutions.current.idx) + { + g_extern.console.screen.resolutions.current.idx--; + g_extern.console.screen.resolutions.current.id = + g_extern.console.screen.resolutions.list[g_extern.console.screen.resolutions.current.idx]; + } + } + else if (action == RGUI_ACTION_RIGHT) + { + if (g_extern.console.screen.resolutions.current.idx + 1 < + g_extern.console.screen.resolutions.count) + { + g_extern.console.screen.resolutions.current.idx++; + g_extern.console.screen.resolutions.current.id = + g_extern.console.screen.resolutions.list[g_extern.console.screen.resolutions.current.idx]; + } + } + else if (action == RGUI_ACTION_OK) + { + if (g_extern.console.screen.resolutions.list[g_extern.console.screen.resolutions.current.idx] == CELL_VIDEO_OUT_RESOLUTION_576) + { + if (g_extern.console.screen.pal_enable) + g_extern.lifecycle_state |= (1ULL<< MODE_VIDEO_PAL_ENABLE); + } + else + { + g_extern.lifecycle_state &= ~(1ULL << MODE_VIDEO_PAL_ENABLE); + g_extern.lifecycle_state &= ~(1ULL << MODE_VIDEO_PAL_TEMPORAL_ENABLE); + } + + if (driver.video && driver.video->restart) + driver.video->restart(); + if (driver.menu_ctx && driver.menu_ctx->free_assets) + driver.menu_ctx->free_assets(rgui); + if (driver.menu_ctx && driver.menu_ctx->init_assets) + driver.menu_ctx->init_assets(rgui); + } + break; + case RGUI_SETTINGS_VIDEO_PAL60: + switch (action) + { + case RGUI_ACTION_LEFT: + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + if (g_extern.lifecycle_state & (1ULL << MODE_VIDEO_PAL_ENABLE)) + { + if (g_extern.lifecycle_state & (1ULL << MODE_VIDEO_PAL_TEMPORAL_ENABLE)) + g_extern.lifecycle_state &= ~(1ULL << MODE_VIDEO_PAL_TEMPORAL_ENABLE); + else + g_extern.lifecycle_state |= (1ULL << MODE_VIDEO_PAL_TEMPORAL_ENABLE); + + if (driver.video && driver.video->restart) + driver.video->restart(); + if (driver.menu_ctx && driver.menu_ctx->free_assets) + driver.menu_ctx->free_assets(rgui); + if (driver.menu_ctx && driver.menu_ctx->init_assets) + driver.menu_ctx->init_assets(rgui); + } + break; + case RGUI_ACTION_START: + if (g_extern.lifecycle_state & (1ULL << MODE_VIDEO_PAL_ENABLE)) + { + g_extern.lifecycle_state &= ~(1ULL << MODE_VIDEO_PAL_TEMPORAL_ENABLE); + + if (driver.video && driver.video->restart) + driver.video->restart(); + if (driver.menu_ctx && driver.menu_ctx->free_assets) + driver.menu_ctx->free_assets(rgui); + if (driver.menu_ctx && driver.menu_ctx->init_assets) + driver.menu_ctx->init_assets(rgui); + } + break; + } + break; +#endif +#ifdef HW_RVL + case RGUI_SETTINGS_VIDEO_SOFT_FILTER: + if (g_extern.lifecycle_state & (1ULL << MODE_VIDEO_SOFT_FILTER_ENABLE)) + g_extern.lifecycle_state &= ~(1ULL << MODE_VIDEO_SOFT_FILTER_ENABLE); + else + g_extern.lifecycle_state |= (1ULL << MODE_VIDEO_SOFT_FILTER_ENABLE); + + if (driver.video_data && driver.video_poke && driver.video_poke->apply_state_changes) + driver.video_poke->apply_state_changes(driver.video_data); + break; +#endif + + case RGUI_SETTINGS_VIDEO_VSYNC: + switch (action) + { + case RGUI_ACTION_START: + g_settings.video.vsync = true; + break; + + case RGUI_ACTION_LEFT: + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + g_settings.video.vsync = !g_settings.video.vsync; + break; + + default: + break; + } + break; + + case RGUI_SETTINGS_VIDEO_HARD_SYNC: + switch (action) + { + case RGUI_ACTION_START: + g_settings.video.hard_sync = false; + break; + + case RGUI_ACTION_LEFT: + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + g_settings.video.hard_sync = !g_settings.video.hard_sync; + break; + + default: + break; + } + break; + + case RGUI_SETTINGS_VIDEO_BLACK_FRAME_INSERTION: + switch (action) + { + case RGUI_ACTION_START: + g_settings.video.black_frame_insertion = false; + break; + + case RGUI_ACTION_LEFT: + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + g_settings.video.black_frame_insertion = !g_settings.video.black_frame_insertion; + break; + + default: + break; + } + break; + + case RGUI_SETTINGS_VIDEO_CROP_OVERSCAN: + switch (action) + { + case RGUI_ACTION_START: + g_settings.video.crop_overscan = true; + break; + + case RGUI_ACTION_LEFT: + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + g_settings.video.crop_overscan = !g_settings.video.crop_overscan; + break; + + default: + break; + } + break; + + case RGUI_SETTINGS_VIDEO_WINDOW_SCALE_X: + case RGUI_SETTINGS_VIDEO_WINDOW_SCALE_Y: + { + float *scale = setting == RGUI_SETTINGS_VIDEO_WINDOW_SCALE_X ? &g_settings.video.xscale : &g_settings.video.yscale; + float old_scale = *scale; + + switch (action) + { + case RGUI_ACTION_START: + *scale = 3.0f; + break; + + case RGUI_ACTION_LEFT: + *scale -= 1.0f; + break; + + case RGUI_ACTION_RIGHT: + *scale += 1.0f; + break; + + default: + break; + } + + *scale = roundf(*scale); + *scale = max(*scale, 1.0f); + + if (old_scale != *scale && !g_settings.video.fullscreen) + rarch_set_fullscreen(g_settings.video.fullscreen); // Reinit video driver. + + break; + } + +#ifdef HAVE_THREADS + case RGUI_SETTINGS_VIDEO_THREADED: + { + bool old = g_settings.video.threaded; + if (action == RGUI_ACTION_OK || + action == RGUI_ACTION_LEFT || + action == RGUI_ACTION_RIGHT) + g_settings.video.threaded = !g_settings.video.threaded; + else if (action == RGUI_ACTION_START) + g_settings.video.threaded = false; + + if (g_settings.video.threaded != old) + rarch_set_fullscreen(g_settings.video.fullscreen); // Reinit video driver. + break; + } +#endif + + case RGUI_SETTINGS_VIDEO_SWAP_INTERVAL: + { + unsigned old = g_settings.video.swap_interval; + switch (action) + { + case RGUI_ACTION_START: + g_settings.video.swap_interval = 1; + break; + + case RGUI_ACTION_LEFT: + g_settings.video.swap_interval--; + break; + + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + g_settings.video.swap_interval++; + break; + + default: + break; + } + + g_settings.video.swap_interval = min(g_settings.video.swap_interval, 4); + g_settings.video.swap_interval = max(g_settings.video.swap_interval, 1); + if (old != g_settings.video.swap_interval && driver.video && driver.video_data) + video_set_nonblock_state_func(false); // This will update the current swap interval. Since we're in RGUI now, always apply VSync. + + break; + } + + case RGUI_SETTINGS_VIDEO_HARD_SYNC_FRAMES: + switch (action) + { + case RGUI_ACTION_START: + g_settings.video.hard_sync_frames = 0; + break; + + case RGUI_ACTION_LEFT: + if (g_settings.video.hard_sync_frames > 0) + g_settings.video.hard_sync_frames--; + break; + + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + if (g_settings.video.hard_sync_frames < 3) + g_settings.video.hard_sync_frames++; + break; + + default: + break; + } + break; + + case RGUI_SETTINGS_VIDEO_MONITOR_INDEX: + switch (action) + { + case RGUI_ACTION_START: + g_settings.video.monitor_index = 0; + rarch_set_fullscreen(g_settings.video.fullscreen); + break; + + case RGUI_ACTION_OK: + case RGUI_ACTION_RIGHT: + g_settings.video.monitor_index++; + rarch_set_fullscreen(g_settings.video.fullscreen); + break; + + case RGUI_ACTION_LEFT: + if (g_settings.video.monitor_index) + { + g_settings.video.monitor_index--; + rarch_set_fullscreen(g_settings.video.fullscreen); + } + break; + + default: + break; + } + break; + + case RGUI_SETTINGS_VIDEO_REFRESH_RATE_AUTO: + switch (action) + { + case RGUI_ACTION_START: + g_extern.measure_data.frame_time_samples_count = 0; + break; + + case RGUI_ACTION_OK: + { + double refresh_rate = 0.0; + double deviation = 0.0; + unsigned sample_points = 0; + if (driver_monitor_fps_statistics(&refresh_rate, &deviation, &sample_points)) + { + driver_set_monitor_refresh_rate(refresh_rate); + // Incase refresh rate update forced non-block video. + video_set_nonblock_state_func(false); + } + break; + } + + default: + break; + } + break; +#ifdef HAVE_SHADER_MANAGER + case RGUI_SETTINGS_SHADER_PASSES: + switch (action) + { + case RGUI_ACTION_START: + rgui->shader.passes = 0; + break; + + case RGUI_ACTION_LEFT: + if (rgui->shader.passes) + { + rgui->shader.passes--; + rgui->need_refresh = true; + } + break; + + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + if (rgui->shader.passes < GFX_MAX_SHADERS) + { + rgui->shader.passes++; + rgui->need_refresh = true; + } + break; + + default: + break; + } + +#ifndef HAVE_RMENU + rgui->need_refresh = true; +#endif + break; + case RGUI_SETTINGS_SHADER_APPLY: + { + unsigned type = RARCH_SHADER_NONE; + + if (!driver.video || !driver.video->set_shader || action != RGUI_ACTION_OK) + return 0; + + RARCH_LOG("Applying shader ...\n"); + + if (driver.menu_ctx && driver.menu_ctx->backend && driver.menu_ctx->backend->shader_manager_get_type) + type = driver.menu_ctx->backend->shader_manager_get_type(&rgui->shader); + + if (rgui->shader.passes && type != RARCH_SHADER_NONE + && driver.menu_ctx && driver.menu_ctx->backend && + driver.menu_ctx->backend->shader_manager_save_preset) + driver.menu_ctx->backend->shader_manager_save_preset(rgui, NULL, true); + else + { + type = gfx_shader_parse_type("", DEFAULT_SHADER_TYPE); + if (type == RARCH_SHADER_NONE) + { +#if defined(HAVE_GLSL) + type = RARCH_SHADER_GLSL; +#elif defined(HAVE_CG) || defined(HAVE_HLSL) + type = RARCH_SHADER_CG; +#endif + } + if (driver.menu_ctx && driver.menu_ctx->backend && driver.menu_ctx->backend->shader_manager_set_preset) + driver.menu_ctx->backend->shader_manager_set_preset(NULL, type, NULL); + } + break; + } + case RGUI_SETTINGS_SHADER_PRESET_SAVE: + if (action == RGUI_ACTION_OK) + { +#ifdef HAVE_OSK + if (g_settings.osk.enable) + { + g_extern.osk.cb_init = osk_callback_enter_filename_init; + g_extern.osk.cb_callback = osk_callback_enter_filename; + } + else +#endif + menu_key_start_line(rgui, "Preset Filename: ", preset_filename_callback); + } + break; +#endif +#ifdef _XBOX1 + case RGUI_SETTINGS_FLICKER_FILTER: + switch (action) + { + case RGUI_ACTION_LEFT: + if (g_extern.console.screen.flicker_filter_index > 0) + g_extern.console.screen.flicker_filter_index--; + break; + case RGUI_ACTION_RIGHT: + if (g_extern.console.screen.flicker_filter_index < 5) + g_extern.console.screen.flicker_filter_index++; + break; + case RGUI_ACTION_START: + g_extern.console.screen.flicker_filter_index = 0; + break; + } + break; + case RGUI_SETTINGS_SOFT_DISPLAY_FILTER: + switch (action) + { + case RGUI_ACTION_LEFT: + case RGUI_ACTION_RIGHT: + case RGUI_ACTION_OK: + if (g_extern.lifecycle_state & (1ULL << MODE_VIDEO_SOFT_FILTER_ENABLE)) + g_extern.lifecycle_state &= ~(1ULL << MODE_VIDEO_SOFT_FILTER_ENABLE); + else + g_extern.lifecycle_state |= (1ULL << MODE_VIDEO_SOFT_FILTER_ENABLE); + break; + case RGUI_ACTION_START: + g_extern.lifecycle_state |= (1ULL << MODE_VIDEO_SOFT_FILTER_ENABLE); + break; + } + break; +#endif + case RGUI_SETTINGS_CUSTOM_BGM_CONTROL_ENABLE: + switch (action) + { + case RGUI_ACTION_OK: +#if (CELL_SDK_VERSION > 0x340000) + if (g_extern.lifecycle_state & (1ULL << MODE_AUDIO_CUSTOM_BGM_ENABLE)) + g_extern.lifecycle_state &= ~(1ULL << MODE_AUDIO_CUSTOM_BGM_ENABLE); + else + g_extern.lifecycle_state |= (1ULL << MODE_AUDIO_CUSTOM_BGM_ENABLE); + if (g_extern.lifecycle_state & (1ULL << MODE_AUDIO_CUSTOM_BGM_ENABLE)) + cellSysutilEnableBgmPlayback(); + else + cellSysutilDisableBgmPlayback(); + +#endif + break; + case RGUI_ACTION_START: +#if (CELL_SDK_VERSION > 0x340000) + g_extern.lifecycle_state |= (1ULL << MODE_AUDIO_CUSTOM_BGM_ENABLE); +#endif + break; + } + break; + case RGUI_SETTINGS_PAUSE_IF_WINDOW_FOCUS_LOST: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_LEFT || action == RGUI_ACTION_RIGHT) + g_settings.pause_nonactive = !g_settings.pause_nonactive; + else if (action == RGUI_ACTION_START) + g_settings.pause_nonactive = false; + break; + case RGUI_SETTINGS_WINDOW_COMPOSITING_ENABLE: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_LEFT || action == RGUI_ACTION_RIGHT) + { + g_settings.video.disable_composition = !g_settings.video.disable_composition; + rarch_set_fullscreen(g_settings.video.fullscreen); + } + else if (action == RGUI_ACTION_START) + { + g_settings.video.disable_composition = false; + rarch_set_fullscreen(g_settings.video.fullscreen); + } + break; +#ifdef HAVE_NETPLAY + case RGUI_SETTINGS_NETPLAY_ENABLE: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_LEFT || action == RGUI_ACTION_RIGHT) + { + g_extern.netplay_enable = !g_extern.netplay_enable; + /* TODO/FIXME - toggle netplay on/off */ + } + else if (action == RGUI_ACTION_START) + { + g_extern.netplay_enable = false; + /* TODO/FIXME - toggle netplay on/off */ + } + break; + case RGUI_SETTINGS_NETPLAY_HOST_IP_ADDRESS: + if (action == RGUI_ACTION_OK) + menu_key_start_line(rgui, "IP Address: ", netplay_ipaddress_callback); + else if (action == RGUI_ACTION_START) + *g_extern.netplay_server = '\0'; + break; + case RGUI_SETTINGS_NETPLAY_DELAY_FRAMES: + if (action == RGUI_ACTION_LEFT) + { + if (g_extern.netplay_sync_frames != 0) + g_extern.netplay_sync_frames--; + } + else if (action == RGUI_ACTION_RIGHT) + g_extern.netplay_sync_frames++; + else if (action == RGUI_ACTION_START) + g_extern.netplay_sync_frames = 0; + break; + case RGUI_SETTINGS_NETPLAY_TCP_UDP_PORT: + if (action == RGUI_ACTION_OK) + menu_key_start_line(rgui, "TCP/UDP Port: ", netplay_port_callback); + else if (action == RGUI_ACTION_START) + g_extern.netplay_port = RARCH_DEFAULT_PORT; + break; + case RGUI_SETTINGS_NETPLAY_NICKNAME: + if (action == RGUI_ACTION_OK) + menu_key_start_line(rgui, "Nickname: ", netplay_nickname_callback); + else if (action == RGUI_ACTION_START) + *g_extern.netplay_nick = '\0'; + break; + case RGUI_SETTINGS_NETPLAY_MODE: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_LEFT || action == RGUI_ACTION_RIGHT) + g_extern.netplay_is_client = !g_extern.netplay_is_client; + else if (action == RGUI_ACTION_START) + g_extern.netplay_is_client = false; + break; + case RGUI_SETTINGS_NETPLAY_SPECTATOR_MODE_ENABLE: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_LEFT || action == RGUI_ACTION_RIGHT) + g_extern.netplay_is_spectate = !g_extern.netplay_is_spectate; + else if (action == RGUI_ACTION_START) + g_extern.netplay_is_spectate = false; + break; +#endif +#ifdef HAVE_OSK + case RGUI_SETTINGS_ONSCREEN_KEYBOARD_ENABLE: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_LEFT || action == RGUI_ACTION_RIGHT) + g_settings.osk.enable = !g_settings.osk.enable; + else if (action == RGUI_ACTION_START) + g_settings.osk.enable = false; + break; +#endif +#ifdef HAVE_CAMERA + case RGUI_SETTINGS_PRIVACY_CAMERA_ALLOW: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_LEFT || action == RGUI_ACTION_RIGHT) + g_settings.camera.allow = !g_settings.camera.allow; + else if (action == RGUI_ACTION_START) + g_settings.camera.allow = false; + break; +#endif +#ifdef HAVE_LOCATION + case RGUI_SETTINGS_PRIVACY_LOCATION_ALLOW: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_LEFT || action == RGUI_ACTION_RIGHT) + g_settings.location.allow = !g_settings.location.allow; + else if (action == RGUI_ACTION_START) + g_settings.location.allow = false; + break; +#endif + case RGUI_SETTINGS_FONT_ENABLE: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_LEFT || action == RGUI_ACTION_RIGHT) + g_settings.video.font_enable = !g_settings.video.font_enable; + else if (action == RGUI_ACTION_START) + g_settings.video.font_enable = true; + break; + case RGUI_SETTINGS_FONT_SCALE: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_LEFT || action == RGUI_ACTION_RIGHT) + g_settings.video.font_scale = !g_settings.video.font_scale; + else if (action == RGUI_ACTION_START) + g_settings.video.font_scale = true; + break; + case RGUI_SETTINGS_FONT_SIZE: + if (action == RGUI_ACTION_LEFT) + g_settings.video.font_size -= 1.0f; + else if (action == RGUI_ACTION_RIGHT) + g_settings.video.font_size += 1.0f; + else if (action == RGUI_ACTION_START) + g_settings.video.font_size = font_size; + g_settings.video.font_size = roundf(max(g_settings.video.font_size, 1.0f)); + break; + case RGUI_SETTINGS_LOAD_DUMMY_ON_CORE_SHUTDOWN: + if (action == RGUI_ACTION_OK || action == RGUI_ACTION_LEFT || action == RGUI_ACTION_RIGHT) + g_settings.load_dummy_on_core_shutdown = !g_settings.load_dummy_on_core_shutdown; + else if (action == RGUI_ACTION_START) + g_settings.load_dummy_on_core_shutdown = load_dummy_on_core_shutdown; + break; + default: + break; + } + + return 0; +} + +static void menu_lakka_setting_set_label(char *type_str, size_t type_str_size, unsigned *w, unsigned type) +{ + switch (type) + { + case RGUI_SETTINGS_VIDEO_ROTATION: + strlcpy(type_str, rotation_lut[g_settings.video.rotation], + type_str_size); + break; + case RGUI_SETTINGS_VIDEO_SOFT_FILTER: + snprintf(type_str, type_str_size, + (g_extern.lifecycle_state & (1ULL << MODE_VIDEO_SOFT_FILTER_ENABLE)) ? "ON" : "OFF"); + break; + case RGUI_SETTINGS_VIDEO_FILTER: + if (g_settings.video.smooth) + strlcpy(type_str, "Bilinear filtering", type_str_size); + else + strlcpy(type_str, "Point filtering", type_str_size); + break; + case RGUI_SETTINGS_VIDEO_GAMMA: + snprintf(type_str, type_str_size, "%d", g_extern.console.screen.gamma_correction); + break; + case RGUI_SETTINGS_VIDEO_VSYNC: + strlcpy(type_str, g_settings.video.vsync ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_VIDEO_HARD_SYNC: + strlcpy(type_str, g_settings.video.hard_sync ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_VIDEO_BLACK_FRAME_INSERTION: + strlcpy(type_str, g_settings.video.black_frame_insertion ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_VIDEO_SWAP_INTERVAL: + snprintf(type_str, type_str_size, "%u", g_settings.video.swap_interval); + break; + case RGUI_SETTINGS_VIDEO_THREADED: + strlcpy(type_str, g_settings.video.threaded ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_VIDEO_WINDOW_SCALE_X: + snprintf(type_str, type_str_size, "%.1fx", g_settings.video.xscale); + break; + case RGUI_SETTINGS_VIDEO_WINDOW_SCALE_Y: + snprintf(type_str, type_str_size, "%.1fx", g_settings.video.yscale); + break; + case RGUI_SETTINGS_VIDEO_CROP_OVERSCAN: + strlcpy(type_str, g_settings.video.crop_overscan ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_VIDEO_HARD_SYNC_FRAMES: + snprintf(type_str, type_str_size, "%u", g_settings.video.hard_sync_frames); + break; + case RGUI_SETTINGS_DRIVER_VIDEO: + strlcpy(type_str, g_settings.video.driver, type_str_size); + break; + case RGUI_SETTINGS_DRIVER_AUDIO: + strlcpy(type_str, g_settings.audio.driver, type_str_size); + break; + case RGUI_SETTINGS_DRIVER_AUDIO_DEVICE: + strlcpy(type_str, g_settings.audio.device, type_str_size); + break; + case RGUI_SETTINGS_DRIVER_AUDIO_RESAMPLER: + strlcpy(type_str, g_settings.audio.resampler, type_str_size); + break; + case RGUI_SETTINGS_DRIVER_INPUT: + strlcpy(type_str, g_settings.input.driver, type_str_size); + break; +#ifdef HAVE_CAMERA + case RGUI_SETTINGS_DRIVER_CAMERA: + strlcpy(type_str, g_settings.camera.driver, type_str_size); + break; +#endif +#ifdef HAVE_LOCATION + case RGUI_SETTINGS_DRIVER_LOCATION: + strlcpy(type_str, g_settings.location.driver, type_str_size); + break; +#endif +#ifdef HAVE_MENU + case RGUI_SETTINGS_DRIVER_MENU: + strlcpy(type_str, g_settings.menu.driver, type_str_size); + break; +#endif + case RGUI_SETTINGS_VIDEO_MONITOR_INDEX: + if (g_settings.video.monitor_index) + snprintf(type_str, type_str_size, "%u", g_settings.video.monitor_index); + else + strlcpy(type_str, "0 (Auto)", type_str_size); + break; + case RGUI_SETTINGS_VIDEO_REFRESH_RATE_AUTO: + { + double refresh_rate = 0.0; + double deviation = 0.0; + unsigned sample_points = 0; + if (driver_monitor_fps_statistics(&refresh_rate, &deviation, &sample_points)) + snprintf(type_str, type_str_size, "%.3f Hz (%.1f%% dev, %u samples)", refresh_rate, 100.0 * deviation, sample_points); + else + strlcpy(type_str, "N/A", type_str_size); + break; + } + case RGUI_SETTINGS_VIDEO_INTEGER_SCALE: + strlcpy(type_str, g_settings.video.scale_integer ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_VIDEO_ASPECT_RATIO: + strlcpy(type_str, aspectratio_lut[g_settings.video.aspect_ratio_idx].name, type_str_size); + break; +#if defined(GEKKO) + case RGUI_SETTINGS_VIDEO_RESOLUTION: + strlcpy(type_str, gx_get_video_mode(), type_str_size); + break; +#elif defined(__CELLOS_LV2__) + case RGUI_SETTINGS_VIDEO_RESOLUTION: + { + unsigned width = gfx_ctx_get_resolution_width(g_extern.console.screen.resolutions.list[g_extern.console.screen.resolutions.current.idx]); + unsigned height = gfx_ctx_get_resolution_height(g_extern.console.screen.resolutions.list[g_extern.console.screen.resolutions.current.idx]); + snprintf(type_str, type_str_size, "%dx%d", width, height); + } + break; + case RGUI_SETTINGS_VIDEO_PAL60: + if (g_extern.lifecycle_state & (1ULL << MODE_VIDEO_PAL_TEMPORAL_ENABLE)) + strlcpy(type_str, "ON", type_str_size); + else + strlcpy(type_str, "OFF", type_str_size); + break; +#endif + case RGUI_FILE_PLAIN: + strlcpy(type_str, "(FILE)", type_str_size); + *w = 6; + break; + case RGUI_FILE_DIRECTORY: + strlcpy(type_str, "(DIR)", type_str_size); + *w = 5; + break; + case RGUI_SETTINGS_REWIND_ENABLE: + strlcpy(type_str, g_settings.rewind_enable ? "ON" : "OFF", type_str_size); + break; +#ifdef HAVE_SCREENSHOTS + case RGUI_SETTINGS_GPU_SCREENSHOT: + strlcpy(type_str, g_settings.video.gpu_screenshot ? "ON" : "OFF", type_str_size); + break; +#endif + case RGUI_SETTINGS_REWIND_GRANULARITY: + snprintf(type_str, type_str_size, "%u", g_settings.rewind_granularity); + break; + case RGUI_SETTINGS_CONFIG_SAVE_ON_EXIT: + strlcpy(type_str, g_extern.config_save_on_exit ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_SAVESTATE_AUTO_SAVE: + strlcpy(type_str, g_settings.savestate_auto_save ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_SAVESTATE_AUTO_LOAD: + strlcpy(type_str, g_settings.savestate_auto_load ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_BLOCK_SRAM_OVERWRITE: + strlcpy(type_str, g_settings.block_sram_overwrite ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_PER_CORE_CONFIG: + strlcpy(type_str, g_settings.core_specific_config ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_SRAM_AUTOSAVE: + if (g_settings.autosave_interval) + snprintf(type_str, type_str_size, "%u seconds", g_settings.autosave_interval); + else + strlcpy(type_str, "OFF", type_str_size); + break; + case RGUI_SETTINGS_SAVESTATE_SAVE: + case RGUI_SETTINGS_SAVESTATE_LOAD: + if (g_extern.state_slot < 0) + strlcpy(type_str, "-1 (auto)", type_str_size); + else + snprintf(type_str, type_str_size, "%d", g_extern.state_slot); + break; + case RGUI_SETTINGS_AUDIO_MUTE: + strlcpy(type_str, g_extern.audio_data.mute ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_AUDIO_CONTROL_RATE_DELTA: + snprintf(type_str, type_str_size, "%.3f", g_settings.audio.rate_control_delta); + break; + case RGUI_SETTINGS_DEBUG_TEXT: + snprintf(type_str, type_str_size, (g_settings.fps_show) ? "ON" : "OFF"); + break; + case RGUI_BROWSER_DIR_PATH: + strlcpy(type_str, *g_settings.rgui_content_directory ? g_settings.rgui_content_directory : "", type_str_size); + break; +#ifdef HAVE_SCREENSHOTS + case RGUI_SCREENSHOT_DIR_PATH: + strlcpy(type_str, *g_settings.screenshot_directory ? g_settings.screenshot_directory : "", type_str_size); + break; +#endif + case RGUI_SAVEFILE_DIR_PATH: + strlcpy(type_str, *g_extern.savefile_dir ? g_extern.savefile_dir : "", type_str_size); + break; +#ifdef HAVE_OVERLAY + case RGUI_OVERLAY_DIR_PATH: + strlcpy(type_str, *g_extern.overlay_dir ? g_extern.overlay_dir : "", type_str_size); + break; +#endif + case RGUI_SAVESTATE_DIR_PATH: + strlcpy(type_str, *g_extern.savestate_dir ? g_extern.savestate_dir : "", type_str_size); + break; + case RGUI_LIBRETRO_DIR_PATH: + strlcpy(type_str, *rgui->libretro_dir ? rgui->libretro_dir : "", type_str_size); + break; + case RGUI_LIBRETRO_INFO_DIR_PATH: + strlcpy(type_str, *g_settings.libretro_info_path ? g_settings.libretro_info_path : "", type_str_size); + break; + case RGUI_CONFIG_DIR_PATH: + strlcpy(type_str, *g_settings.rgui_config_directory ? g_settings.rgui_config_directory : "", type_str_size); + break; + case RGUI_FILTER_DIR_PATH: + strlcpy(type_str, *g_settings.video.filter_dir ? g_settings.video.filter_dir : "", type_str_size); + break; + case RGUI_DSP_FILTER_DIR_PATH: + strlcpy(type_str, *g_settings.audio.filter_dir ? g_settings.audio.filter_dir : "", type_str_size); + break; + case RGUI_SHADER_DIR_PATH: + strlcpy(type_str, *g_settings.video.shader_dir ? g_settings.video.shader_dir : "", type_str_size); + break; + case RGUI_SYSTEM_DIR_PATH: + strlcpy(type_str, *g_settings.system_directory ? g_settings.system_directory : "", type_str_size); + break; + case RGUI_SETTINGS_DISK_INDEX: + { + const struct retro_disk_control_callback *control = &g_extern.system.disk_control; + unsigned images = control->get_num_images(); + unsigned current = control->get_image_index(); + if (current >= images) + strlcpy(type_str, "No Disk", type_str_size); + else + snprintf(type_str, type_str_size, "%u", current + 1); + break; + } + case RGUI_SETTINGS_CONFIG: + if (*g_extern.config_path) + fill_pathname_base(type_str, g_extern.config_path, type_str_size); + else + strlcpy(type_str, "", type_str_size); + break; + case RGUI_SETTINGS_OPEN_FILEBROWSER: + case RGUI_SETTINGS_OPEN_FILEBROWSER_DEFERRED_CORE: + case RGUI_SETTINGS_OPEN_HISTORY: + case RGUI_SETTINGS_CORE_OPTIONS: + case RGUI_SETTINGS_CORE_INFO: + case RGUI_SETTINGS_CUSTOM_VIEWPORT: + case RGUI_SETTINGS_TOGGLE_FULLSCREEN: + case RGUI_SETTINGS_VIDEO_OPTIONS: + case RGUI_SETTINGS_FONT_OPTIONS: + case RGUI_SETTINGS_AUDIO_OPTIONS: + case RGUI_SETTINGS_DISK_OPTIONS: +#ifdef HAVE_SHADER_MANAGER + case RGUI_SETTINGS_SHADER_OPTIONS: + case RGUI_SETTINGS_SHADER_PRESET: +#endif + case RGUI_SETTINGS_GENERAL_OPTIONS: + case RGUI_SETTINGS_SHADER_PRESET_SAVE: + case RGUI_SETTINGS_CORE: + case RGUI_SETTINGS_DISK_APPEND: + case RGUI_SETTINGS_INPUT_OPTIONS: + case RGUI_SETTINGS_PATH_OPTIONS: + case RGUI_SETTINGS_OVERLAY_OPTIONS: + case RGUI_SETTINGS_NETPLAY_OPTIONS: + case RGUI_SETTINGS_PRIVACY_OPTIONS: + case RGUI_SETTINGS_OPTIONS: + case RGUI_SETTINGS_DRIVERS: + case RGUI_SETTINGS_CUSTOM_BIND_ALL: + case RGUI_SETTINGS_CUSTOM_BIND_DEFAULT_ALL: + strlcpy(type_str, "...", type_str_size); + break; + case RGUI_SETTINGS_VIDEO_SOFTFILTER: + { + const char *filter_name = rarch_softfilter_get_name(g_extern.filter.filter); + strlcpy(type_str, filter_name ? filter_name : "N/A", type_str_size); + } + break; + case RGUI_SETTINGS_AUDIO_DSP_FILTER: + { + const char *filter_name = rarch_dspfilter_get_name((void*)g_extern.audio_data.dsp_plugin); + strlcpy(type_str, filter_name ? filter_name : "N/A", type_str_size); + } + break; +#ifdef HAVE_OVERLAY + case RGUI_SETTINGS_OVERLAY_PRESET: + strlcpy(type_str, path_basename(g_settings.input.overlay), type_str_size); + break; + case RGUI_SETTINGS_OVERLAY_OPACITY: + snprintf(type_str, type_str_size, "%.2f", g_settings.input.overlay_opacity); + break; + case RGUI_SETTINGS_OVERLAY_SCALE: + snprintf(type_str, type_str_size, "%.2f", g_settings.input.overlay_scale); + break; +#endif + case RGUI_SETTINGS_BIND_PLAYER: + snprintf(type_str, type_str_size, "#%d", rgui->current_pad + 1); + break; + case RGUI_SETTINGS_BIND_DEVICE: + { + int map = g_settings.input.joypad_map[rgui->current_pad]; + if (map >= 0 && map < MAX_PLAYERS) + { + const char *device_name = g_settings.input.device_names[map]; + if (*device_name) + strlcpy(type_str, device_name, type_str_size); + else + snprintf(type_str, type_str_size, "N/A (port #%u)", map); + } + else + strlcpy(type_str, "Disabled", type_str_size); + } + break; + case RGUI_SETTINGS_BIND_ANALOG_MODE: + { + static const char *modes[] = { + "None", + "Left Analog", + "Right Analog", + "Dual Analog", + }; + + strlcpy(type_str, modes[g_settings.input.analog_dpad_mode[rgui->current_pad] % ANALOG_DPAD_LAST], type_str_size); + } + break; + case RGUI_SETTINGS_INPUT_AXIS_THRESHOLD: + snprintf(type_str, type_str_size, "%.3f", g_settings.input.axis_threshold); + break; + case RGUI_SETTINGS_BIND_DEVICE_TYPE: + { + const struct retro_controller_description *desc; + desc = NULL; + if (rgui->current_pad < g_extern.system.num_ports) + { + desc = libretro_find_controller_description(&g_extern.system.ports[rgui->current_pad], + g_settings.input.libretro_device[rgui->current_pad]); + } + + const char *name = desc ? desc->desc : NULL; + if (!name) // Find generic name. + { + switch (g_settings.input.libretro_device[rgui->current_pad]) + { + case RETRO_DEVICE_NONE: + name = "None"; + break; + case RETRO_DEVICE_JOYPAD: + name = "Joypad"; + break; + case RETRO_DEVICE_ANALOG: + name = "Joypad w/ Analog"; + break; + default: + name = "Unknown"; + break; + } + } + + strlcpy(type_str, name, type_str_size); + } + break; + case RGUI_SETTINGS_DEVICE_AUTODETECT_ENABLE: + strlcpy(type_str, g_settings.input.autodetect_enable ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_CUSTOM_BIND_MODE: + strlcpy(type_str, rgui->bind_mode_keyboard ? "Keyboard" : "Joypad", type_str_size); + break; + case RGUI_SETTINGS_BIND_UP: + case RGUI_SETTINGS_BIND_DOWN: + case RGUI_SETTINGS_BIND_LEFT: + case RGUI_SETTINGS_BIND_RIGHT: + case RGUI_SETTINGS_BIND_A: + case RGUI_SETTINGS_BIND_B: + case RGUI_SETTINGS_BIND_X: + case RGUI_SETTINGS_BIND_Y: + case RGUI_SETTINGS_BIND_START: + case RGUI_SETTINGS_BIND_SELECT: + case RGUI_SETTINGS_BIND_L: + case RGUI_SETTINGS_BIND_R: + case RGUI_SETTINGS_BIND_L2: + case RGUI_SETTINGS_BIND_R2: + case RGUI_SETTINGS_BIND_L3: + case RGUI_SETTINGS_BIND_R3: + case RGUI_SETTINGS_BIND_TURBO_ENABLE: + case RGUI_SETTINGS_BIND_ANALOG_LEFT_X_PLUS: + case RGUI_SETTINGS_BIND_ANALOG_LEFT_X_MINUS: + case RGUI_SETTINGS_BIND_ANALOG_LEFT_Y_PLUS: + case RGUI_SETTINGS_BIND_ANALOG_LEFT_Y_MINUS: + case RGUI_SETTINGS_BIND_ANALOG_RIGHT_X_PLUS: + case RGUI_SETTINGS_BIND_ANALOG_RIGHT_X_MINUS: + case RGUI_SETTINGS_BIND_ANALOG_RIGHT_Y_PLUS: + case RGUI_SETTINGS_BIND_ANALOG_RIGHT_Y_MINUS: + case RGUI_SETTINGS_BIND_FAST_FORWARD_KEY: + case RGUI_SETTINGS_BIND_FAST_FORWARD_HOLD_KEY: + case RGUI_SETTINGS_BIND_LOAD_STATE_KEY: + case RGUI_SETTINGS_BIND_SAVE_STATE_KEY: + case RGUI_SETTINGS_BIND_FULLSCREEN_TOGGLE_KEY: + case RGUI_SETTINGS_BIND_QUIT_KEY: + case RGUI_SETTINGS_BIND_STATE_SLOT_PLUS: + case RGUI_SETTINGS_BIND_STATE_SLOT_MINUS: + case RGUI_SETTINGS_BIND_REWIND: + case RGUI_SETTINGS_BIND_MOVIE_RECORD_TOGGLE: + case RGUI_SETTINGS_BIND_PAUSE_TOGGLE: + case RGUI_SETTINGS_BIND_FRAMEADVANCE: + case RGUI_SETTINGS_BIND_RESET: + case RGUI_SETTINGS_BIND_SHADER_NEXT: + case RGUI_SETTINGS_BIND_SHADER_PREV: + case RGUI_SETTINGS_BIND_CHEAT_INDEX_PLUS: + case RGUI_SETTINGS_BIND_CHEAT_INDEX_MINUS: + case RGUI_SETTINGS_BIND_CHEAT_TOGGLE: + case RGUI_SETTINGS_BIND_SCREENSHOT: + case RGUI_SETTINGS_BIND_DSP_CONFIG: + case RGUI_SETTINGS_BIND_MUTE: + case RGUI_SETTINGS_BIND_NETPLAY_FLIP: + case RGUI_SETTINGS_BIND_SLOWMOTION: + case RGUI_SETTINGS_BIND_ENABLE_HOTKEY: + case RGUI_SETTINGS_BIND_VOLUME_UP: + case RGUI_SETTINGS_BIND_VOLUME_DOWN: + case RGUI_SETTINGS_BIND_OVERLAY_NEXT: + case RGUI_SETTINGS_BIND_DISK_EJECT_TOGGLE: + case RGUI_SETTINGS_BIND_DISK_NEXT: + case RGUI_SETTINGS_BIND_GRAB_MOUSE_TOGGLE: + case RGUI_SETTINGS_BIND_MENU_TOGGLE: + input_get_bind_string(type_str, &g_settings.input.binds[rgui->current_pad][type - RGUI_SETTINGS_BIND_BEGIN], type_str_size); + break; + case RGUI_SETTINGS_AUDIO_DSP_EFFECT: +#ifdef RARCH_CONSOLE + strlcpy(type_str, (g_extern.console.sound.volume_level) ? "Loud" : "Normal", type_str_size); + break; +#endif + case RGUI_SETTINGS_AUDIO_VOLUME: + snprintf(type_str, type_str_size, "%.1f dB", g_extern.audio_data.volume_db); + break; +#ifdef _XBOX1 + case RGUI_SETTINGS_FLICKER_FILTER: + snprintf(type_str, type_str_size, "%d", g_extern.console.screen.flicker_filter_index); + break; + case RGUI_SETTINGS_SOFT_DISPLAY_FILTER: + snprintf(type_str, type_str_size, + (g_extern.lifecycle_state & (1ULL << MODE_VIDEO_SOFT_FILTER_ENABLE)) ? "ON" : "OFF"); + break; +#endif + case RGUI_SETTINGS_CUSTOM_BGM_CONTROL_ENABLE: + strlcpy(type_str, (g_extern.lifecycle_state & (1ULL << MODE_AUDIO_CUSTOM_BGM_ENABLE)) ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_PAUSE_IF_WINDOW_FOCUS_LOST: + strlcpy(type_str, g_settings.pause_nonactive ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_WINDOW_COMPOSITING_ENABLE: + strlcpy(type_str, g_settings.video.disable_composition ? "OFF" : "ON", type_str_size); + break; +#ifdef HAVE_NETPLAY + case RGUI_SETTINGS_NETPLAY_ENABLE: + strlcpy(type_str, g_extern.netplay_enable ? "ON" : "OFF", type_str_size); + break; + case RGUI_SETTINGS_NETPLAY_HOST_IP_ADDRESS: + strlcpy(type_str, g_extern.netplay_server, type_str_size); + break; + case RGUI_SETTINGS_NETPLAY_DELAY_FRAMES: + snprintf(type_str, type_str_size, "%d", g_extern.netplay_sync_frames); + break; + case RGUI_SETTINGS_NETPLAY_TCP_UDP_PORT: + snprintf(type_str, type_str_size, "%d", g_extern.netplay_port ? g_extern.netplay_port : RARCH_DEFAULT_PORT); + break; + case RGUI_SETTINGS_NETPLAY_NICKNAME: + snprintf(type_str, type_str_size, "%s", g_extern.netplay_nick); + break; + case RGUI_SETTINGS_NETPLAY_MODE: + snprintf(type_str, type_str_size, g_extern.netplay_is_client ? "Client" : "Server"); + break; + case RGUI_SETTINGS_NETPLAY_SPECTATOR_MODE_ENABLE: + snprintf(type_str, type_str_size, g_extern.netplay_is_spectate ? "ON" : "OFF"); + break; +#endif +#ifdef HAVE_CAMERA + case RGUI_SETTINGS_PRIVACY_CAMERA_ALLOW: + snprintf(type_str, type_str_size, g_settings.camera.allow ? "ON" : "OFF"); + break; +#endif +#ifdef HAVE_LOCATION + case RGUI_SETTINGS_PRIVACY_LOCATION_ALLOW: + snprintf(type_str, type_str_size, g_settings.location.allow ? "ON" : "OFF"); + break; +#endif +#ifdef HAVE_OSK + case RGUI_SETTINGS_ONSCREEN_KEYBOARD_ENABLE: + snprintf(type_str, type_str_size, g_settings.osk.enable ? "ON" : "OFF"); + break; +#endif + case RGUI_SETTINGS_FONT_ENABLE: + snprintf(type_str, type_str_size, g_settings.video.font_enable ? "ON" : "OFF"); + break; + case RGUI_SETTINGS_FONT_SCALE: + snprintf(type_str, type_str_size, g_settings.video.font_scale ? "ON" : "OFF"); + break; + case RGUI_SETTINGS_FONT_SIZE: + snprintf(type_str, type_str_size, "%.1f", g_settings.video.font_size); + break; + case RGUI_SETTINGS_LOAD_DUMMY_ON_CORE_SHUTDOWN: + snprintf(type_str, type_str_size, g_settings.load_dummy_on_core_shutdown ? "ON" : "OFF"); + break; + default: + *type_str = '\0'; + *w = 0; + break; + } +} + +const menu_ctx_driver_backend_t menu_ctx_backend_lakka = { + menu_lakka_entries_init, + menu_lakka_iterate, + menu_lakka_shader_manager_init, + menu_lakka_shader_manager_get_str, + menu_lakka_shader_manager_set_preset, + menu_lakka_shader_manager_save_preset, + menu_lakka_shader_manager_get_type, + menu_lakka_shader_manager_setting_toggle, + menu_lakka_type_is, + menu_lakka_core_setting_toggle, + menu_lakka_setting_toggle, + menu_lakka_setting_set, + menu_lakka_setting_set_label, + "menu_lakka", +}; diff --git a/frontend/menu/disp/lakka.c b/frontend/menu/disp/lakka.c index 93d3e301f9..bdd6382a10 100644 --- a/frontend/menu/disp/lakka.c +++ b/frontend/menu/disp/lakka.c @@ -39,240 +39,478 @@ #include "../../../screenshot.h" #include "../../../gfx/fonts/bitmap.h" -GLuint texid = 0; -struct texture_image *menu_texture; -static bool textures_inited =false; +#include "lakka.h" +#include "tween.h" +#include "png_texture_load.h" -// thanks to https://github.com/DavidEGrayson/ahrs-visualizer/blob/master/png_texture.cpp -static GLuint png_texture_load(void *data, int * width, int * height) -{ - struct texture_image *menu_texture = (struct texture_image*)data; - // Generate the OpenGL texture object - GLuint texture; - glGenTextures(1, &texture); - glBindTexture(GL_TEXTURE_2D, texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +#define HSPACING 300 +#define VSPACING 75 +#define C_ACTIVE_ZOOM 1.0 +#define C_PASSIVE_ZOOM 0.5 +#define I_ACTIVE_ZOOM 0.75 +#define I_PASSIVE_ZOOM 0.35 +#define DELAY 0.02 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, menu_texture->width, menu_texture->height, 0, GL_RGB, GL_UNSIGNED_BYTE, menu_texture->pixels); +const GLfloat background_color[] = { + 0.1, 0.74, 0.61, 1.00, + 0.1, 0.74, 0.61, 1.00, + 0.1, 0.74, 0.61, 1.00, + 0.1, 0.74, 0.61, 1.00, +}; - return texture; -} +menu_category* categories = NULL; -#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_HLSL) -#define HAVE_SHADER_MANAGER -#endif +int depth = 0; -static uint16_t menu_framebuf[400 * 240]; +GLuint settings_icon; +GLuint arrow_icon; +GLuint run_icon; +GLuint resume_icon; +GLuint savestate_icon; +GLuint loadstate_icon; +GLuint screenshot_icon; +GLuint reload_icon; -#define LAKKA_TERM_START_X 15 -#define LAKKA_TERM_START_Y 27 -#define LAKKA_TERM_WIDTH (((rgui->width - LAKKA_TERM_START_X - 15) / (FONT_WIDTH_STRIDE))) -#define LAKKA_TERM_HEIGHT (((rgui->height - LAKKA_TERM_START_Y - 15) / (FONT_HEIGHT_STRIDE)) - 1) +struct font_output_list run_label; +struct font_output_list resume_label; + +int num_categories = 0; + +int menu_active_category = 0; int dim = 192; -static void lakka_copy_glyph(uint8_t *glyph, const uint8_t *buf) +float all_categories_x = 0; + +rgui_handle_t *rgui; + +// Fonts +void *font; +const gl_font_renderer_t *font_ctx; +const font_renderer_driver_t *font_driver; +GLuint font_tex; +GLint max_font_size; +int font_tex_w, font_tex_h; +uint32_t *font_tex_buf; +char font_last_msg[256]; +int font_last_width, font_last_height; +GLfloat font_color[16]; +GLfloat font_color_dark[16]; +GLuint texture; + +// Move the categories left or right depending on the menu_active_category variable +void lakka_switch_categories() { - int y, x; - for (y = 0; y < FONT_HEIGHT; y++) + // translation + add_tween(DELAY, -menu_active_category * HSPACING, &all_categories_x, &inOutQuad); + + // alpha tweening + for (int i = 0; i < num_categories; i++) { - for (x = 0; x < FONT_WIDTH; x++) + float ca = (i == menu_active_category) ? 1.0 : 0.5; + float cz = (i == menu_active_category) ? C_ACTIVE_ZOOM : C_PASSIVE_ZOOM; + add_tween(DELAY, ca, &categories[i].alpha, &inOutQuad); + add_tween(DELAY, cz, &categories[i].zoom, &inOutQuad); + + for (int j = 0; j < categories[i].num_items; j++) { - uint32_t col = - ((uint32_t)buf[3 * (-y * 256 + x) + 0] << 0) | - ((uint32_t)buf[3 * (-y * 256 + x) + 1] << 8) | - ((uint32_t)buf[3 * (-y * 256 + x) + 2] << 16); - - uint8_t rem = 1 << ((x + y * FONT_WIDTH) & 7); - unsigned offset = (x + y * FONT_WIDTH) >> 3; - - if (col != 0xff) - glyph[offset] |= rem; + float ia = (i != menu_active_category ) ? 0 : + (j == categories[i].active_item) ? 1.0 : 0.5; + add_tween(DELAY, ia, &categories[i].items[j].alpha, &inOutQuad); } } } -static uint16_t gray_filler(unsigned x, unsigned y) +void lakka_switch_items() { - return 0x0008; -} - -static uint16_t green_filler(unsigned x, unsigned y) -{ - return 0xf008; -} - -static void fill_rect(uint16_t *buf, unsigned pitch, - unsigned x, unsigned y, - unsigned width, unsigned height, - uint16_t (*col)(unsigned x, unsigned y)) -{ - unsigned j, i; - for (j = y; j < y + height; j++) - for (i = x; i < x + width; i++) - buf[j * (pitch >> 1) + i] = col(i, j); -} - -static void blit_line(rgui_handle_t *rgui, - int x, int y, const char *message, bool green) -{ - int j, i; - while (*message) + for (int j = 0; j < categories[menu_active_category].num_items; j++) { - for (j = 0; j < FONT_HEIGHT; j++) - { - for (i = 0; i < FONT_WIDTH; i++) - { - uint8_t rem = 1 << ((i + j * FONT_WIDTH) & 7); - int offset = (i + j * FONT_WIDTH) >> 3; - bool col = (rgui->font[FONT_OFFSET((unsigned char)*message) + offset] & rem); + float ia = (j == categories[menu_active_category].active_item) ? 1.0 : 0.5; + float iz = (j == categories[menu_active_category].active_item) ? I_ACTIVE_ZOOM : I_PASSIVE_ZOOM; + float iy = (j == categories[menu_active_category].active_item) ? VSPACING*2.5 : + (j < categories[menu_active_category].active_item) ? VSPACING*(j-categories[menu_active_category].active_item - 1) : + VSPACING*(j-categories[menu_active_category].active_item + 3); - if (col) - { - rgui->frame_buf[(y + j) * (rgui->frame_buf_pitch >> 1) + (x + i)] = green ? -#ifdef GEKKO - (3 << 0) | (10 << 4) | (3 << 8) | (7 << 12) : 0x7FFF; -#else - (15 << 0) | (7 << 4) | (15 << 8) | (7 << 12) : 0xFFFF; -#endif + add_tween(DELAY, ia, &categories[menu_active_category].items[j].alpha, &inOutQuad); + add_tween(DELAY, iz, &categories[menu_active_category].items[j].zoom, &inOutQuad); + add_tween(DELAY, iy, &categories[menu_active_category].items[j].y, &inOutQuad); + } +} + +void lakka_switch_subitems () +{ + menu_item ai = categories[menu_active_category].items[categories[menu_active_category].active_item]; + + for (int k = 0; k < ai.num_subitems; k++) { + // Above items + if (k < ai.active_subitem) { + add_tween(DELAY, 0.5, &ai.subitems[k].alpha, &inOutQuad); + add_tween(DELAY, VSPACING*(k-ai.active_subitem + 2), &ai.subitems[k].y, &inOutQuad); + add_tween(DELAY, I_PASSIVE_ZOOM, &ai.subitems[k].zoom, &inOutQuad); + // Active item + } else if (k == ai.active_subitem) { + add_tween(DELAY, 1.0, &ai.subitems[k].alpha, &inOutQuad); + add_tween(DELAY, VSPACING*2.5, &ai.subitems[k].y, &inOutQuad); + add_tween(DELAY, I_ACTIVE_ZOOM, &ai.subitems[k].zoom, &inOutQuad); + // Under items + } else if (k > ai.active_subitem) { + add_tween(DELAY, 0.5, &ai.subitems[k].alpha, &inOutQuad); + add_tween(DELAY, VSPACING*(k-ai.active_subitem + 3), &ai.subitems[k].y, &inOutQuad); + add_tween(DELAY, I_PASSIVE_ZOOM, &ai.subitems[k].zoom, &inOutQuad); + } + } +} + +void lakka_reset_submenu() +{ + if (! (g_extern.main_is_init && !g_extern.libretro_dummy && strcmp(g_extern.fullpath, categories[menu_active_category].items[categories[menu_active_category].active_item].rom) == 0)) { // Keeps active submenu state (do we really want that?) + categories[menu_active_category].items[categories[menu_active_category].active_item].active_subitem = 0; + for (int i = 0; i < num_categories; i++) { + for (int j = 0; j < categories[i].num_items; j++) { + for (int k = 0; k < categories[i].items[j].num_subitems; k++) { + categories[i].items[j].subitems[k].alpha = 0; + categories[i].items[j].subitems[k].zoom = k == categories[i].items[j].active_subitem ? I_ACTIVE_ZOOM : I_PASSIVE_ZOOM; + categories[i].items[j].subitems[k].y = k == 0 ? VSPACING*2.5 : VSPACING*(3+k); } } } - - x += FONT_WIDTH_STRIDE; - message++; } } -static void init_font(rgui_handle_t *rgui, const uint8_t *font_bmp_buf) +void lakka_open_submenu () +{ + add_tween(DELAY, -HSPACING * (menu_active_category+1), &all_categories_x, &inOutQuad); + + // Reset contextual menu style + lakka_reset_submenu(); + + for (int i = 0; i < num_categories; i++) { + if (i == menu_active_category) { + add_tween(DELAY, 1.0, &categories[i].alpha, &inOutQuad); + for (int j = 0; j < categories[i].num_items; j++) { + if (j == categories[i].active_item) { + for (int k = 0; k < categories[i].items[j].num_subitems; k++) { + if (k == categories[i].items[j].active_subitem) { + add_tween(DELAY, 1.0, &categories[i].items[j].subitems[k].alpha, &inOutQuad); + add_tween(DELAY, I_ACTIVE_ZOOM, &categories[i].items[j].subitems[k].zoom, &inOutQuad); + } else { + add_tween(DELAY, 0.5, &categories[i].items[j].subitems[k].alpha, &inOutQuad); + add_tween(DELAY, I_PASSIVE_ZOOM, &categories[i].items[j].subitems[k].zoom, &inOutQuad); + } + } + } else { + add_tween(DELAY, 0, &categories[i].items[j].alpha, &inOutQuad); + } + } + } else { + add_tween(DELAY, 0, &categories[i].alpha, &inOutQuad); + } + } +} + +void lakka_close_submenu () +{ + add_tween(DELAY, -HSPACING * menu_active_category, &all_categories_x, &inOutQuad); + + for (int i = 0; i < num_categories; i++) { + if (i == menu_active_category) { + add_tween(DELAY, 1.0, &categories[i].alpha, &inOutQuad); + add_tween(DELAY, C_ACTIVE_ZOOM, &categories[i].zoom, &inOutQuad); + for (int j = 0; j < categories[i].num_items; j++) { + if (j == categories[i].active_item) { + add_tween(DELAY, 1.0, &categories[i].items[j].alpha, &inOutQuad); + for (int k = 0; k < categories[i].items[j].num_subitems; k++) { + add_tween(DELAY, 0, &categories[i].items[j].subitems[k].alpha, &inOutQuad); + } + } else { + add_tween(DELAY, 0.5, &categories[i].items[j].alpha, &inOutQuad); + } + } + } else { + add_tween(DELAY, 0.5, &categories[i].alpha, &inOutQuad); + add_tween(DELAY, C_PASSIVE_ZOOM, &categories[i].zoom, &inOutQuad); + for (int j = 0; j < categories[i].num_items; j++) { + add_tween(DELAY, 0, &categories[i].items[j].alpha, &inOutQuad); + } + } + } +} + +struct font_rect +{ + int x, y; + int width, height; + int pot_width, pot_height; +}; + +/* font rendering */ + +static bool init_font(void *data, const char *font_path, float font_size, unsigned win_width, unsigned win_height) +{ + size_t i, j; + (void)win_width; + (void)win_height; + + if (!g_settings.video.font_enable) + return false; + + (void)font_size; + gl_t *gl = (gl_t*)data; + + if (font_renderer_create_default(&font_driver, &font)) + { + glGenTextures(1, &font_tex); + glBindTexture(GL_TEXTURE_2D, font_tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, texture); + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_font_size); + } + else + { + RARCH_WARN("Couldn't init font renderer.\n"); + return false; + } + + for (i = 0; i < 4; i++) + { + font_color[4 * i + 0] = g_settings.video.msg_color_r; + font_color[4 * i + 1] = g_settings.video.msg_color_g; + font_color[4 * i + 2] = g_settings.video.msg_color_b; + font_color[4 * i + 3] = 1.0; + } + + for (i = 0; i < 4; i++) + { + for (j = 0; j < 3; j++) + font_color_dark[4 * i + j] = 0.3 * font_color[4 * i + j]; + font_color_dark[4 * i + 3] = 1.0; + } + + return true; +} + +static void calculate_msg_geometry(const struct font_output *head, struct font_rect *rect) +{ + int x_min = head->off_x; + int x_max = head->off_x + head->width+10; + int y_min = head->off_y; + int y_max = head->off_y + head->height; + + while ((head = head->next)) + { + int left = head->off_x; + int right = head->off_x + head->width; + int bottom = head->off_y; + int top = head->off_y + head->height; + + if (left < x_min) + x_min = left; + if (right > x_max) + x_max = right; + + if (bottom < y_min) + y_min = bottom; + if (top > y_max) + y_max = top; + } + + rect->x = x_min; + rect->y = y_min; + rect->width = x_max - x_min; + rect->height = y_max - y_min; +} + +static void adjust_power_of_two(gl_t *gl, struct font_rect *geom) +{ + // Some systems really hate NPOT textures. + geom->pot_width = next_pow2(geom->width); + geom->pot_height = next_pow2(geom->height); + + if (geom->pot_width > max_font_size) + geom->pot_width = max_font_size; + if (geom->pot_height > max_font_size) + geom->pot_height = max_font_size; + + if ((geom->pot_width > font_tex_w) || (geom->pot_height > font_tex_h)) + { + font_tex_buf = (uint32_t*)realloc(font_tex_buf, + geom->pot_width * geom->pot_height * sizeof(uint32_t)); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, geom->pot_width, geom->pot_height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + + font_tex_w = geom->pot_width; + font_tex_h = geom->pot_height; + } +} + +static void copy_glyph(const struct font_output *head, const struct font_rect *geom, uint32_t *buffer, unsigned width, unsigned height) +{ + int h, w; + // head has top-left oriented coords. + int x = head->off_x - geom->x; + int y = head->off_y - geom->y; + y = height - head->height - y - 1; + + const uint8_t *src = head->output; + int font_width = head->width + ((x < 0) ? x : 0); + int font_height = head->height + ((y < 0) ? y : 0); + + if (x < 0) + { + src += -x; + x = 0; + } + + if (y < 0) + { + src += -y * head->pitch; + y = 0; + } + + if (x + font_width > (int)width) + font_width = width - x; + + if (y + font_height > (int)height) + font_height = height - y; + + uint32_t *dst = buffer + y * width + x; + for (h = 0; h < font_height; h++, dst += width, src += head->pitch) + { + uint8_t *d = (uint8_t*)dst; + for (w = 0; w < font_width; w++) + { + *d++ = 0xff; + *d++ = 0xff; + *d++ = 0xff; + *d++ = src[w]; + } + } +} + +static void blit_fonts(gl_t *gl, const struct font_output *head, const struct font_rect *geom) +{ + memset(font_tex_buf, 0, font_tex_w * font_tex_h * sizeof(uint32_t)); + + while (head) + { + copy_glyph(head, geom, font_tex_buf, font_tex_w, font_tex_h); + head = head->next; + } + + glPixelStorei(GL_UNPACK_ALIGNMENT, 8); + glTexSubImage2D(GL_TEXTURE_2D, + 0, 0, 0, font_tex_w, font_tex_h, + GL_RGBA, GL_UNSIGNED_BYTE, font_tex_buf); +} + +static void calculate_font_coords(gl_t *gl, + GLfloat font_vertex[8], GLfloat font_vertex_dark[8], GLfloat font_tex_coords[8], GLfloat scale, GLfloat pos_x, GLfloat pos_y) { unsigned i; - uint8_t *font = (uint8_t *) calloc(1, FONT_OFFSET(256)); - rgui->alloc_font = true; - for (i = 0; i < 256; i++) + GLfloat scale_factor = scale; + + GLfloat lx = pos_x; + GLfloat hx = (GLfloat)font_last_width * scale_factor / gl->vp.width + lx; + GLfloat ly = pos_y; + GLfloat hy = (GLfloat)font_last_height * scale_factor / gl->vp.height + ly; + + font_vertex[0] = lx; + font_vertex[2] = hx; + font_vertex[4] = lx; + font_vertex[6] = hx; + font_vertex[1] = hy; + font_vertex[3] = hy; + font_vertex[5] = ly; + font_vertex[7] = ly; + + GLfloat shift_x = 2.0f / gl->vp.width; + GLfloat shift_y = 2.0f / gl->vp.height; + for (i = 0; i < 4; i++) { - unsigned y = i / 16; - unsigned x = i % 16; - lakka_copy_glyph(&font[FONT_OFFSET(i)], - font_bmp_buf + 54 + 3 * (256 * (255 - 16 * y) + 16 * x)); + font_vertex_dark[2 * i + 0] = font_vertex[2 * i + 0] - shift_x; + font_vertex_dark[2 * i + 1] = font_vertex[2 * i + 1] - shift_y; } - rgui->font = font; + lx = 0.0f; + hx = (GLfloat)font_last_width / font_tex_w; + ly = 1.0f - (GLfloat)font_last_height / font_tex_h; + hy = 1.0f; + + font_tex_coords[0] = lx; + font_tex_coords[2] = hx; + font_tex_coords[4] = lx; + font_tex_coords[6] = hx; + font_tex_coords[1] = ly; + font_tex_coords[3] = ly; + font_tex_coords[5] = hy; + font_tex_coords[7] = hy; } -static bool rguidisp_init_font(void *data) +static void lakka_draw_text(void *data, struct font_output_list out, float x, float y, float scale, float alpha) { - rgui_handle_t *rgui = (rgui_handle_t*)data; - - const uint8_t *font_bmp_buf = NULL; - const uint8_t *font_bin_buf = bitmap_bin; - bool ret = true; - - if (font_bmp_buf) - init_font(rgui, font_bmp_buf); - else if (font_bin_buf) - rgui->font = font_bin_buf; - else - ret = false; - - return ret; -} - -static void lakka_render_background(rgui_handle_t *rgui) -{ - fill_rect(rgui->frame_buf, rgui->frame_buf_pitch, - 0, 0, rgui->width, rgui->height, gray_filler); - - fill_rect(rgui->frame_buf, rgui->frame_buf_pitch, - 5, 5, rgui->width - 10, 5, green_filler); - - fill_rect(rgui->frame_buf, rgui->frame_buf_pitch, - 5, rgui->height - 10, rgui->width - 10, 5, green_filler); - - fill_rect(rgui->frame_buf, rgui->frame_buf_pitch, - 5, 5, 5, rgui->height - 10, green_filler); - - fill_rect(rgui->frame_buf, rgui->frame_buf_pitch, - rgui->width - 10, 5, 5, rgui->height - 10, green_filler); -} - -static void lakka_render_messagebox(void *data, const char *message) -{ - rgui_handle_t *rgui = (rgui_handle_t*)data; - size_t i; - - if (!message || !*message) + gl_t *gl = (gl_t*)data; + if (!font) return; - struct string_list *list = string_split(message, "\n"); - if (!list) - return; - if (list->elems == 0) + for (int i = 0; i < 4; i++) { - string_list_free(list); - return; + font_color[4 * i + 0] = 1.0; + font_color[4 * i + 1] = 1.0; + font_color[4 * i + 2] = 1.0; + font_color[4 * i + 3] = alpha; } - unsigned width = 0; - unsigned glyphs_width = 0; - for (i = 0; i < list->size; i++) - { - char *msg = list->elems[i].data; - unsigned msglen = strlen(msg); - if (msglen > LAKKA_TERM_WIDTH) - { - msg[LAKKA_TERM_WIDTH - 2] = '.'; - msg[LAKKA_TERM_WIDTH - 1] = '.'; - msg[LAKKA_TERM_WIDTH - 0] = '.'; - msg[LAKKA_TERM_WIDTH + 1] = '\0'; - msglen = LAKKA_TERM_WIDTH; - } + if (gl->shader) + gl->shader->use(gl, GL_SHADER_STOCK_BLEND); - unsigned line_width = msglen * FONT_WIDTH_STRIDE - 1 + 6 + 10; - width = max(width, line_width); - glyphs_width = max(glyphs_width, msglen); - } + gl_set_viewport(gl, gl->win_width, gl->win_height, true, false); - unsigned height = FONT_HEIGHT_STRIDE * list->size + 6 + 10; - int x = (rgui->width - width) / 2; - int y = (rgui->height - height) / 2; - - fill_rect(rgui->frame_buf, rgui->frame_buf_pitch, - x + 5, y + 5, width - 10, height - 10, gray_filler); + glEnable(GL_BLEND); - fill_rect(rgui->frame_buf, rgui->frame_buf_pitch, - x, y, width - 5, 5, green_filler); + GLfloat font_vertex[8]; + GLfloat font_vertex_dark[8]; + GLfloat font_tex_coords[8]; - fill_rect(rgui->frame_buf, rgui->frame_buf_pitch, - x + width - 5, y, 5, height - 5, green_filler); + glBindTexture(GL_TEXTURE_2D, font_tex); - fill_rect(rgui->frame_buf, rgui->frame_buf_pitch, - x + 5, y + height - 5, width - 5, 5, green_filler); + gl->coords.tex_coord = font_tex_coords; - fill_rect(rgui->frame_buf, rgui->frame_buf_pitch, - x, y + 5, 5, height - 5, green_filler); + struct font_output *head = out.head; - for (i = 0; i < list->size; i++) - { - const char *msg = list->elems[i].data; - int offset_x = FONT_WIDTH_STRIDE * (glyphs_width - strlen(msg)) / 2; - int offset_y = FONT_HEIGHT_STRIDE * i; - blit_line(rgui, x + 8 + offset_x, y + 8 + offset_y, msg, false); - } + struct font_rect geom; + calculate_msg_geometry(head, &geom); + adjust_power_of_two(gl, &geom); + blit_fonts(gl, head, &geom); - string_list_free(list); + //font_driver->free_output(font, &out); + + font_last_width = geom.width; + font_last_height = geom.height; + + calculate_font_coords(gl, font_vertex, font_vertex_dark, font_tex_coords, + scale, x / gl->win_width, (gl->win_height - y) / gl->win_height); + + gl->coords.vertex = font_vertex; + gl->coords.color = font_color; + gl_shader_set_coords(gl, &gl->coords, &gl->mvp); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Post - Go back to old rendering path. + gl->coords.vertex = gl->vertex_ptr; + gl->coords.tex_coord = gl->tex_coords; + gl->coords.color = gl->white_color_ptr; + glBindTexture(GL_TEXTURE_2D, texture); + + glDisable(GL_BLEND); + + struct gl_ortho ortho = {0, 1, 0, 1, -1, 1}; + gl_set_projection(gl, &ortho, true); } -const GLfloat background_color[] = { - 0, 200, 200, 0.75, - 0, 200, 200, 0.75, - 0, 200, 200, 0.75, - 0, 200, 200, 0.75, -}; - void lakka_draw_background(void *data) { gl_t *gl = (gl_t*)data; @@ -284,7 +522,7 @@ void lakka_draw_background(void *data) glBindTexture(GL_TEXTURE_2D, 0); if (gl->shader) - gl->shader->use(GL_SHADER_STOCK_BLEND); + gl->shader->use(gl, GL_SHADER_STOCK_BLEND); gl_shader_set_coords(gl, &gl->coords, &gl->mvp_no_rot); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); @@ -296,7 +534,7 @@ void lakka_draw_icon(void *data, GLuint texture, float x, float y, float alpha, { gl_t *gl = (gl_t*)data; - glViewport(x, gl->win_height - y, dim, dim); + glViewport(x, 900 - y, dim, dim); glEnable(GL_BLEND); @@ -307,13 +545,6 @@ void lakka_draw_icon(void *data, GLuint texture, float x, float y, float alpha, 1.0f, 1.0f, 1.0f, alpha, }; - static const GLfloat vertexes_flipped[] = { - 0, 1, - 1, 1, - 0, 0, - 1, 0 - }; - static const GLfloat vtest[] = { 0, 0, 1, 0, @@ -327,7 +558,7 @@ void lakka_draw_icon(void *data, GLuint texture, float x, float y, float alpha, glBindTexture(GL_TEXTURE_2D, texture); if (gl->shader) - gl->shader->use(GL_SHADER_STOCK_BLEND); + gl->shader->use(gl, GL_SHADER_STOCK_BLEND); math_matrix mymat; @@ -347,301 +578,143 @@ void lakka_draw_icon(void *data, GLuint texture, float x, float y, float alpha, gl->coords.vertex = gl->vertex_ptr; gl->coords.tex_coord = gl->tex_coords; gl->coords.color = gl->white_color_ptr; - - gl_set_viewport(gl, gl->win_width, gl->win_height, 0, 0); -} - -static void lakka_render(void *data) -{ - rgui_handle_t *rgui = (rgui_handle_t*)data; - - gl_t *gl = (gl_t*)driver.video_data; - - gl_set_viewport(gl, gl->win_width, gl->win_height, 0, 0); - - lakka_draw_background(gl); - - lakka_draw_icon(gl, texid, 0, 0, 1, 0, 1); - - gl_set_viewport(gl, gl->win_width, gl->win_height, false, false); - - if (rgui->need_refresh && - (g_extern.lifecycle_state & (1ULL << MODE_MENU)) - && !rgui->msg_force) - return; - - size_t begin = rgui->selection_ptr >= LAKKA_TERM_HEIGHT / 2 ? - rgui->selection_ptr - LAKKA_TERM_HEIGHT / 2 : 0; - size_t end = rgui->selection_ptr + LAKKA_TERM_HEIGHT <= rgui->selection_buf->size ? - rgui->selection_ptr + LAKKA_TERM_HEIGHT : rgui->selection_buf->size; - - // Do not scroll if all items are visible. - if (rgui->selection_buf->size <= LAKKA_TERM_HEIGHT) - begin = 0; - - if (end - begin > LAKKA_TERM_HEIGHT) - end = begin + LAKKA_TERM_HEIGHT; - - lakka_render_background(rgui); - - char title[256]; - const char *dir = NULL; - unsigned menu_type = 0; - unsigned menu_type_is = 0; - file_list_get_last(rgui->menu_stack, &dir, &menu_type); - - if (driver.menu_ctx && driver.menu_ctx->backend && driver.menu_ctx->backend->type_is) - menu_type_is = driver.menu_ctx->backend->type_is(menu_type); - - if (menu_type == RGUI_SETTINGS_CORE) - snprintf(title, sizeof(title), "CORE SELECTION %s", dir); - else if (menu_type == RGUI_SETTINGS_DEFERRED_CORE) - snprintf(title, sizeof(title), "DETECTED CORES %s", dir); - else if (menu_type == RGUI_SETTINGS_CONFIG) - snprintf(title, sizeof(title), "CONFIG %s", dir); - else if (menu_type == RGUI_SETTINGS_DISK_APPEND) - snprintf(title, sizeof(title), "DISK APPEND %s", dir); - else if (menu_type == RGUI_SETTINGS_VIDEO_OPTIONS) - strlcpy(title, "VIDEO OPTIONS", sizeof(title)); - else if (menu_type == RGUI_SETTINGS_DRIVERS) - strlcpy(title, "DRIVER OPTIONS", sizeof(title)); -#ifdef HAVE_SHADER_MANAGER - else if (menu_type == RGUI_SETTINGS_SHADER_OPTIONS) - strlcpy(title, "SHADER OPTIONS", sizeof(title)); -#endif - else if (menu_type == RGUI_SETTINGS_AUDIO_OPTIONS) - strlcpy(title, "AUDIO OPTIONS", sizeof(title)); - else if (menu_type == RGUI_SETTINGS_DISK_OPTIONS) - strlcpy(title, "DISK OPTIONS", sizeof(title)); - else if (menu_type == RGUI_SETTINGS_CORE_OPTIONS) - strlcpy(title, "CORE OPTIONS", sizeof(title)); -#ifdef HAVE_SHADER_MANAGER - else if (menu_type_is == RGUI_SETTINGS_SHADER_OPTIONS) - snprintf(title, sizeof(title), "SHADER %s", dir); -#endif - else if ((menu_type == RGUI_SETTINGS_INPUT_OPTIONS) || - (menu_type == RGUI_SETTINGS_PATH_OPTIONS) || - (menu_type == RGUI_SETTINGS_OPTIONS) || - (menu_type == RGUI_SETTINGS_CUSTOM_VIEWPORT || menu_type == RGUI_SETTINGS_CUSTOM_VIEWPORT_2) || - menu_type == RGUI_SETTINGS_CUSTOM_BIND || - menu_type == RGUI_START_SCREEN || - menu_type == RGUI_SETTINGS) - snprintf(title, sizeof(title), "MENU %s", dir); - else if (menu_type == RGUI_SETTINGS_OPEN_HISTORY) - strlcpy(title, "LOAD HISTORY", sizeof(title)); -#ifdef HAVE_OVERLAY - else if (menu_type == RGUI_SETTINGS_OVERLAY_PRESET) - snprintf(title, sizeof(title), "OVERLAY %s", dir); -#endif - else if (menu_type == RGUI_BROWSER_DIR_PATH) - snprintf(title, sizeof(title), "BROWSER DIR %s", dir); -#ifdef HAVE_SCREENSHOTS - else if (menu_type == RGUI_SCREENSHOT_DIR_PATH) - snprintf(title, sizeof(title), "SCREENSHOT DIR %s", dir); -#endif - else if (menu_type == RGUI_SHADER_DIR_PATH) - snprintf(title, sizeof(title), "SHADER DIR %s", dir); - else if (menu_type == RGUI_FILTER_DIR_PATH) - snprintf(title, sizeof(title), "FILTER DIR %s", dir); - else if (menu_type == RGUI_SAVESTATE_DIR_PATH) - snprintf(title, sizeof(title), "SAVESTATE DIR %s", dir); -#ifdef HAVE_DYNAMIC - else if (menu_type == RGUI_LIBRETRO_DIR_PATH) - snprintf(title, sizeof(title), "LIBRETRO DIR %s", dir); -#endif - else if (menu_type == RGUI_CONFIG_DIR_PATH) - snprintf(title, sizeof(title), "CONFIG DIR %s", dir); - else if (menu_type == RGUI_SAVEFILE_DIR_PATH) - snprintf(title, sizeof(title), "SAVEFILE DIR %s", dir); -#ifdef HAVE_OVERLAY - else if (menu_type == RGUI_OVERLAY_DIR_PATH) - snprintf(title, sizeof(title), "OVERLAY DIR %s", dir); -#endif - else if (menu_type == RGUI_SYSTEM_DIR_PATH) - snprintf(title, sizeof(title), "SYSTEM DIR %s", dir); - else - { - if (rgui->defer_core) - snprintf(title, sizeof(title), "CONTENT %s", dir); - else - { - const char *core_name = rgui->info.library_name; - if (!core_name) - core_name = g_extern.system.info.library_name; - if (!core_name) - core_name = "No Core"; - snprintf(title, sizeof(title), "CONTENT (%s) %s", core_name, dir); - } - } - - char title_buf[256]; - menu_ticker_line(title_buf, LAKKA_TERM_WIDTH - 3, g_extern.frame_count / 15, title, true); - blit_line(rgui, LAKKA_TERM_START_X + 15, 15, title_buf, true); - - char title_msg[64]; - const char *core_name = rgui->info.library_name; - if (!core_name) - core_name = g_extern.system.info.library_name; - if (!core_name) - core_name = "No Core"; - - const char *core_version = rgui->info.library_version; - if (!core_version) - core_version = g_extern.system.info.library_version; - if (!core_version) - core_version = ""; - - snprintf(title_msg, sizeof(title_msg), "%s - %s %s", PACKAGE_VERSION, core_name, core_version); - blit_line(rgui, LAKKA_TERM_START_X + 15, (LAKKA_TERM_HEIGHT * FONT_HEIGHT_STRIDE) + LAKKA_TERM_START_Y + 2, title_msg, true); - - unsigned x, y; - size_t i; - - x = LAKKA_TERM_START_X; - y = LAKKA_TERM_START_Y; - - for (i = begin; i < end; i++, y += FONT_HEIGHT_STRIDE) - { - const char *path = 0; - unsigned type = 0; - file_list_get_at_offset(rgui->selection_buf, i, &path, &type); - char message[256]; - char type_str[256]; - - unsigned w = 19; - if (menu_type == RGUI_SETTINGS_INPUT_OPTIONS || menu_type == RGUI_SETTINGS_CUSTOM_BIND) - w = 21; - else if (menu_type == RGUI_SETTINGS_PATH_OPTIONS) - w = 24; - -#ifdef HAVE_SHADER_MANAGER - if (type >= RGUI_SETTINGS_SHADER_FILTER && - type <= RGUI_SETTINGS_SHADER_LAST) - { - // HACK. Work around that we're using the menu_type as dir type to propagate state correctly. - if ((menu_type_is == RGUI_SETTINGS_SHADER_OPTIONS) - && (menu_type_is == RGUI_SETTINGS_SHADER_OPTIONS)) - { - type = RGUI_FILE_DIRECTORY; - strlcpy(type_str, "(DIR)", sizeof(type_str)); - w = 5; - } - else if (type == RGUI_SETTINGS_SHADER_OPTIONS || type == RGUI_SETTINGS_SHADER_PRESET) - strlcpy(type_str, "...", sizeof(type_str)); - else if (type == RGUI_SETTINGS_SHADER_FILTER) - snprintf(type_str, sizeof(type_str), "%s", - g_settings.video.smooth ? "Linear" : "Nearest"); - else - shader_manager_get_str(&rgui->shader, type_str, sizeof(type_str), type); - } - else -#endif - // Pretty-print libretro cores from menu. - if (menu_type == RGUI_SETTINGS_CORE || menu_type == RGUI_SETTINGS_DEFERRED_CORE) - { - if (type == RGUI_FILE_PLAIN) - { - strlcpy(type_str, "(CORE)", sizeof(type_str)); - file_list_get_alt_at_offset(rgui->selection_buf, i, &path); - w = 6; - } - else - { - strlcpy(type_str, "(DIR)", sizeof(type_str)); - type = RGUI_FILE_DIRECTORY; - w = 5; - } - } - else if (menu_type == RGUI_SETTINGS_CONFIG || -#ifdef HAVE_OVERLAY - menu_type == RGUI_SETTINGS_OVERLAY_PRESET || -#endif - menu_type == RGUI_SETTINGS_DISK_APPEND || - menu_type_is == RGUI_FILE_DIRECTORY) - { - if (type == RGUI_FILE_PLAIN) - { - strlcpy(type_str, "(FILE)", sizeof(type_str)); - w = 6; - } - else if (type == RGUI_FILE_USE_DIRECTORY) - { - *type_str = '\0'; - w = 0; - } - else - { - strlcpy(type_str, "(DIR)", sizeof(type_str)); - type = RGUI_FILE_DIRECTORY; - w = 5; - } - } - else if (menu_type == RGUI_SETTINGS_OPEN_HISTORY) - { - *type_str = '\0'; - w = 0; - } - else if (type >= RGUI_SETTINGS_CORE_OPTION_START) - strlcpy(type_str, - core_option_get_val(g_extern.system.core_options, type - RGUI_SETTINGS_CORE_OPTION_START), - sizeof(type_str)); - else if (driver.menu_ctx && driver.menu_ctx->backend && driver.menu_ctx->backend->setting_set_label) - driver.menu_ctx->backend->setting_set_label(type_str, sizeof(type_str), &w, type); - - char entry_title_buf[256]; - char type_str_buf[64]; - bool selected = i == rgui->selection_ptr; - - strlcpy(entry_title_buf, path, sizeof(entry_title_buf)); - strlcpy(type_str_buf, type_str, sizeof(type_str_buf)); - - if ((type == RGUI_FILE_PLAIN || type == RGUI_FILE_DIRECTORY)) - menu_ticker_line(entry_title_buf, LAKKA_TERM_WIDTH - (w + 1 + 2), g_extern.frame_count / 15, path, selected); - else - menu_ticker_line(type_str_buf, w, g_extern.frame_count / 15, type_str, selected); - - snprintf(message, sizeof(message), "%c %-*.*s %-*s", - selected ? '>' : ' ', - LAKKA_TERM_WIDTH - (w + 1 + 2), LAKKA_TERM_WIDTH - (w + 1 + 2), - entry_title_buf, - w, - type_str_buf); - - blit_line(rgui, x, y, message, selected); - } - -#ifdef GEKKO - const char *message_queue; - - if (rgui->msg_force) - { - message_queue = msg_queue_pull(g_extern.msg_queue); - rgui->msg_force = false; - } - else - message_queue = driver.current_msg; - - rgui_render_messagebox(rgui, message_queue); -#endif - - if (rgui->keyboard.display) - { - char msg[1024]; - const char *str = *rgui->keyboard.buffer; - if (!str) - str = ""; - snprintf(msg, sizeof(msg), "%s\n%s", rgui->keyboard.label, str); - lakka_render_messagebox(rgui, msg); - } } static void lakka_set_texture(void *data, bool enable) { rgui_handle_t *rgui = (rgui_handle_t*)data; - if (driver.video_data && driver.video_poke && driver.video_poke->set_texture_enable) - driver.video_poke->set_texture_frame(driver.video_data, menu_framebuf, - enable, rgui->width, rgui->height, 1.0f); + //if (driver.video_data && driver.video_poke && driver.video_poke->set_texture_enable) + //driver.video_poke->set_texture_frame(driver.video_data, menu_framebuf, + // enable, 1440, 900, 1.0f); +} + +void lakka_render(void *data) +{ + // Throttle in case VSync is broken (avoid 1000+ FPS RGUI). + /*retro_time_t time, delta, target_msec, sleep_msec; + time = rarch_get_time_usec(); + delta = (time - rgui->last_time) / 1000; + target_msec = 750 / g_settings.video.refresh_rate; // Try to sleep less, so we can hopefully rely on FPS logger. + sleep_msec = target_msec - delta; + if (sleep_msec > 0) + rarch_sleep((unsigned int)sleep_msec); + rgui->last_time = rarch_get_time_usec();*/ + + //update_tweens((float)delta/10000); + + gl_t *gl = (gl_t*)data; + + glViewport(0, 0, gl->win_width, gl->win_height); + + lakka_draw_background(gl); + + for(int i = 0; i < num_categories; i++) + { + // draw items + for(int j = 0; j < categories[i].num_items; j++) + { + lakka_draw_icon(gl, + categories[i].items[j].icon, + 156 + HSPACING*(i+1) + all_categories_x - dim/2.0, + 300 + categories[i].items[j].y + dim/2.0, + categories[i].items[j].alpha, + 0, + categories[i].items[j].zoom); + + if (i == menu_active_category && j == categories[i].active_item && depth == 1) // performance improvement + { + for(int k = 0; k < categories[i].items[j].num_subitems; k++) + { + if (k == 0 && g_extern.main_is_init && !g_extern.libretro_dummy && strcmp(g_extern.fullpath, categories[menu_active_category].items[categories[menu_active_category].active_item].rom) == 0) + { + lakka_draw_icon(gl, + resume_icon, + 156 + HSPACING*(i+2) + all_categories_x - dim/2.0, + 300 + categories[i].items[j].subitems[k].y + dim/2.0, + categories[i].items[j].subitems[k].alpha, + 0, + categories[i].items[j].subitems[k].zoom); + lakka_draw_text(gl, + resume_label, + 156 + HSPACING*(i+2) + all_categories_x + dim/2.0, + 300 + categories[i].items[j].subitems[k].y + 15, + 1, + categories[i].items[j].subitems[k].alpha); + } + else if (k == 0) + { + lakka_draw_icon(gl, + run_icon, + 156 + HSPACING*(i+2) + all_categories_x - dim/2.0, + 300 + categories[i].items[j].subitems[k].y + dim/2.0, + categories[i].items[j].subitems[k].alpha, + 0, + categories[i].items[j].subitems[k].zoom); + lakka_draw_text(gl, + run_label, + 156 + HSPACING*(i+2) + all_categories_x + dim/2.0, + 300 + categories[i].items[j].subitems[k].y + 15, + 1, + categories[i].items[j].subitems[k].alpha); + } else if (g_extern.main_is_init && !g_extern.libretro_dummy && strcmp(g_extern.fullpath, categories[menu_active_category].items[categories[menu_active_category].active_item].rom) == 0) + { + lakka_draw_icon(gl, + categories[i].items[j].subitems[k].icon, + 156 + HSPACING*(i+2) + all_categories_x - dim/2.0, + 300 + categories[i].items[j].subitems[k].y + dim/2.0, + categories[i].items[j].subitems[k].alpha, + 0, + categories[i].items[j].subitems[k].zoom); + /*if category.prefix ~= "settings" and (k == 2 or k == 3) and item.slot == -1 then + love.graphics.print(subitem.name .. " <" .. item.slot .. " (auto)>", 256 + (HSPACING*(i+1)) + all_categories.x, 300-15 + subitem.y) + elseif category.prefix ~= "settings" and (k == 2 or k == 3) then + love.graphics.print(subitem.name .. " <" .. item.slot .. ">", 256 + (HSPACING*(i+1)) + all_categories.x, 300-15 + subitem.y) + else*/ + lakka_draw_text(gl, + categories[i].items[j].subitems[k].out, + 156 + HSPACING*(i+2) + all_categories_x + dim/2.0, + 300 + categories[i].items[j].subitems[k].y + 15, + 1, + categories[i].items[j].subitems[k].alpha); + /*end*/ + } + } + } + + if (depth == 0) { + if (i == menu_active_category && j > categories[menu_active_category].active_item - 4 && j < categories[menu_active_category].active_item + 10) // performance improvement + lakka_draw_text(gl, + categories[i].items[j].out, + 156 + HSPACING*(i+1) + all_categories_x + dim/2.0, + 300 + categories[i].items[j].y + 15, + 1, + categories[i].items[j].alpha); + } else { + lakka_draw_icon(gl, + arrow_icon, + 156 + (HSPACING*(i+1)) + all_categories_x + 150 +-dim/2.0, + 300 + categories[i].items[j].y + dim/2.0, + categories[i].items[j].alpha, + 0, + categories[i].items[j].zoom); + } + } + + // draw category + lakka_draw_icon(gl, + categories[i].icon, + 156 + (HSPACING*(i+1)) + all_categories_x - dim/2.0, + 300 + dim/2.0, + categories[i].alpha, + 0, + categories[i].zoom); + } + + struct font_output_list msg = (depth == 0) ? categories[menu_active_category].out : categories[menu_active_category].items[categories[menu_active_category].active_item].out; + lakka_draw_text(gl, msg, 15.0, 40.0, 1, 1.0); + + gl_set_viewport(gl, gl->win_width, gl->win_height, false, false); } static void lakka_init_assets(void *data) @@ -651,47 +724,204 @@ static void lakka_init_assets(void *data) if (!rgui) return; - menu_texture = (struct texture_image*)calloc(1, sizeof(*menu_texture)); - texture_image_load("/home/squarepusher/1391666767568.png", menu_texture); + gl_t *gl = (gl_t*)data; - texid = png_texture_load(menu_texture, &dim, &dim); + settings_icon = png_texture_load("/usr/share/retroarch/settings.png", &dim, &dim); + arrow_icon = png_texture_load("/usr/share/retroarch/arrow.png", &dim, &dim); + run_icon = png_texture_load("/usr/share/retroarch/run.png", &dim, &dim); + resume_icon = png_texture_load("/usr/share/retroarch/resume.png", &dim, &dim); + savestate_icon = png_texture_load("/usr/share/retroarch/savestate.png", &dim, &dim); + loadstate_icon = png_texture_load("/usr/share/retroarch/loadstate.png", &dim, &dim); + screenshot_icon = png_texture_load("/usr/share/retroarch/screenshot.png", &dim, &dim); + reload_icon = png_texture_load("/usr/share/retroarch/reload.png", &dim, &dim); - lakka_set_texture(rgui, true); + font_driver->render_msg(font, "Run", &run_label); + font_driver->render_msg(font, "Resume", &resume_label); +} + +void lakka_init_settings() +{ + gl_t *gl = (gl_t*)driver.video_data; + + menu_category mcat; + mcat.name = "Settings"; + mcat.icon = settings_icon; + mcat.alpha = 1.0; + mcat.zoom = C_ACTIVE_ZOOM; + mcat.active_item = 0; + mcat.num_items = 0; + mcat.items = calloc(mcat.num_items, sizeof(menu_item)); + struct font_output_list out; + font_driver->render_msg(font, mcat.name, &out); + mcat.out = out; + categories[0] = mcat; +} + +char * str_replace ( const char *string, const char *substr, const char *replacement ){ + char *tok = NULL; + char *newstr = NULL; + char *oldstr = NULL; + char *head = NULL; + + /* if either substr or replacement is NULL, duplicate string a let caller handle it */ + if ( substr == NULL || replacement == NULL ) return strdup (string); + newstr = strdup (string); + head = newstr; + while ( (tok = strstr ( head, substr ))){ + oldstr = newstr; + newstr = malloc ( strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) + 1 ); + /*failed to alloc mem, free old string and return NULL */ + if ( newstr == NULL ){ + free (oldstr); + return NULL; + } + memcpy ( newstr, oldstr, tok - oldstr ); + memcpy ( newstr + (tok - oldstr), replacement, strlen ( replacement ) ); + memcpy ( newstr + (tok - oldstr) + strlen( replacement ), tok + strlen ( substr ), strlen ( oldstr ) - strlen ( substr ) - ( tok - oldstr ) ); + memset ( newstr + strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) , 0, 1 ); + /* move back head right after the last replacement */ + head = newstr + (tok - oldstr) + strlen( replacement ); + free (oldstr); + } + return newstr; +} + +void lakka_init_items(int i, menu_category *mcat, core_info_t corenfo, char* gametexturepath, char* path) +{ + gl_t *gl = (gl_t*)driver.video_data; + + struct string_list *list = dir_list_new(path, corenfo.supported_extensions, true); + dir_list_sort(list, true); + + int num_items = list ? list->size : 0; + + for (int j = 0; j < num_items; j++) { + if (list->elems[j].attr.b) // is a directory + { + lakka_init_items(i, mcat, corenfo, gametexturepath, list->elems[j].data); + } + else + { + int n = mcat->num_items; + mcat->num_items++; + mcat->items = realloc(mcat->items, mcat->num_items * sizeof(menu_item)); + + mcat->items[n].name = path_basename(list->elems[j].data); + mcat->items[n].rom = list->elems[j].data; + mcat->items[n].icon = png_texture_load(gametexturepath, &dim, &dim); + mcat->items[n].alpha = i != menu_active_category ? 0 : n ? 0.5 : 1; + mcat->items[n].zoom = n ? I_PASSIVE_ZOOM : I_ACTIVE_ZOOM; + mcat->items[n].y = n ? VSPACING*(3+n) : VSPACING*2.5; + mcat->items[n].active_subitem = 0; + mcat->items[n].num_subitems = 5; + mcat->items[n].subitems = calloc(mcat->items[n].num_subitems, sizeof(menu_subitem)); + + for (int k = 0; k < mcat->items[n].num_subitems; k++) { + switch (k) { + case 0: + mcat->items[n].subitems[k].name = "Run"; + mcat->items[n].subitems[k].icon = run_icon; + break; + case 1: + mcat->items[n].subitems[k].name = "Save State"; + mcat->items[n].subitems[k].icon = savestate_icon; + break; + case 2: + mcat->items[n].subitems[k].name = "Load State"; + mcat->items[n].subitems[k].icon = loadstate_icon; + break; + case 3: + mcat->items[n].subitems[k].name = "Take Screenshot"; + mcat->items[n].subitems[k].icon = screenshot_icon; + break; + case 4: + mcat->items[n].subitems[k].name = "Reload"; + mcat->items[n].subitems[k].icon = reload_icon; + break; + } + mcat->items[n].subitems[k].alpha = 0; + mcat->items[n].subitems[k].zoom = k == mcat->items[n].active_subitem ? I_ACTIVE_ZOOM : I_PASSIVE_ZOOM; + mcat->items[n].subitems[k].y = k == 0 ? VSPACING*2.5 : VSPACING*(3+k); + struct font_output_list out; + font_driver->render_msg(font, mcat->items[n].subitems[k].name, &out); + mcat->items[n].subitems[k].out = out; + } + + struct font_output_list out; + font_driver->render_msg(font, mcat->items[n].name, &out); + mcat->items[n].out = out; + } + } } static void *lakka_init(void) { - uint16_t *framebuf = menu_framebuf; - size_t framebuf_pitch; - rgui_handle_t *rgui = (rgui_handle_t*)calloc(1, sizeof(*rgui)); - rgui->frame_buf = framebuf; - rgui->width = 320; - rgui->height = 240; - framebuf_pitch = rgui->width * sizeof(uint16_t); + gl_t *gl = (gl_t*)driver.video_data; - rgui->frame_buf_pitch = framebuf_pitch; + init_font(gl, g_settings.video.font_path, 6, 1440, 900); - bool ret = rguidisp_init_font(rgui); + menu_init_core_info(rgui); - if (!ret) - { - RARCH_ERR("No font bitmap or binary, abort"); - /* TODO - should be refactored - perhaps don't do rarch_fail but instead - * exit program */ - g_extern.lifecycle_state &= ~((1ULL << MODE_MENU) | (1ULL << MODE_GAME)); - return NULL; + rgui->core_info = core_info_list_new("/usr/lib/libretro"); + + num_categories = rgui->core_info ? rgui->core_info->count + 1 : 1; + + lakka_init_assets(gl); + + categories = realloc(categories, num_categories * sizeof(menu_category)); + + lakka_init_settings(); + + for (int i = 0; i < num_categories-1; i++) { + core_info_t corenfo = rgui->core_info->list[i]; + + char core_id[256]; + strcpy(core_id, basename(corenfo.path)); + strcpy(core_id, str_replace(core_id, ".so", "")); + strcpy(core_id, str_replace(core_id, ".dll", "")); + strcpy(core_id, str_replace(core_id, ".dylib", "")); + strcpy(core_id, str_replace(core_id, "-libretro", "")); + strcpy(core_id, str_replace(core_id, "_libretro", "")); + strcpy(core_id, str_replace(core_id, "libretro-", "")); + strcpy(core_id, str_replace(core_id, "libretro_", "")); + + char texturepath[256]; + strcpy(texturepath, "/usr/share/retroarch/"); + strcat(texturepath, core_id); + strcat(texturepath, ".png"); + + char gametexturepath[256]; + strcpy(gametexturepath, "/usr/share/retroarch/"); + strcat(gametexturepath, core_id); + strcat(gametexturepath, "-game.png"); + + menu_category mcat; + mcat.name = corenfo.display_name; + mcat.libretro = corenfo.path; + mcat.icon = png_texture_load(texturepath, &dim, &dim); + mcat.alpha = 0.5; + mcat.zoom = C_PASSIVE_ZOOM; + mcat.active_item = 0; + mcat.num_items = 0; + mcat.items = calloc(mcat.num_items, sizeof(menu_item)); + struct font_output_list out; + font_driver->render_msg(font, mcat.name, &out); + mcat.out = out; + + lakka_init_items(i+1, &mcat, corenfo, gametexturepath, g_settings.content_directory); + + categories[i+1] = mcat; } - - lakka_init_assets(rgui); - return rgui; + rgui->last_time = rarch_get_time_usec(); + + return NULL; } static void lakka_free_assets(void *data) { - texture_image_free(menu_texture); } static void lakka_free(void *data) @@ -721,7 +951,7 @@ static int lakka_input_postprocess(void *data, uint64_t old_state) const menu_ctx_driver_t menu_ctx_lakka = { lakka_set_texture, - lakka_render_messagebox, + NULL, lakka_render, NULL, lakka_init, @@ -742,7 +972,6 @@ const menu_ctx_driver_t menu_ctx_lakka = { NULL, NULL, NULL, - NULL, - NULL, + &menu_ctx_backend_lakka, "lakka", }; diff --git a/frontend/menu/disp/lakka.h b/frontend/menu/disp/lakka.h new file mode 100644 index 0000000000..9df07c65a3 --- /dev/null +++ b/frontend/menu/disp/lakka.h @@ -0,0 +1,38 @@ +void lakka_render(void *data); + +typedef struct +{ + char* name; + GLuint icon; + float alpha; + float zoom; + float y; + struct font_output_list out; +} menu_subitem; + +typedef struct +{ + char* name; + char* rom; + GLuint icon; + float alpha; + float zoom; + float y; + int active_subitem; + int num_subitems; + menu_subitem *subitems; + struct font_output_list out; +} menu_item; + +typedef struct +{ + char* name; + char* libretro; + GLuint icon; + float alpha; + float zoom; + int active_item; + int num_items; + menu_item *items; + struct font_output_list out; +} menu_category; diff --git a/frontend/menu/disp/png_texture_load.c b/frontend/menu/disp/png_texture_load.c new file mode 100644 index 0000000000..69b0945566 --- /dev/null +++ b/frontend/menu/disp/png_texture_load.c @@ -0,0 +1,172 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2013 - Hans-Kristian Arntzen + * Copyright (C) 2011-2013 - Daniel De Matteis + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include "png_texture_load.h" + +// thanks to https://github.com/DavidEGrayson/ahrs-visualizer/blob/master/png_texture.cpp +GLuint png_texture_load(const char * file_name, int * width, int * height) +{ + png_byte header[8]; + + FILE *fp = fopen(file_name, "rb"); + if (fp == 0) + { + perror(file_name); + return 0; + } + + // read the header + fread(header, 1, 8, fp); + + if (png_sig_cmp(header, 0, 8)) + { + fprintf(stderr, "error: %s is not a PNG.\n", file_name); + fclose(fp); + return 0; + } + + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) + { + fprintf(stderr, "error: png_create_read_struct returned 0.\n"); + fclose(fp); + return 0; + } + + // create png info struct + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + fprintf(stderr, "error: png_create_info_struct returned 0.\n"); + png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); + fclose(fp); + return 0; + } + + // create png info struct + png_infop end_info = png_create_info_struct(png_ptr); + if (!end_info) + { + fprintf(stderr, "error: png_create_info_struct returned 0.\n"); + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); + fclose(fp); + return 0; + } + + // the code in this if statement gets called if libpng encounters an error + if (setjmp(png_jmpbuf(png_ptr))) { + fprintf(stderr, "error from libpng\n"); + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + fclose(fp); + return 0; + } + + // init png reading + png_init_io(png_ptr, fp); + + // let libpng know you already read the first 8 bytes + png_set_sig_bytes(png_ptr, 8); + + // read all the info up to the image data + png_read_info(png_ptr, info_ptr); + + // variables to pass to get info + int bit_depth, color_type; + png_uint_32 temp_width, temp_height; + + // get info about png + png_get_IHDR(png_ptr, info_ptr, &temp_width, &temp_height, &bit_depth, &color_type, + NULL, NULL, NULL); + + if (width){ *width = temp_width; } + if (height){ *height = temp_height; } + + //printf("%s: %lux%lu %d\n", file_name, temp_width, temp_height, color_type); + + if (bit_depth != 8) + { + fprintf(stderr, "%s: Unsupported bit depth %d. Must be 8.\n", file_name, bit_depth); + return 0; + } + + GLint format; + switch(color_type) + { + case PNG_COLOR_TYPE_RGB: + format = GL_RGB; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + format = GL_RGBA; + break; + default: + fprintf(stderr, "%s: Unknown libpng color type %d.\n", file_name, color_type); + return 0; + } + + // Update the png info struct. + png_read_update_info(png_ptr, info_ptr); + + // Row size in bytes. + int rowbytes = png_get_rowbytes(png_ptr, info_ptr); + + // glTexImage2d requires rows to be 4-byte aligned + rowbytes += 3 - ((rowbytes-1) % 4); + + // Allocate the image_data as a big block, to be given to opengl + png_byte * image_data = (png_byte *)malloc(rowbytes * temp_height * sizeof(png_byte)+15); + if (image_data == NULL) + { + fprintf(stderr, "error: could not allocate memory for PNG image data\n"); + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + fclose(fp); + return 0; + } + + // row_pointers is for pointing to image_data for reading the png with libpng + png_byte ** row_pointers = (png_byte **)malloc(temp_height * sizeof(png_byte *)); + if (row_pointers == NULL) + { + fprintf(stderr, "error: could not allocate memory for PNG row pointers\n"); + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + free(image_data); + fclose(fp); + return 0; + } + + // set the individual row_pointers to point at the correct offsets of image_data + for (unsigned int i = 0; i < temp_height; i++) + { + row_pointers[temp_height - 1 - i] = image_data + i * rowbytes; + } + + // read the png into image_data through row_pointers + png_read_image(png_ptr, row_pointers); + + // Generate the OpenGL texture object + GLuint texture; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, format, temp_width, temp_height, 0, format, GL_UNSIGNED_BYTE, image_data); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // clean up + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + free(image_data); + free(row_pointers); + fclose(fp); + return texture; +} \ No newline at end of file diff --git a/frontend/menu/disp/png_texture_load.h b/frontend/menu/disp/png_texture_load.h new file mode 100644 index 0000000000..e06cb67102 --- /dev/null +++ b/frontend/menu/disp/png_texture_load.h @@ -0,0 +1,21 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2013 - Hans-Kristian Arntzen + * Copyright (C) 2011-2013 - Daniel De Matteis + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include "../../../gfx/gl_common.h" +#include "../../../gfx/gfx_common.h" +#include + +GLuint png_texture_load(const char *, int *, int *); \ No newline at end of file diff --git a/frontend/menu/disp/tween.c b/frontend/menu/disp/tween.c new file mode 100644 index 0000000000..380b680978 --- /dev/null +++ b/frontend/menu/disp/tween.c @@ -0,0 +1,75 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2013 - Hans-Kristian Arntzen + * Copyright (C) 2011-2013 - Daniel De Matteis + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include +#include +#include "tween.h" + +tween* tweens = NULL; +int numtweens = 0; + +float inOutQuad(float t, float b, float c, float d) +{ + t = t / d * 2; + if (t < 1) + return c / 2 * pow(t, 2) + b; + return -c / 2 * ((t - 1) * (t - 3) - 1) + b; +} + +void add_tween(float duration, float target_value, float* subject, easingFunc easing) { + numtweens++; + tweens = realloc(tweens, numtweens * sizeof(tween)); + tweens[numtweens-1].alive = 1; + tweens[numtweens-1].duration = duration; + tweens[numtweens-1].running_since = 0; + tweens[numtweens-1].initial_value = *subject; + tweens[numtweens-1].target_value = target_value; + tweens[numtweens-1].subject = subject; + tweens[numtweens-1].easing = easing; +} + +void update_tweens(float dt) +{ + int active_tweens = 0; + for(int i = 0; i < numtweens; i++) + { + tweens[i] = update_tween(tweens[i], dt); + active_tweens += tweens[i].running_since < tweens[i].duration ? 1 : 0; + } + if (numtweens && !active_tweens) { + numtweens = 0; + } +} + +tween update_tween(tween tw, float dt) +{ + if (tw.running_since < tw.duration) { + tw.running_since += dt; + *(tw.subject) = tw.easing( + tw.running_since, + tw.initial_value, + tw.target_value - tw.initial_value, + tw.duration); + if (tw.running_since >= tw.duration) + *(tw.subject) = tw.target_value; + } + return tw; +} + +void free_tweens() +{ + free(tweens); +} diff --git a/frontend/menu/disp/tween.h b/frontend/menu/disp/tween.h new file mode 100644 index 0000000000..35446e9dd1 --- /dev/null +++ b/frontend/menu/disp/tween.h @@ -0,0 +1,37 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2010-2013 - Hans-Kristian Arntzen + * Copyright (C) 2011-2013 - Daniel De Matteis + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include "math.h" + +typedef float (*easingFunc)(float, float, float, float); + +float inOutQuad(float, float, float, float); + +typedef struct +{ + int alive; + float duration; + float running_since; + float initial_value; + float target_value; + float* subject; + easingFunc easing; +} tween; + +tween update_tween(tween, float); +void update_tweens(float); +void add_tween(float, float, float*, easingFunc); +void free_tweens(); \ No newline at end of file diff --git a/gfx/gl.c b/gfx/gl.c index 881bd2bf19..35516cb1d1 100644 --- a/gfx/gl.c +++ b/gfx/gl.c @@ -49,6 +49,10 @@ #include "shader_glsl.h" #endif +#ifdef HAVE_LAKKA +#include "../frontend/menu/disp/lakka.h" +#endif + #include "shader_common.h" // Used for the last pass when rendering to the back buffer. @@ -1515,7 +1519,11 @@ static bool gl_frame(void *data, const void *frame, unsigned width, unsigned hei #if defined(HAVE_MENU) if (gl->rgui_texture_enable) - gl_draw_texture(gl); + #if defined(HAVE_LAKKA) + lakka_render(gl); + #else + gl_draw_texture(gl); + #endif #endif if (msg && gl->font_ctx) diff --git a/media/lakka/fceu-game.png b/media/lakka/fceu-game.png new file mode 100644 index 0000000000000000000000000000000000000000..f8ed60da39988f2c7fb70233f0fc29c523ff33a1 GIT binary patch literal 1551 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcalEX7WqAsj$Z!;#Vf4nJ zXtsecqtcuEJwQRp64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq!<`jdpunn zLn`LHy}LI*I#lBL$KyYq9xLk+cGp~a=+dg0N-7~g^H&^kJ09lKBUaCMio^R#--eI= z6$yz+2|5CTA19gU_a(JGaEjgku=u=f|KHzr)4cp-=9_(gUHpFX>SZP}e7pf)<6f~c zXbLhc33ZxR^W{jg|Ni#b1cSe>t3!Y9xIOpR$BFOv{Z&h;-1+;- z<>kWi|34j{+Z4WO*FK9MxsM)u%NW-hTN`bDeD8Dm<@pxw0Ssm3AL>p2Jy^5;6{1soneLb=y3quWGh4Qg5D7Kl|I_3yX}gaQ{Kq)^`lT=acQK{#blCeedSq z1pVpjng28O&HVZNl5v3`PbLck0xb+)u)?qIr{!0n4bN^cD)cfS(Alpd9o3Bj($)-1 zfPR9bo%5Iq^#ziW*&9rl8ceP;=4^{vIsHT1@~sN?>zH>uf1!SG-nFJbJNOcIU*6c> z&o^n`@4RhM-#%Py%q_P%d99%$#=mh*$fmb*WpCemxL?jiW?eahf*Hu9XNzn|`_ermsu-S(_atuoyvDmjo>pPRF_V%*6!__Bk&YsGBrMjp2eH35W z=Gj-gBRJ*$&*e~H=bzP#;IzN>zP+IiB z-s^ldQY)$z+~YNCYIOClhx5N3mY4o^x$>&tI;~6M$IhNw`!>z|+k%&$=hh!7WmsLI ze7n9edn-?1Kgl>|vrNP)K?bpEbCbyZo{!^0ip7f(yw zcJ<|xElNGf>>Vqv=0&dziTd?(Y0$~2w`G0Whb)*nR>bH9XU5I&$w_|U8jwKE8w)z>eR?B=Z}8^#{IRIaSOtB=Pp-QH2q}H)nakBTF~v>q&jJ?S44$rj JF6*2UngF7-g(m<2 literal 0 HcmV?d00001 diff --git a/media/lakka/fceu.png b/media/lakka/fceu.png new file mode 100644 index 0000000000000000000000000000000000000000..15954e4bf0fb626cd7a771f3abd2fc40abba17a4 GIT binary patch literal 2312 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 JF6*2UngF7-g(m<2 literal 0 HcmV?d00001 diff --git a/media/lakka/fceumm.png b/media/lakka/fceumm.png new file mode 100644 index 0000000000000000000000000000000000000000..15954e4bf0fb626cd7a771f3abd2fc40abba17a4 GIT binary patch literal 2312 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 LSi948W<_Ux`6{?R_#t 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 F{{t`Eg#Q2l literal 0 HcmV?d00001 diff --git a/media/lakka/genplus-game.png b/media/lakka/genplus-game.png new file mode 100644 index 0000000000000000000000000000000000000000..c4e6b6cfa796e12fcac9ff67a03f462da6af5022 GIT binary patch literal 1098 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
  • I!_Wki{dxQf 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 KYNN7k%>Mw|t2u%I literal 0 HcmV?d00001 diff --git a/media/lakka/imame4all-game.png b/media/lakka/imame4all-game.png new file mode 100644 index 0000000000000000000000000000000000000000..aaa7f41950d0143631808e6ecda9d98775c414ee GIT binary patch literal 9541 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 literal 0 HcmV?d00001 diff --git a/media/lakka/loadstate.png b/media/lakka/loadstate.png new file mode 100644 index 0000000000000000000000000000000000000000..cb425a38b7dffcd5a067c8a23b3b1e47910038ef GIT binary patch literal 1293 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcalEX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4%J=^6jD<8LuIdFJO{mFJU-6^l!%s-h;mYFx?Q zpvl>g!K5&Yp+k*f(GX>_natMjTRyMxDvQB{r|16vyHdPi76V(tb9))47c=Mn-@viJ zz3ks%<%W4Te)3Odr~*pFFx{94l#rb}Z=Rp+;e%1d&*MX`?tOav@2XwVq1$(Fo5^b} z`&!_@w6Z^^>w`keSG~HtYS-nhz43=!ST?lI^RM2wZk74&Z9n%ef4hMgY&hmXFulbTM<=TzGCGn^=b0{VOO<7 zzh;Hn?s{f){+*HX>Yco|nnSmH?A~U%>%mX{>-!b=goo zvz2{y>)H8rx%*z+dUkYG)!tQg(I3B*_KV~=GT7eyHfurl?C8+ay{q>9nZMj-)8UAi zLz)*XH~&m}%Kv0h@T}tX_Unw7J5RU$f9CbN(5uHozkb`9Ker_J^98Hh5qas$e_pm= z`jXSGeDHqk?LEPv^XLD*<|$uXXvoNua6Ed+`>?C))8(q;4>SOyUbc_r^Qx-XXQsdV zYAXC+wSiGr`Np%-`yp4qpKfQ8n85Juy87w!5XRf5>5M!c4Aohcx(4f4y?fr)~u?VQPsEFkKy<0gX?nZW>$5qKKGl^tmNkZ yTVM8Ei|{^L#DbZDD*X*E$}kS$Tm^Ode};62%S~SWW_-Y+i^0>?&t;ucLK6UJnHBf| literal 0 HcmV?d00001 diff --git a/media/lakka/mame078-game.png b/media/lakka/mame078-game.png new file mode 100644 index 0000000000000000000000000000000000000000..aaa7f41950d0143631808e6ecda9d98775c414ee GIT binary patch literal 9541 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 literal 0 HcmV?d00001 diff --git a/media/lakka/mednafen-psx-game.png b/media/lakka/mednafen-psx-game.png new file mode 100644 index 0000000000000000000000000000000000000000..6fe56c163c8c1c81f73095000c2afc93d2142ee4 GIT binary patch literal 9847 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 literal 0 HcmV?d00001 diff --git a/media/lakka/mednafen-psx.png b/media/lakka/mednafen-psx.png new file mode 100644 index 0000000000000000000000000000000000000000..15c91b795a8da12d56ab0f62137137f2020be64b GIT binary patch literal 7500 zcmc(E1y>wFv-T`3us8&FcLIw`a18_v65QPb1ee7_&>#VV1Ofy?2=4Cg9u@)w53Y*^ zzJ1?&&i51UopX9xPIo;uHC5Hs)v;O{%D7mRSO5Uvs=QQqjjX%=Qy}Qb_v;lG3}glN zl2g%zAfF(J&3j}Y)8nO~7XWZX|EGW$57*+6gB0G12HraEcHX|0p0>UmP!Fpp$m;s#9OVXj6YJ#-cFtE8z1Vk4(deO)l|e(N853oGCJ45X zjhZGrgN%qOTl%E^GPJ+mn;6TAIL@0kOcXdiz7b`dpuZvaFGa=k)1u{K$HyQf+4=`z zkV(;)nOEs7YdL7aL^SZwWqFYz$dzsa$K%V2FgtO>>IZ1eWl}e|V-qZ&z}G0=;}U{Uu$u7K$Ad^$*6BQdgY-{SjEL z6;JMkWu^~$_w4L9HL-8k?o-eD&)dlBHQxWKv3(UgY{aLBF~-`(4Q$?LZHqyd)F8hO zg|Zvg42=&y_G+lUw+c2BBk7uu3><)<*b!Qg%4a$P+*qq4nq7x;VJ>!;HeS}j|5T!m zb>;gVws%)~kj>$EKwY+y2|-MBIDRnLh%zXWOw!eo9p4 zV8X%z(8_lHe-HYXr1Ud_N4M_0&65$9`vQ>&_R!-wPLKT2Bdo=oU%Z=61lp3PlAv>8 zCq6GI_q3JQVC^B#=UX+D?A!2DzTvKYn-;gS`hA z-6!}(^&l}nGciJ$8sXf2tLRm`sBt?*M-`RjAT?GZHP+NUAt`e3XiImETT@^F100w0z|vs!HCE$rFsxmJO@bQ@^$p7cG?jUC^Jw)SnCI zQY9jkY13cm2`(=$yZT!;JgXtm{W80#GerN|F2!jMeW(tFxkg4u3ugH~tfX$0BMgU*WEc`1zaQ*P9PN3BVxJX<(3|hBz7pQQGsJigq*(s zECKnlpK!dCU%>qFT91h8afewn$O5tD>MiP*8*GN$0EFmagRmHXsv@94c5fQ_o0;h@ zH%r_D2-&HEn`y(7P1e1ur9r1`LrV&hL6^*U)Z*dRY!mBC!&9Y*dTXVPuJN;|C zRP+2Dnt0&dbAuK?&4#$MB-#*}|N=wJ=(f=$P zQEhvi5%639mjG%$ORf_x9XWb}4t{m^k{>2-!;&M)aTnbbWp5hGqtqtw0p7Md{hSK{ z3$poMI21j6K0dz?C(m$A_9VA@7KzDOYZNuGqO2YHu_+e3E z#+5`}mz>Vl?0%{PVaw45(>;>dXJ8nA97S!3W>%%l!=+`=S&an_FSL( znAwTCh#Ovc>;%9zzGB-NcYyf*P6ggyLBvPabH!+*g-N3YCNd>#Xeo>iy{pY_mqSNO z(IH7!(%BykDb5F4)D1cvonP-_$Q?~yWjFjuc$b|f7?l!f&GuC|&Vt;}KDq&7B>_t7 zyb!y5ZVJtL+jDmv0+qsxv|P{Pe<6l)rXa=>TdXt;@+zi4!)K%jX1uy1(88iQPP@W>~5sRvJlJlU+{v7(i zH^;XMKMOXL%wt>LPsrQh4CQC>s2~l%7=JzHmyrSoPX!Itb{oqZCAz?nQ`1v<3|M9Y zy&chwHs0mfliAukE8*B(25D)Wdl#dkj_C|UFS+VRZKM@E+>YY{Krj_{h=IdRDxDeV zdSk0PIeOtZQ-at|4EBBhS@BO$54JJZ^PbeN^h6a@5x-{LL4bkO1A4qjuy;)dJfcS3 z90ch{8M$0gH@!l)W>*}T>Bm03?RKAKX2BtlL#|9 zl@E`d#t|*K29r2XH+e2A-ytb>bMVhqM&7OwOn~ZzAh`Cs^~) znV6Nh5=Pidc%AIv6w}1qw=t`MROwK1-^baX%_am)Bs6kTZTyDe-Y7%<7lU?O%8h+D zRN3m&Dvs(NH+w2UZ}%XHd7?C%O^XTblp=QPO-L~?#ifqgY7+Z0L;)!k7#=MKs4!Y! zAHkjW5f`aqnw1?K97mI+ynT{hcAPHSZ)cEZ8_zxPr9aqP~b`R(H8pW1x!9 zVnDSMZDC|Y?TF&I3U1WLQ}nFM1_&(v%|OY)Fiz+lk0)#_Mw=tew&R+o3O<|Yw4Bv1 zH*Ce^hNm_>B^PS_Z7W{f>`$V-CutFHf zmLSM&!)N<7y7$+o!16qy2JUq@r2Wv{GawpTn?D%xJ90K0q=a52P+;$D3vt4w7Onyx zt(NNsHW;YBQdq=gqfvV6<0F|i%^TZc{K;~}{pZsQ{cV=r6K*g>Efr8xIUefOd`aWN=-3$4_G%&BgfNhn2}=j{S;Eo2&V2wxp)X68Gnj+bKBFObElKY6UMIjVw~f7QMF(+xED6NqMUE zKLlN4dpX=+HnZ~T-#t$`RM{R?aRv&z^Cx^JAh8w)iJGZqYb~3zA!42SVgZc*TwHn1 z-%mK#@ht5|j)$Ag-7>jss7`Z7E?gY|@e61yzy8A@mW=yU99@43ld#Jj`w!=|t)XhH zR}<+bgBp4JRDZ!R>+Z{sV$+Tl(_a4rh}Lx>yRE5|ujMJjX|BNuByQ@GbV+)-jsB%% z5%VnXN%3M?{LgOfcRCXO+4*W;8Mknj8lUpNV2Wr}!3Z<#!Lt!+2um2+#WBIrjZ&tV zD;DWkD^m<92?o+o#d(oDAEf0g+w9my^1T10WIl6Qn;;brNg~ArG%y2Hu05zHUDn#) zc%LlWB=B$z+IFG^X7%S>vA=o{eOjRTa$SFWlI3Om1lCxzLW1nXpX3Zu~7(FS`iNd8{dC=NnGyPER9PZ-E zZNq9Jf?jJM8G^8v98c*aQ+W|Mplett&U9UVX*@+qyUJk+39&rxcHTDi>p2`CvJ)}m zI_cpZlVr!#jIPHDK+a-@6J6U*52;LNtqY7lXCjZj-ThlOQXaA-RHz)j8zH(l^Vvt( zU~tc((YV@gT0%lJ)NO4S$wsg7f%MClPgfr|As#_v@zuzPc~CP~p1!L%7ni3t^DM$p z-EaN382j^a#~gDrsN)||m(??li6;2e@V$jd8fz}D+lI+avSe7qZ&Fce62I%lq|p$+ z-$k*TRsMtaOYq*pg|2Q&AI}#^|3}y75M1R>O5XGgE9?X3@U@BCfee)V;i8nVmuJwN zC;@G`Bk+Vc+S14TiRRsPjQY;QG|~yOed2BrQ6??6JG!T{nunisZ>s+eK?CBNiSbuM zMRwMNUR~Z@n+BfUnLnXdF2-2OV_4oK%aTiZ6GTTnNtb@(^uFl0ZC zp1d+2$MaUTuO*U;CAr~^O{#VH$*%#Pg@o=1bUW|f$DWozVijW-a@=#58Ma+j9NPrhVF~sAY%DXqFE%|4cC$E2a<%OVKvfi@i?V8pKV{R3jI* zp|7A3z=|W=9<1*8J^?vcT8q*|goa6XHp|8e^vwLZxZglvFoxPO1I}A?<)aBRlt&4l zY5q&Vp}7jZb<{5Y{(S-Q;{O&}e)b;yc8YZ0UC&n5Ay^7d84+fu07J zd+-;!2c=_yp4UAsE7PGy{WC@7Aea`(YaJbyLc{*ShgLflnqR|_-@d(7n9&}7z$+MXd$}Zg^>r2z5BE9waDzHM3 zFcYY+E~CYiTY}O=R*{hHE*|>0t_0?#KE5}W_C|@r(J$wE=ujh7`w$FLYwr) z#hqhX3RZ+nsTlFHj4|p6s*c^7b<;PkI$J41Z6q$`E5P5;c&X7F1J%`@WC{d&0OqUt z!)zH~3Vr~hf`89=Bs_GUBagfTwO3 zrws4GNaa6s3^PRL3;Bl%-Al=UJJ)!{V4TMMm^uqqFTJFatFX9cHfU1Fvn%QNrQUkK z|Ffk%>xIb>k zfu#kaoORPr};y>Fb!ZRtoG>is^COmSEy$hlqMzOWfR0>?w@& zbkIZ?5S8Nam9&1?yttoU;cnUBQAFi7Z3Fbu(;*?PsC@_Mp%1lv(|J{po6Rp_eyhkq z`D=lm&)LT7uYMmVDP8vGRngla@go3}G$)yN>8SGPyTUd`9036VZ|_`c|1g<4bwhC~ zXvI*rx6fVfGE|_{9^3KI=I{+8UNfb@7=hGE9Hd8RCH@y?fSWvB&2>bhA=qfb?pLyZ z_?O2Cc~JS+IBTCh{cAo~sv0~e&J<4&zVit?OI(`%58O(q%NImFCTJhmAOG?Zn(qdfg?!+r` zUq(#Kj^_*DXSvX1l)*EkXX9>ZG2JKn5kGk{7^`D1xc@-7BH1qb_HX>0Mp2pfyH=0$ zxnGZ>|KYl39w3A;I7o$pxDN)_WraSBkz8WP5={M`j%~QcgLspZg@giZ8hNtn??}pi za2CvNFHYBn$pB=$^(s$BX<*6ZSAXxu-Fw$03v1@Q%jU{P{ng4CM zV1()$y#LS4cEcXp3^Ac!p7N^$#kv={zc+NhMt@?4mEQxy z1Yl4Ge0k&WFubq^#ix~IXJ)IFX$UlV;Zg18?c+1Zn^0JUOirzvcwsM(+v)1#!B*|` z+RzbqK_1O39zH%ChVoo%7%vq|jhqL!8+n)DV0sc?+Z`YQq&K61jiQ$c1)JuaKgzH*60tzcB4w*bb6iN<{4p>a}T0p~-lQ(yN zz09t;095HAat<-#Uow+<;HTn>dY);;o85DOIy4scExP5O z6V=Dc$k83xvJd+z)+0UL_FUoo)AKtJk4 zH_|ya_>GDiXs|3}&B^J^QPbpm!f#L_0Y5`mCam`vVS>Bkq^vl9oIrdaVkSBfp}AZw z_O&jrRY*Ck-;PE^`37rD~I4+&`~>;qyj5~P^K@LzCy z_t-YbxxOH#9~g{lM_&iZb>Z((>{#S%4b&XUFfZL6G%SesBS()->yMHt)G_l7UU)Va zEsbai1jBlfBw`0Mbod!fqGM)Rxv2$bG1wbKGCmXQ7&?8$oI|xpSXLq?bSB`OwUQx1 zF_@jxQckK%i}f%h9mQ58LSg^L`NEw26aVu&jnfLGpxoz#zb-*C_IZ#{VQIPRf2k?I z68E2f8&UpH;9?ylVFvViIlO;nqI9OdvPE>ls5=m)7N0bTO^;|T7#STw{)-mW3c1M9 zhAH`1R{xnE^|mffw4bsq-Mw6ZE~oGyt>CM4JHBL$tH(45c@SA}XKo=yC@hC;f& z8^1Q-F4Q0`fLI>mrxyEGP2$;^ABfm#-sf4DvJ+JSgOpt981~2il5obu{g|4OP5Jl3 zTS8y6XFLaTt2RjaS%YP;bO7oPzEnv;b|hFH)oN`?g9`u-&(<8J|B}2&>$v0w|5{-K z4F%EeX3LT@ngJ@oPNh?Jz9ClC;bySJ<3oX*j(3-PPc{-yugBKhfGFv$6fG0bZND7f zMSFw3UNb2-!(&UT5D zncxIx00|$$`IsU3)zu9VmP03A);bH|cL5u3oTy|m2}4NzX%xGlTZn{&dYZE@aWu(c zuW25sWPa!6Xvmm3msd)cHPECHBwSr7V-7hx!9Rw7s?ubm=Y-we1@NMtQ`x$Xd^VGG zH~7{-m}by3S@g#BMW2OCh>p2AZ-D5BQUI_plK)4!wegn|eQSDPAbZaz#$U;LQ$2$` zS$V!eKGL_hgIo1A2%v8ptbuef+`5iDlgrCq(CRdD6=46P=3HuyE0?DOPh`i73Kn(D zx8Vd??2H`*qOppS$z-p5vc>7%G1WQvq6^1Sb@EaF8Tt&IJzJuiBOHb~kt=p_mo{FL zh62DSbiN4+beRB0HCdndY%3gjV+IV+sDa#;~DIZvh!*$GW(m*+)-*u z9)rT2cnUrqb(KNeia9VgxzL5PE^~T;3{j(6PQy_w?kn1*(ITEe7GvE<3jt#TbpO4q zxr>SiBQJz1g7Kl^=U?c_v(7S#&py&8v)Qk_4%I4n;1Ju|)Vx-k+0T4J@yKQrvGDi= z8FMeu%zaQAFdtU}fU4smLY?%~;CP@DgsQBc)h8?SSip3HaMU@#=IBXg{7BP!twu)v zkO#VuG~{RY;O{ySr*0rSp^=vQQFU03MqoMG5iCr^yD|zHGl4eYW}Q6u27$Bl?SDJB z_@67YPoRG0cw^BJ+HfKa9$vM8VsuU6be^DfD=R1qqvN!^`*%cjZ468w{;C~3#N`(N zRz?VLm~bdipz;6?XdU$>S63ZB_$SJcW4lIY^V<;wK1{q_-_j?frBj$4?7kS*{25A) za(S!90n>-o@kctc;1~f9Uko*+G=zU-r-MAPU~(LS%(xM++553b0L57?kd`3s|5D>Y a`cO0P3xe#!N94^XprWXuP$6d#{{H}0)bfe| literal 0 HcmV?d00001 diff --git a/media/lakka/nx.png b/media/lakka/nx.png new file mode 100644 index 0000000000000000000000000000000000000000..6af096a23c7169e6ee4ad104ee5ef0b48e70d9d4 GIT binary patch literal 10526 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
  • I!_Wki{dxQf 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 KYNN7k%>Mw|t2u%I literal 0 HcmV?d00001 diff --git a/media/lakka/pocketsnes-game.png b/media/lakka/pocketsnes-game.png new file mode 100644 index 0000000000000000000000000000000000000000..cd5cc5c8239b5ae828985d880627802378e7844f GIT binary patch literal 1894 zcmcIldpOf;9RE#*L{tvPC_^I-R#GNoF1d_~iXoW|k#t6F4k?EzO&5|yGE%V=o68|o z<`Pp1t1+D1k6R|@(sH-6I_K%%PJf*D`F`K;^ZCB-^LhXHexA>J#r2Sbyevc(004Qk zx1(|>ZFpqtLTKv?(M60HlV{`>ov~GbekkL)3yK8fSvke<^V_ zG=l`$!o7Mnb6sUMKaHN>u?#NPi+^ag{p}l@=eIw%cVo*fl)1Jvo0DyDMW=fXy*4ri znrbgPOnl1AZnk&jCvP#kYte9)j!y9BT({2|6J2!WKm)P+1T`7wjyhW_*-0F48MXkR zE|NgV{}-k8YE0gfl-vp!OLttj>jvF$yq=jY(tK<=I;ZV@&bL1##gaW_l1$UNpjZNm8$Yqt$H&WM`E9yiUfL*WZMaIGjvG@Ea7*uks$J&xo6@4Qo^$`;;_Sqs>56`^LKE@Wc66wL}(+~~H=eo>G->}4Du~Tw2pRZ5`Mn*=G^kD;oG^11MhTan&1|&Sn=~HwaH~% zr0-K?9)D#6$0F9d#j?V}v4xi!-us@-bPZnvr@kR-F9+sCYnVV17Z*Z?6zY5(qap;h z(I06vpJ-tbfm;uB_nl)tXe+{S5$(6}%&6arH^oRJsEw+G3R5mZuGo-m79JR>y{(pQ z(i)bRwL~>>%E*XMVRZYhzK9EQ!iL!mBc47vK| zr$8>K>daziE(N$d-Ajvc8rsP@4qgg@1OYl-cLoz9f&QdWbt@w=UxNqvkSQdQ7`#$r zWrg(w$L+z8af$BG1N2)MvJvG8f4okrz$$FF@k=jo9POrAtG?pHjRjW4jrxj=64^_S z8}t>E)&Ic{)&A|nyueY;TojCz9sn>TG&krmBvuW^1$gF?5a+P66*@C4tziP!v zk>4%df{Z15w^S2WW?d_~)t)xQuY*EP7o zIMz^X-ZMbqh_Nj0g4mFdv7AOka$a!gYTXy_G&g=Z1UjBV!Yo#8DT75<3UviBXh+|^ w&iRaOoJr?;a|a9=FhxrK@c4g7EAK_1=&q{-6Q}#L;y)2U+a20pXoHLU8!y99b^rhX literal 0 HcmV?d00001 diff --git a/media/lakka/pocketsnes.png b/media/lakka/pocketsnes.png new file mode 100644 index 0000000000000000000000000000000000000000..4f801db6478b4dc216aacb8b40ff22554a5eb674 GIT binary patch literal 7187 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 LH54l3EW`c_Rw=vE literal 0 HcmV?d00001 diff --git a/media/lakka/reload.png b/media/lakka/reload.png new file mode 100644 index 0000000000000000000000000000000000000000..b9f556065ec596ce59b2081aef2f9aaf324a7f1e GIT binary patch literal 1697 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcalEX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4WO!N*93t!(Ooa)Gp;Uj;y9Gu?Vz>f z|H9r$oI(;)#GZI}izPO8FA$ry_im^4=1wJBy>k-J?Y`%pGfq$A`?qiY-7`DW=dsuB z`+jb2by^x*b0b4|_OE#i7u1;=Ooujl5g9jSbM5(ql8j&88wh@BXIRE8_@#wm8N1+@ z28Lzaf+al+%lHLLIvAD-3zh)I#RW?e(hMZN*s^n12HsQ6T)!k;)mA05dr7s*-JPeR z`%^NFZohj|Bj|AY)1n%`OWBj=P1$1h$-w2pq|#dd2W3;mpYnOStG<0ztrsE3=o1k) z<+aLQ^_N#UWhcG=r>oS!d8*rJa*5NWcd7fHY+7=8(mC&6u9r+be{!||ssH%gwX%K5 z+Q?Zo)*CsS?=z|`jrucP#dgn;)9R{wr!GA?RrTKXJM{-%JgWD(^n8+$r?`sk{G}(o z9xhPRYxZ-9o06?6d;QVT|C^W0jhwY-%aup*jup2)Z))p~pH${$71!I@#*tujZ|WBH z8|99d9!JiKk@+0qJ@<1w@3!wD$%_>I8l_Hkf70}fXW6aqacl0FbQ+D{ANNxB9j+^jU-9 z2Wy8Vdo;|we#LU{@3fh<^yGKl{@)!--n*6Eov_8yd-6`R<9lO23M<@;)BW>(_nmK# zrZ36rIjy|=T1c@Y3sc8;pAEIU4sSfX@zBl%YCvwnhT3%qZh8KOzuE1{@!Z=EgQdmw zb`|Q^nte>);L9+PgW(#7LR^K^rj>^`zUM=z(3S0zo%cz=K?P`B@P@s2d<}9rj&nV4 zn#G}TFa6-ReSF9HU|dOF`?iA9+g>GetzC8OhS{dqZl>Zc3Jo4t)AJ9_j=d-MT%!1M zApbtD<2z0~Z+Il=VC>%f_HSjNUEuTH;?Gatoay1~H;yQM^LO;4ej1uLqP}U ztF}9jH{IQ>vR9}s_sOICYaX`WFJse}eO&k887S&6d|381kb7UWyWT(9N&7B7I$FO? zN|;yh082_{QS2p0-uY~byz{?(d${)c@z_6=fq7@UKP`S$@ts}&zrxD|Q#Gka(@t=% zp7*Fs)q8UNlBjO`%O4i=e$T9UwZ)%%{pWtGbE}*3~Qrxn{<5BcFo}zdt{Vx+APl_jh$KcWwl&{6k|yP$1-uok`|6-!i*&y z9Lx9_OL{n#aWj4ainG^qygqr1_vD*9H8;PTy!lwp5b;=q;~FTYha<{k^6<zopr0M#YOjsO4v literal 0 HcmV?d00001 diff --git a/media/lakka/resume.png b/media/lakka/resume.png new file mode 100644 index 0000000000000000000000000000000000000000..398043479b1950beb0b33a04b899e670b9cb7eee GIT binary patch literal 900 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcalEX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4`v@ zECvl6&lDRRT@2V6c~qJ{vKY+ZI3{#}#l@hHK~1sAki|eyP{yBOvf#muECz~#GVTnM zg$@Em4F%r-MTHOUW7)tU_{N=~Ti~D_%LW0#9Djyx!GqgaHYf<@07Zoknz3v!5WM5Z z&@Fs0m!*K+rNEZ)4yRy@J1}S;`2G1=`mH@WT;PDL!N1p>35Cb%r5Sb3{K!{taFzTI z)T!|JejkHa@}Jv62b_B9tC=ENKE8Km;5PX8mm{I**nWLRpEEzct2cN`{s%fq@v%M7 z&B=d`3mtIj`Cq{_qvd12Vgskde+wpwGe3Z$4#({0F|Zl_v*bu%cs#$IfiLY(aivu4 zfh@aw_w#=3`*{8+H%8Qd1EvnQ`wWLG7zb^VKuV?mn3T#F6y(H~wgPhugQu&X%Q~lo FCIGS3C&mB( literal 0 HcmV?d00001 diff --git a/media/lakka/run.png b/media/lakka/run.png new file mode 100644 index 0000000000000000000000000000000000000000..6b5177b877128291004bce99e11e0a36b5318389 GIT binary patch literal 827 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcalEX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4_h8 z_h2ZO_#eb%asG$6@PWu<`{yv6GyKOca6t0de*vZ+HOKxd8;&fw|l=d#Wzp$Py~ECRFu literal 0 HcmV?d00001 diff --git a/media/lakka/savestate.png b/media/lakka/savestate.png new file mode 100644 index 0000000000000000000000000000000000000000..e865e460cf4cf1ff92c337e99edf3b9c77918cf5 GIT binary patch literal 1236 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE4M+yv$zcalEX7WqAsj$Z!;#Vf2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4&+R4wJ=k9+i)jN4prCx34yeIuC_G&w)J-MxN zzvu9)-KuHND$@J<`lii$IB)yCocVsAawc7$yk|pwZH+* zYqQ_Zcyi2QP2KvT`O-ovk6|6QIE&UE84&`QOV?Vj^)J^Zxz zj*Z|w$@Z(~C-WVX6+FO_H<#nRdS&wQ=s&ZbTrG^KHN6{hhm+AKWADwn2~QsNP5(dl z$y%VW(Os80$4)P}TxEUoxpmxck4gR4XTQ}JK6hAi4uf!X{TYm9C_bQ(UXps?>XA5;@@*P%NFRQbDRc~fd)@~ve`DT(9HaPZkij@ zjO*gnarKFlG%cnvJo|ci-u3yjQ@Hw68!F%aj{5i0bJ>FkrWw;2-WA5N-U5ce-Z@Y1 zw#xlGWwAz2|H}=27WD)3@^l|eWtF@2$=`uNI6{Hxw7nps&yxnhh<}`%2~O-C2R`}x zFbK~!1WGV7o=Irq(E0DfVlbIgdBY#$Pj&}#fkEF{ulRVwuGRdj-u$#X7_0yP%4ho* za~O@2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4Z(ru#t*(E>05?<4Cpx6l<~&1N?W&L^V$0ss#bovP?4p7Y5&1T zjsj9u+VgH{G;QLn5O`}i{mCN^y2o4oU+rbK=lzt-PyW4z z*HrIj@6?V@%iMIYh=&CjDj&9de>ioz_|w^w_D%nC&yfG-q%WS+)ardDl4IYh|7TQs z!NKS=QT(Z-r@iMclcx*qy?-fqPpQ^+{%`Z`<@et>pKGsm4y- zawkc;deS`aU6Yrd^y@BsCdBA+=*j=kpsF*@da0S8yer=PUNy~b^Q{SAT&Jn&XT7`3 z>iJzYHhaTb=GGaU-WzRGX%KiS;uz)PZ`%frLJ{Lj3$_Jiq!FDCzFo#UqDudvdsuDm9D@={9TwdZGy znL54*9Ozpz^@n>m7e#ut$o-DuUcizgXw*_Twh3DFy z-;vsD+_U#$7XR6eEL|mgSgNYO(7cWA}>uU1LIC~f7-a}^t|+_&&fv9 z945+5cW}4t?02(iQN57(=Rd>aZzeyRH}2Z9X7XRvYxg+wUT-}hlyT?t<$H~CXFq?x z#=+RLd-1F0l;^$+UsWIGVCrb`_ur@SGWzWCNs0bfzaLU}mTwe4xZ#)}!!?c(gZ?r1 YipG7c`g=cT$SifVWY-2M@c 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 g^pF1^sT0wIopaT0Cm$=QnJ#r2Sbyevc(004Qk zx1(|>ZFpqtLTKv?(M60HlV{`>ov~GbekkL)3yK8fSvke<^V_ zG=l`$!o7Mnb6sUMKaHN>u?#NPi+^ag{p}l@=eIw%cVo*fl)1Jvo0DyDMW=fXy*4ri znrbgPOnl1AZnk&jCvP#kYte9)j!y9BT({2|6J2!WKm)P+1T`7wjyhW_*-0F48MXkR zE|NgV{}-k8YE0gfl-vp!OLttj>jvF$yq=jY(tK<=I;ZV@&bL1##gaW_l1$UNpjZNm8$Yqt$H&WM`E9yiUfL*WZMaIGjvG@Ea7*uks$J&xo6@4Qo^$`;;_Sqs>56`^LKE@Wc66wL}(+~~H=eo>G->}4Du~Tw2pRZ5`Mn*=G^kD;oG^11MhTan&1|&Sn=~HwaH~% zr0-K?9)D#6$0F9d#j?V}v4xi!-us@-bPZnvr@kR-F9+sCYnVV17Z*Z?6zY5(qap;h z(I06vpJ-tbfm;uB_nl)tXe+{S5$(6}%&6arH^oRJsEw+G3R5mZuGo-m79JR>y{(pQ z(i)bRwL~>>%E*XMVRZYhzK9EQ!iL!mBc47vK| zr$8>K>daziE(N$d-Ajvc8rsP@4qgg@1OYl-cLoz9f&QdWbt@w=UxNqvkSQdQ7`#$r zWrg(w$L+z8af$BG1N2)MvJvG8f4okrz$$FF@k=jo9POrAtG?pHjRjW4jrxj=64^_S z8}t>E)&Ic{)&A|nyueY;TojCz9sn>TG&krmBvuW^1$gF?5a+P66*@C4tziP!v zk>4%df{Z15w^S2WW?d_~)t)xQuY*EP7o zIMz^X-ZMbqh_Nj0g4mFdv7AOka$a!gYTXy_G&g=Z1UjBV!Yo#8DT75<3UviBXh+|^ w&iRaOoJr?;a|a9=FhxrK@c4g7EAK_1=&q{-6Q}#L;y)2U+a20pXoHLU8!y99b^rhX literal 0 HcmV?d00001 diff --git a/media/lakka/snes9x-next.png b/media/lakka/snes9x-next.png new file mode 100644 index 0000000000000000000000000000000000000000..4f801db6478b4dc216aacb8b40ff22554a5eb674 GIT binary patch literal 7187 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 LH54l3EW`c_Rw=vE literal 0 HcmV?d00001 diff --git a/qb/config.params.sh b/qb/config.params.sh index fc239642b5..d97b8f29c6 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -1,4 +1,5 @@ HAVE_RGUI=yes # Disable RGUI +HAVE_LAKKA=yes # Disable Lakka HAVE_DYNAMIC=yes # Disable dynamic loading of libretro library HAVE_SDL=auto # SDL support HAVE_UDEV=auto # Udev/Evdev gamepad support