diff --git a/README.md b/README.md index aba2958..ab28e7f 100644 --- a/README.md +++ b/README.md @@ -180,3 +180,5 @@ gba emulator witten in c++23. - ocornut for imgui_club - everyone that has contributed to the bios decomp - [ftpd](https://github.com/mtheall/ftpd) and [nxshell](https://github.com/joel16/NX-Shell) for the deko3d backend for switch. +- Visual Game Boy icon by Icons8 +- Mac Folder icon by Icons8 diff --git a/src/frontend/backend/nx/icon.jpg b/assets/icons/icon.jpg similarity index 100% rename from src/frontend/backend/nx/icon.jpg rename to assets/icons/icon.jpg diff --git a/assets/icons/icons8-mac-folder-64.png b/assets/icons/icons8-mac-folder-64.png new file mode 100644 index 0000000..951b4c3 Binary files /dev/null and b/assets/icons/icons8-mac-folder-64.png differ diff --git a/assets/icons/icons8-visual-game-boy-48.png b/assets/icons/icons8-visual-game-boy-48.png new file mode 100644 index 0000000..e79581f Binary files /dev/null and b/assets/icons/icons8-visual-game-boy-48.png differ diff --git a/src/frontend/backend/nx/shaders/imgui_fsh.glsl b/assets/shaders/nx/imgui_fsh.glsl similarity index 100% rename from src/frontend/backend/nx/shaders/imgui_fsh.glsl rename to assets/shaders/nx/imgui_fsh.glsl diff --git a/src/frontend/backend/nx/shaders/imgui_vsh.glsl b/assets/shaders/nx/imgui_vsh.glsl similarity index 100% rename from src/frontend/backend/nx/shaders/imgui_vsh.glsl rename to assets/shaders/nx/imgui_vsh.glsl diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3478c1e..b6aee84 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,7 +37,8 @@ list(APPEND gcc_flags -Wmissing-requires ) -if (CMAKE_BUILD_TYPE STREQUAL "Debug") +# for switch build, always enable full optimisations, even in debug +if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT NINTENDO_SWITCH) list(APPEND gcc_flags -Og) # always enable otherwise debug builds would be too slow else() list(APPEND gcc_flags -fno-rtti -Ofast) diff --git a/src/frontend/CMakeLists.txt b/src/frontend/CMakeLists.txt index 28511bd..dfdf85c 100644 --- a/src/frontend/CMakeLists.txt +++ b/src/frontend/CMakeLists.txt @@ -30,8 +30,17 @@ FetchContent_Declare(minizip BUILD_COMMAND "" ) +FetchContent_Declare(stb + GIT_REPOSITORY https://github.com/nothings/stb.git + GIT_TAG af1a5bc352164740c1cc1354942b1c6b72eacb8a + GIT_PROGRESS TRUE + CONFIGURE_COMMAND "" + BUILD_COMMAND "" +) + FetchContent_MakeAvailable(imgui) FetchContent_MakeAvailable(imgui_club) +FetchContent_MakeAvailable(stb) add_library(imgui ${imgui_SOURCE_DIR}/imgui.cpp @@ -45,9 +54,12 @@ target_include_directories(imgui PUBLIC ${imgui_SOURCE_DIR}) # memory editor add_library(imgui_club INTERFACE) -target_include_directories(imgui INTERFACE ${imgui_club_SOURCE_DIR}/imgui_memory_editor) +target_include_directories(imgui_club INTERFACE ${imgui_club_SOURCE_DIR}/imgui_memory_editor) # fetch imgui is done! +add_library(stb INTERFACE) +target_include_directories(stb INTERFACE ${stb_SOURCE_DIR}) + # always include backend after fetching imgui add_subdirectory(backend) @@ -134,9 +146,14 @@ target_compile_definitions(main PRIVATE ) if (NINTENDO_SWITCH) + set(ASSETS ${CMAKE_SOURCE_DIR}/assets) + # create romfs folder dkp_add_asset_target(main_romfs ${CMAKE_CURRENT_BINARY_DIR}/romfs) + configure_file(${ASSETS}/icons/icons8-mac-folder-64.png ${CMAKE_CURRENT_BINARY_DIR}/romfs/icons/icons8-mac-folder-64.png COPYONLY) + configure_file(${ASSETS}/icons/icons8-visual-game-boy-48.png ${CMAKE_CURRENT_BINARY_DIR}/romfs/icons/icons8-visual-game-boy-48.png COPYONLY) + # setup nacp nx_generate_nacp(main.nacp NAME "Notorious BEEG" @@ -146,13 +163,13 @@ if (NINTENDO_SWITCH) # create nro (final binary) nx_create_nro(main - ICON ${CMAKE_SOURCE_DIR}/src/frontend/backend/nx/icon.jpg + ICON ${ASSETS}/icons/icon.jpg NACP main.nacp ROMFS main_romfs ) # compile and add shaders to romfs - set(SHADER_FOLDER ${CMAKE_SOURCE_DIR}/src/frontend/backend/nx/shaders) + set(SHADER_FOLDER ${ASSETS}/shaders/nx) nx_add_shader_program(imgui_fsh ${SHADER_FOLDER}/imgui_fsh.glsl frag) nx_add_shader_program(imgui_vsh ${SHADER_FOLDER}/imgui_vsh.glsl vert) @@ -163,4 +180,5 @@ if (NINTENDO_SWITCH) imgui_fsh imgui_vsh ) + endif() diff --git a/src/frontend/backend/backend.hpp b/src/frontend/backend/backend.hpp index ae39fc4..08bcb6c 100644 --- a/src/frontend/backend/backend.hpp +++ b/src/frontend/backend/backend.hpp @@ -13,7 +13,11 @@ namespace sys::backend { auto quit() -> void; auto poll_events() -> void; +// used for setup auto render_begin() -> void; +// render anything specific to the backend +auto render() -> void; +// flip the screen auto render_end() -> void; auto get_texture(TextureID id) -> void*; diff --git a/src/frontend/backend/nx/CMakeLists.txt b/src/frontend/backend/nx/CMakeLists.txt index 656c591..59bb7ec 100644 --- a/src/frontend/backend/nx/CMakeLists.txt +++ b/src/frontend/backend/nx/CMakeLists.txt @@ -6,11 +6,13 @@ add_library(backend ftpd_imgui/imgui_nx.cpp audio/audio.cpp + fs.cpp ) target_link_libraries(backend PRIVATE imgui) target_link_libraries(backend PRIVATE GBA) -target_link_libraries(backend PRIVATE deko3dd) +target_link_libraries(backend PRIVATE deko3d) +target_link_libraries(backend PRIVATE stb) # why is this even needed??? target_include_directories(backend PRIVATE ${DEVKITPRO}/portlibs/switch/include) diff --git a/src/frontend/backend/nx/audio/audio.cpp b/src/frontend/backend/nx/audio/audio.cpp index b04d4a5..8368ea8 100644 --- a/src/frontend/backend/nx/audio/audio.cpp +++ b/src/frontend/backend/nx/audio/audio.cpp @@ -18,12 +18,11 @@ #include "../../../system.hpp" #include -#include -#include #include #include #include #include +#include #include #include #include @@ -74,6 +73,7 @@ constexpr uint8_t sink_channels[channels]{ 0, 1 }; int sink_id; int mem_pool_id; +// mempool that is aligned for audio std::vector> mem_pool; std::size_t spec_size; @@ -82,7 +82,7 @@ std::vector temp_buf; std::size_t temp_buffer_index; // this is what we copy the temp_buf into per audio frame -std::vector wave_buffers; +std::array wave_buffers; std::size_t wave_buffer_index; std::jthread thread; @@ -103,14 +103,8 @@ auto audio_callback(void* user, int16_t left, int16_t right) -> void auto audio_thread(std::stop_token token) -> void { - for (;;) + while (!token.stop_requested()) { - if (token.stop_requested()) - { - printf("[INFO] stop token requested in loop!\n"); - return; - } - auto& buffer = wave_buffers[wave_buffer_index]; if (buffer.state == AudioDriverWaveBufState_Free || buffer.state == AudioDriverWaveBufState_Done) { @@ -125,9 +119,10 @@ auto audio_thread(std::stop_token token) -> void // stretch last sample if (temp_buffer_index >= 2 && temp_buffer_index < temp_buf.size()) { - for (size_t i = temp_buffer_index; i < temp_buf.size(); i++) + for (size_t i = temp_buffer_index; i < temp_buf.size(); i += 2) { - data16[i] = temp_buf[temp_buffer_index - 2]; + data16[i+0] = temp_buf[temp_buffer_index - 2]; // left + data16[i+1] = temp_buf[temp_buffer_index - 1]; // right } } @@ -165,8 +160,6 @@ namespace nx::audio { auto init() -> bool { - wave_buffers.resize(2); - if (auto r = audrenInitialize(&cfg); R_FAILED(r)) { printf("failed to init audren\n"); @@ -216,9 +209,9 @@ auto init() -> bool spec_size = sizeof(std::int16_t) * channels * samples; const auto mem_pool_size = ((spec_size * wave_buffers.size()) + (AUDREN_MEMPOOL_ALIGNMENT - 1)) &~ (AUDREN_MEMPOOL_ALIGNMENT - 1); mem_pool.resize(mem_pool_size); - // LOG("unaliged size 0x%lX aligned size: 0x%lX vector size: 0x%lX\n", spec_size * wave_buffers.size(), ((spec_size * wave_buffers.size()) + (AUDREN_MEMPOOL_ALIGNMENT - 1)) &~ (AUDREN_MEMPOOL_ALIGNMENT - 1), mem_pool.size()); - for (std::size_t i = 0; i < wave_buffers.size(); ++i) { + for (std::size_t i = 0; i < wave_buffers.size(); ++i) + { wave_buffers[i].data_adpcm = mem_pool.data(); wave_buffers[i].size = mem_pool.size(); wave_buffers[i].start_sample_offset = i * samples; @@ -235,8 +228,15 @@ auto init() -> bool } wave_buffer_index = 0; + + // set the buffer size temp_buf.resize(spec_size / 2); // this is s16 + std::ranges::fill(temp_buf, 0); + + // start audio thread thread = std::jthread(audio_thread); + + // set callback for emu sys::System::gameboy_advance.set_audio_callback(audio_callback); return true; diff --git a/src/frontend/backend/nx/backend_nx.cpp b/src/frontend/backend/nx/backend_nx.cpp index 2c3609f..d5290b7 100644 --- a/src/frontend/backend/nx/backend_nx.cpp +++ b/src/frontend/backend/nx/backend_nx.cpp @@ -6,8 +6,13 @@ #include "../backend.hpp" #include "../../system.hpp" #include "audio/audio.hpp" +#include "fs.hpp" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#include #include -#include #include #include #include @@ -52,9 +57,11 @@ namespace { struct Texture { - auto init() -> void; + auto init(int w, int h, int bpp, DkImageFormat f, int id, void* data = nullptr) -> void; + auto init(const char* file, int id) -> void; + auto quit() -> void; - auto update(std::uint16_t data[160][240]) -> void; + auto update(void* data) -> void; [[nodiscard]] auto get_image_id() { return image_id; } [[nodiscard]] auto get_sampler_id() { return sampler_id; } @@ -65,18 +72,18 @@ struct Texture // todo: make texture creation like a factory which returns an // id (index into texture array) - static inline const auto image_id = 2; - static inline const auto sampler_id = 1; + int image_id; + int sampler_id; // thse should be set in init() - static inline const auto format = DkImageFormat_RGB5_Unorm; - static inline const auto width = 240; - static inline const auto height = 160; - static inline const auto size = width * height * sizeof(uint16_t); + DkImageFormat format; + int width; + int height; + int size; }; constexpr auto MAX_SAMPLERS = 2; -constexpr auto MAX_IMAGES = 8; +constexpr auto MAX_IMAGES = std::to_underlying(TextureID::max) + 2; constexpr auto FB_NUM = 2u; constexpr auto CMDBUF_SIZE = 1024 * 1024; @@ -98,11 +105,13 @@ dk::ImageDescriptor *s_imageDescriptors = nullptr; dk::UniqueQueue s_queue; dk::UniqueSwapchain s_swapchain; -Texture textures[1]; +Texture textures[std::to_underlying(TextureID::max)]; PadState pad; AppletHookCookie appletHookCookie; +bool show_fs_browser{false}; + auto applet_show_error_message(const char* message, const char* long_message) { ErrorApplicationConfig cfg; @@ -360,8 +369,15 @@ void exit_deko3d(void) { } // SOURCE: https://github.com/joel16/NX-Shell/blob/5a5067afeb6b18c0d2bb4d7b16f71899a768012a/source/textures.cpp#L150 -auto Texture::init() -> void +auto Texture::init(int w, int h, int bpp, DkImageFormat f, int id, void* data) -> void { + width = w; + height = h; + format = f; + image_id = 1 + id; + sampler_id = 1; + size = width * height * bpp; + s_queue.waitIdle(); dk::ImageLayout layout; @@ -379,7 +395,14 @@ auto Texture::init() -> void .setFlags(DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image) .create(); - std::memset(memBlock.getCpuAddr(), 0, size); + if (data != nullptr) + { + std::memcpy(memBlock.getCpuAddr(), data, size); + } + else + { + std::memset(memBlock.getCpuAddr(), 0, size); + } image.initialize(layout, s_imageMemBlock, 0); s_imageDescriptors[image_id].initialize(image); @@ -391,14 +414,38 @@ auto Texture::init() -> void s_queue.submitCommands(s_cmdBuf[0].finishList()); - s_samplerDescriptors[sampler_id].initialize(dk::Sampler{} - .setFilter(DkFilter_Nearest, DkFilter_Nearest) - .setWrapMode(DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge)); + // this is a hack because i cba to write good code + static bool sample_already_init = false; + if (!sample_already_init) + { + s_samplerDescriptors[sampler_id].initialize(dk::Sampler{} + .setFilter(DkFilter_Nearest, DkFilter_Nearest) + .setWrapMode(DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge, DkWrapMode_ClampToEdge)); + } s_queue.waitIdle(); } -auto Texture::update(std::uint16_t data[160][240]) -> void +auto Texture::init(const char* file, int id) -> void +{ + // Load from disk into a raw RGBA buffer + int image_width = 0; + int image_height = 0; + int bpp = 0; + unsigned char* image_data = stbi_load(file, &image_width, &image_height, NULL, 4); + if (image_data == NULL) + { + std::printf("failed to load image: %s\n", file); + return; + } + + init(image_width, image_height, 4, DkImageFormat_RGBA8_Unorm, id, image_data); + stbi_image_free(image_data); + + std::printf("loaded file\n"); +} + +auto Texture::update(void* data) -> void { s_queue.waitIdle(); // is this needed? @@ -442,11 +489,14 @@ auto Texture::quit() -> void // init deko3d for imgui imgui::deko3d::init(s_device, s_queue, s_cmdBuf[0], s_samplerDescriptors[0], s_imageDescriptors[0], dkMakeTextureHandle(0, 0), FB_NUM); - // init all textures being used - for (auto& texture : textures) - { - texture.init(); - } + textures[std::to_underlying(TextureID::emu)].init(240, 160, sizeof(u16), DkImageFormat_RGB5_Unorm, std::to_underlying(TextureID::emu)); + textures[std::to_underlying(TextureID::layer0)].init(240, 160, sizeof(u16), DkImageFormat_RGB5_Unorm, std::to_underlying(TextureID::layer0)); + textures[std::to_underlying(TextureID::layer1)].init(240, 160, sizeof(u16), DkImageFormat_RGB5_Unorm, std::to_underlying(TextureID::layer1)); + textures[std::to_underlying(TextureID::layer2)].init(240, 160, sizeof(u16), DkImageFormat_RGB5_Unorm, std::to_underlying(TextureID::layer2)); + textures[std::to_underlying(TextureID::layer3)].init(240, 160, sizeof(u16), DkImageFormat_RGB5_Unorm, std::to_underlying(TextureID::layer3)); + + textures[std::to_underlying(TextureID::folder_icon)].init("romfs:/icons/icons8-mac-folder-64.png", std::to_underlying(TextureID::folder_icon)); + textures[std::to_underlying(TextureID::file_icon)].init("romfs:/icons/icons8-visual-game-boy-48.png", std::to_underlying(TextureID::file_icon)); // setup callback for applet events appletHook(&appletHookCookie, appplet_hook_calback, nullptr); @@ -522,14 +572,122 @@ auto poll_events() -> void System::loadstate(System::rom_path); } + if (!!(down & HidNpadButton_Y)) + { + show_fs_browser ^= 1; + if (System::has_rom) + { + // if showing browser, stop running, else run + System::emu_run = !show_fs_browser; + } + } + // this only update inputs and screen size // so it should be called in poll events imgui::nx::newFrame(&pad); } +void show_debug_monitor() +{ + static int corner = -1; + ImGuiIO& io = ImGui::GetIO(); + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; + if (corner != -1) + { + const float PAD = 10.0f; + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImVec2 work_pos = viewport->WorkPos; // Use work area to avoid menu-bar/task-bar, if any! + ImVec2 work_size = viewport->WorkSize; + ImVec2 window_pos, window_pos_pivot; + window_pos.x = (corner & 1) ? (work_pos.x + work_size.x - PAD) : (work_pos.x + PAD); + window_pos.y = (corner & 2) ? (work_pos.y + work_size.y - PAD) : (work_pos.y + PAD); + window_pos_pivot.x = (corner & 1) ? 1.0f : 0.0f; + window_pos_pivot.y = (corner & 2) ? 1.0f : 0.0f; + ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); + window_flags |= ImGuiWindowFlags_NoMove; + } + + ImGui::SetNextWindowBgAlpha(0.50f); // Transparent background + if (ImGui::Begin("NX overlay", nullptr, window_flags)) + { + ImGui::Text("BEEG Debug Monitor\n"); + ImGui::Separator(); + if (ImGui::CollapsingHeader("Memory")) + { + // SOURCE: https://switchbrew.org/wiki/SVC#SystemInfoType + constexpr u64 TotalPhysicalMemorySize_Application = 0; + constexpr u64 TotalPhysicalMemorySize_Applet = 1; + constexpr u64 TotalPhysicalMemorySize_System = 2; + constexpr u64 TotalPhysicalMemorySize_SystemUnsafe = 3; + + constexpr u64 UsedPhysicalMemorySize_Application = 0; + constexpr u64 UsedPhysicalMemorySize_Applet = 1; + constexpr u64 UsedPhysicalMemorySize_System = 2; + constexpr u64 UsedPhysicalMemorySize_SystemUnsafe = 3; + + constexpr u8 svcGetSystemInfo_id = 0x6F; + + // check if we have priv to use the sys call! + if (envIsSyscallHinted(svcGetSystemInfo_id)) + { + u64 total_application = 0; + u64 total_applet = 0; + u64 total_system = 0; + u64 total_unsafe = 0; + + u64 used_application = 0; + u64 used_applet = 0; + u64 used_system = 0; + u64 used_unsafe = 0; + + svcGetSystemInfo(&total_application, 0, INVALID_HANDLE, TotalPhysicalMemorySize_Application); + svcGetSystemInfo(&total_applet, 0, INVALID_HANDLE, TotalPhysicalMemorySize_Applet); + svcGetSystemInfo(&total_system, 0, INVALID_HANDLE, TotalPhysicalMemorySize_System); + svcGetSystemInfo(&total_unsafe, 0, INVALID_HANDLE, TotalPhysicalMemorySize_SystemUnsafe); + + svcGetSystemInfo(&used_application, 1, INVALID_HANDLE, UsedPhysicalMemorySize_Application); + svcGetSystemInfo(&used_applet, 1, INVALID_HANDLE, UsedPhysicalMemorySize_Applet); + svcGetSystemInfo(&used_system, 1, INVALID_HANDLE, UsedPhysicalMemorySize_System); + svcGetSystemInfo(&used_unsafe, 1, INVALID_HANDLE, UsedPhysicalMemorySize_SystemUnsafe); + + ImGui::Text("[Application] %.2f MB\t%.2f MB\n", (double)used_application / 1024.0 / 1024.0, (double)total_application / 1024.0 / 1024.0); + ImGui::Text("[Applet] %.2f MB\t%.2f MB\n", (double)used_applet / 1024.0 / 1024.0, (double)total_applet / 1024.0 / 1024.0); + ImGui::Text("[System] %.2f MB\t%.2f MB\n", (double)used_system / 1024.0 / 1024.0, (double)total_system / 1024.0 / 1024.0); + ImGui::Text("[SystemUnsafe] %.2f MB\t%.2f MB\n", (double)used_unsafe / 1024.0 / 1024.0, (double)total_unsafe / 1024.0 / 1024.0); + } + } + + if (ImGui::CollapsingHeader("Audio")) + { + + } + + if (ImGui::CollapsingHeader("Display")) + { + + } + + if (ImGui::CollapsingHeader("Misc")) + { + + } + } + ImGui::End(); +} + auto render_begin() -> void { - // imgui::nx::newFrame(&pad); +} + +auto render() -> void +{ + show_debug_monitor(); + + if (!sys::System::has_rom || show_fs_browser) + { + // fs returns true when a rom has been loaded + show_fs_browser = !nx::fs::render(); + } } auto render_end() -> void @@ -568,14 +726,12 @@ auto render_end() -> void auto get_texture(TextureID id) -> void* { - assert(id == TextureID::emu && "only emu texture is impl!"); auto& texture = textures[std::to_underlying(id)]; return imgui::deko3d::makeTextureID(dkMakeTextureHandle(texture.get_image_id(), texture.get_sampler_id())); } auto update_texture(TextureID id, std::uint16_t pixels[160][240]) -> void { - assert(id == TextureID::emu && "only emu texture is impl!"); textures[std::to_underlying(id)].update(pixels); } @@ -601,6 +757,21 @@ auto toggle_fullscreen() -> void auto open_url(const char* url) -> void { + WebCommonConfig config{}; + + auto rc = webPageCreate(&config, url); + + if (R_SUCCEEDED(rc)) + { + rc = webConfigSetWhitelist(&config, "^http*"); + if (R_SUCCEEDED(rc)) + { + rc = webConfigShow(&config, nullptr); + if (R_SUCCEEDED(rc)) + { + } + } + } } } // sys::backend diff --git a/src/frontend/backend/nx/fs.cpp b/src/frontend/backend/nx/fs.cpp new file mode 100644 index 0000000..eb729b5 --- /dev/null +++ b/src/frontend/backend/nx/fs.cpp @@ -0,0 +1,177 @@ +// Copyright 2022 TotalJustice. +// SPDX-License-Identifier: GPL-3.0-only + +#include "fs.hpp" +#include "../backend.hpp" +#include "../../system.hpp" +#include "imgui_internal.h" +#include +#include +#include +#include + +namespace nx::fs { +namespace { + +struct Entry +{ + std::filesystem::path path; + std::string filename; + bool is_dir; +}; + +std::filesystem::path current_path; +std::vector entries; +bool in_new_dir{true}; + +auto scan() -> void +{ + entries.clear(); + + if (current_path.empty()) + { + #if defined(__SWITCH__) + current_path = "/"; + #else + current_path = std::filesystem::current_path(); + #endif + } + + // walk up dir + if (current_path.has_parent_path()) + { + entries.emplace_back(current_path.parent_path(), "../", true); + } + + // should probably have a max here because uhhh + // a folder having 10000+ entries would suck super bad + for (const auto& entry : std::filesystem::directory_iterator{current_path}) + { + const auto& path = entry.path(); + const auto filename = path.filename().string(); + + if (filename.starts_with('.')) + { + continue; + } + + if (entry.is_directory()) + { + entries.emplace_back(path, filename, true); + } + else if (entry.is_regular_file()) + { + if (!path.has_extension()) + { + continue; + } + + const auto extension = path.extension(); + + if (extension != ".gba" && extension != ".zip") + { + continue; + } + + entries.emplace_back(path, filename, false); + } + } +} + +} // namespace + +auto render() -> bool +{ + // ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings; + const auto window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; + + // fill the screen (as this fs is intended for consoles!) + const auto [w, h] = sys::backend::get_window_size(); + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(ImVec2(w, h)); + + if (ImGui::Begin("idk what this thing is", nullptr, window_flags)) + { + // set title + ImGui::Text("Path: %s\n", (current_path).string().c_str()); + ImGui::Spacing(); + + const auto table_flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_ScrollY; + + // basically i dont want the Begin() to have focus, i want the table + // to alwways be focused. + ImGui::SetNextWindowFocus(); + if (ImGui::BeginTable("##table fs", 1, table_flags)) + { + // this stops back button being spammed + static bool back_pressed = false; + + if (ImGui::IsNavInputDown(ImGuiNavInput_Cancel)) + { + if (!back_pressed) + { + ImGui::GetIO().NavInputs[ImGuiNavInput_Cancel] = 0; + + if (current_path.has_parent_path()) + { + printf("has parent path\n"); + current_path = current_path.parent_path(); + in_new_dir = true; + } + + back_pressed = true; + } + } + else + { + back_pressed = false; + } + + // if we are in a new directory, scan the entries + if (in_new_dir) + { + scan(); + in_new_dir = false; + } + + const auto folder_icon = sys::backend::get_texture(sys::TextureID::folder_icon); + const auto file_icon = sys::backend::get_texture(sys::TextureID::file_icon); + + for (const auto& entry : entries) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + const auto texture = entry.is_dir ? folder_icon : file_icon; + if (texture != nullptr) + { + ImGui::Image(texture, ImVec2(75, 50)); + } + + ImGui::SameLine(); + + if (ImGui::Selectable(entry.filename.c_str(), false, ImGuiSelectableFlags_SpanAllColumns, ImVec2(0, 50))) + { + if (entry.is_dir) + { + current_path = entry.path; + in_new_dir = true; + } + else if (sys::System::loadrom(entry.path.string())) + { + ImGui::EndTable(); + ImGui::End(); + return true; + } + } + } + + ImGui::EndTable(); + } + } + ImGui::End(); + // display the entries + return false; +} + +} // namespace nx::fs diff --git a/src/frontend/backend/nx/fs.hpp b/src/frontend/backend/nx/fs.hpp new file mode 100644 index 0000000..ce2cb42 --- /dev/null +++ b/src/frontend/backend/nx/fs.hpp @@ -0,0 +1,14 @@ +// Copyright 2022 TotalJustice. +// SPDX-License-Identifier: GPL-3.0-only + +#include + +// this should probably be a generic impl for all +// systems that don't have os browser +// for now, switch specific +namespace nx::fs { + +// returns true if a file is selected +auto render() -> bool; + +} // namespace nx::fs diff --git a/src/frontend/backend/sdl2/backend_sdl2.cpp b/src/frontend/backend/sdl2/backend_sdl2.cpp index ae93c7c..c8ffaaa 100644 --- a/src/frontend/backend/sdl2/backend_sdl2.cpp +++ b/src/frontend/backend/sdl2/backend_sdl2.cpp @@ -528,6 +528,10 @@ auto render_begin() -> void ImGui_ImplSDL2_NewFrame(); } +auto render() -> void +{ +} + auto render_end() -> void { SDL_RenderClear(renderer); diff --git a/src/frontend/system.cpp b/src/frontend/system.cpp index 5ee5b4f..c2da68a 100644 --- a/src/frontend/system.cpp +++ b/src/frontend/system.cpp @@ -326,11 +326,6 @@ auto System::emu_set_button(gba::Button button, bool down) -> void auto System::init(int argc, char** argv) -> bool { #if defined(__SWITCH__) - if (!System::loadrom("/roms/gba/doom.gba")) - { - std::printf("failed to loadrom\n"); - return false; - } #else if (argc < 2) { @@ -660,7 +655,7 @@ auto System::emu_update_texture() -> void auto System::emu_render() -> void { const auto flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBringToFrontOnFocus; - ImGui::SetNextWindowPos(ImVec2(0, emu_rect.y)); + ImGui::SetNextWindowPos(ImVec2(emu_rect.x, emu_rect.y)); ImGui::SetNextWindowSize(ImVec2(emu_rect.w, emu_rect.h)); ImGui::SetNextWindowSizeConstraints({0, 0}, ImVec2(emu_rect.w, emu_rect.h)); @@ -721,6 +716,8 @@ auto System::run_render() -> void resize_to_menubar(); + backend::render(); + // Rendering (REMEMBER TO RENDER IMGUI STUFF [BEFORE] THIS LINE) ImGui::Render(); backend::render_end(); @@ -774,15 +771,39 @@ auto System::resize_to_menubar() -> void System::resize_emu_screen(); } +int get_scale(int w, int h) +{ + const auto scale_w = w / 240; + const auto scale_h = h / 160; + + return std::min(scale_w, scale_h); +} + auto System::resize_emu_screen() -> void { const auto [w, h] = backend::get_window_size(); // update rect - emu_rect.x = 0; - emu_rect.y = menubar_height; - emu_rect.w = w; - emu_rect.h = h-menubar_height; + if (emu_stretch) + { + emu_rect.x = 0; + emu_rect.y = menubar_height; + emu_rect.w = w; + emu_rect.h = h-menubar_height; + } + else + { + const auto min_scale = get_scale(w, h); + + emu_rect.w = 240 * min_scale; + emu_rect.h = 160 * min_scale; + emu_rect.x = (w - emu_rect.w) / 2; + emu_rect.y = (h - emu_rect.h) / 2; + // emu_rect.x = 0; + // emu_rect.y = menubar_height; + // emu_rect.w = w; + // emu_rect.h = h-menubar_height; + } } } // namespace sys diff --git a/src/frontend/system.hpp b/src/frontend/system.hpp index a9154b7..9b6aad9 100644 --- a/src/frontend/system.hpp +++ b/src/frontend/system.hpp @@ -20,6 +20,10 @@ enum class TextureID layer1, layer2, layer3, + folder_icon, + file_icon, + + max, // not real texture }; struct Rect @@ -61,7 +65,7 @@ struct System static inline std::string rom_path{}; static inline bool has_rom{false}; static inline bool running{true}; - static inline bool emu_run{true}; + static inline bool emu_run{false}; static inline bool show_debug_window{false}; static inline bool show_demo_window{false}; static inline bool show_menubar{true}; @@ -74,6 +78,9 @@ struct System static inline auto start_time = std::chrono::high_resolution_clock::now(); #endif + // set to true to fill the screen + static inline bool emu_stretch{false}; + struct Layer { Layer(TextureID i) : id{i} {};