#include "RendererBackend.h" #include #include #include "IconsForkAwesome.h" #include "config.h" #include #include #include #include #include "theme.h" #include "imgui_stdlib.h" #include "imgui_impl_sdl2.h" #include "imgui_impl_sdlrenderer2.h" #include "base85.h" #include #include #include #include #include using std::vector; using namespace Looper::Options; void RendererBackend::on_resize() { #ifdef __EMSCRIPTEN__ int32_t x, y; get_size(&x, &y); SDL_SetWindowSize(window, (int)x, (int)y); UpdateScale(); #endif } static RendererBackend *renderer_backend; void RendererBackend::resize_static() { renderer_backend->resize_needed = true; } void main_loop() { #ifdef __EMSCRIPTEN__ if (!renderer_backend->started) { renderer_backend->BackendInit(); } #endif renderer_backend->LoopFunction(); #ifdef __EMSCRIPTEN__ if (renderer_backend->done) { renderer_backend->BackendDeinit(); emscripten_cancel_main_loop(); } #endif } struct FontData { const ImWchar *ranges; std::map> data; void Init(const ImWchar *ranges_in, std::map> data_in) { ranges = ranges_in; data = data_in; } FontData(const ImWchar *ranges, std::initializer_list>> data) { std::map> out_data; for (auto pair : data) { out_data[pair.first] = pair.second; } Init(ranges, out_data); } FontData(const ImWchar *ranges, std::initializer_list> data) { std::map> out_data; for (auto tuple : data) { std::pair outPair = {std::get<1>(tuple), std::get<2>(tuple)}; out_data[std::get<0>(tuple)] = outPair; } Init(ranges, out_data); } FontData(const ImWchar *ranges, std::map> data) { Init(ranges, data); } }; void RendererBackend::BackendDeinit() { ImGuiIO& io = ImGui::GetIO(); (void)io; // Cleanup ImGui_ImplSDLRenderer2_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); SDL_DestroyRenderer(rend); SDL_DestroyWindow(window); IMG_Quit(); SDL_Quit(); free((void*)io.IniFilename); Deinit(); renderer_backend = nullptr; } bool RendererBackend::isTouchScreenMode() { return touchScreenModeOverride.value_or(SDL_GetNumTouchDevices() > 0); } void RendererBackend::QueueUpdateScale() { update_scale = true; } void RendererBackend::LoopFunction() { if (update_scale) { UpdateScale(); update_scale = false; } #ifndef __EMSCRIPTEN__ SDL_RenderSetVSync(rend, vsync ? 1 : 0); #endif ImGuiIO& io = ImGui::GetIO(); (void)io; if (resize_needed) { on_resize(); } auto next_frame = std::chrono::steady_clock::now() + std::chrono::milliseconds(1000 / (framerate == 0 ? 60 : framerate)); // Poll and handle events (inputs, window resize, etc.) // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. SDL_Event event; while (SDL_PollEvent(&event)) { ImGui_ImplSDL2_ProcessEvent(&event); if (event.type == SDL_QUIT) done = true; if (event.type == SDL_WINDOWEVENT) { if (event.window.event == SDL_WINDOWEVENT_RESIZED) { window_width = event.window.data1 / scale; window_height = event.window.data2 / scale; //SDL_GetWindowSize(window, &window_width, &window_height); } if (event.window.event == SDL_WINDOWEVENT_DISPLAY_CHANGED) { UpdateScale(); } if (event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) { done = true; } } if (event.type == SDL_DROPFILE) { if (event.drop.file != NULL) { Drop(std::string(event.drop.file)); free(event.drop.file); } } } bool touchScreenMode = isTouchScreenMode(); if (touchScreenMode) { io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen; } else { io.ConfigFlags &= ~ImGuiConfigFlags_IsTouchScreen; } // Start the Dear ImGui frame ImGui_ImplSDLRenderer2_NewFrame(); ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); // Run the GUI GuiFunction(); // Rendering ImGui::Render(); SDL_SetRenderDrawColor(rend, (Uint8)(clear_color.x * clear_color.w * 255), (Uint8)(clear_color.y * clear_color.w * 255), (Uint8)(clear_color.z * clear_color.w * 255), (Uint8)(clear_color.w * 255)); SDL_RenderClear(rend); // Tell ImGui to render. ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData()); // Swap the buffers, and do VSync if enabled. SDL_RenderPresent(rend); // If not doing VSync, wait until the next frame needs to be rendered. if (!vsync) { std::this_thread::sleep_until(next_frame); } } std::map add_font(FontData data_vec, double scale) { ImGuiIO& io = ImGui::GetIO(); std::map output; for (auto value : data_vec.data) { ImFont* font = nullptr; std::string id = value.first; const char *data = value.second.first; double size = value.second.second * scale; { ImFontConfig font_cfg = ImFontConfig(); font_cfg.SizePixels = size; font_cfg.OversampleH = font_cfg.OversampleV = 1; font_cfg.PixelSnapH = true; if (font_cfg.SizePixels <= 0.0f) font_cfg.SizePixels = 13.0f * 1.0f; //font_cfg.EllipsisChar = (ImWchar)0x0085; //font_cfg.GlyphOffset.y = 1.0f * IM_FLOOR(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units const char *ttf_compressed_base85 = data; const ImWchar *glyph_ranges = data_vec.ranges; font = io.Fonts->AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, glyph_ranges); } { ImFontConfig config; config.MergeMode = true; config.GlyphMinAdvanceX = size; config.SizePixels = size; config.DstFont = font; static const ImWchar icon_ranges[] = {ICON_MIN_FK, ICON_MAX_FK, 0}; io.Fonts->AddFontFromMemoryCompressedBase85TTF(forkawesome_compressed_data_base85, float(size), &config, icon_ranges); } output[id] = font; } return output; } RendererBackend::RendererBackend() { } RendererBackend::~RendererBackend() { DEBUG.writeln("Renderer backend destructor run."); renderer_backend = nullptr; } void RendererBackend::SetWindowTitle(const char *title) { SDL_SetWindowTitle(window, title); } void RendererBackend::GuiFunction() { // Do nothing by default. } void RendererBackend::UpdateScale() { double prevScale = scale; const double defaultDPI = #ifdef __APPLE__ 72.0; #else 96.0; #endif if (scaleOverride.has_value()) { scale = scaleOverride.value(); } else { #ifdef __EMSCRIPTEN__ scale = get_dpi(); #else float dpi = defaultDPI; if (SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(window), NULL, &dpi, NULL) == 0){ scale = dpi / defaultDPI; } else { WARNING.writeln("DPI couldn't be determined!"); scale = 1.0; } #ifndef __ANDROID__ SDL_SetWindowSize(window, window_width * scale, window_height * scale); #endif #endif } AddFonts(); OnScale((float)scale); } void RendererBackend::OnScale(float scale) { theme->Apply(accent_color, scale); } void RendererBackend::SetWindowSize(int w, int h) { #if !(defined(__ANDROID__) || defined(__EMSCRIPTEN__)) window_width = w; window_height = h; SDL_SetWindowSize(window, w * scale, h * scale); #endif } void RendererBackend::GetWindowsize(int *w, int *h) { int ww, wh; SDL_GetWindowSize(window, &ww, &wh); ww /= scale; wh /= scale; if (w) *w = ww; if (h) *h = wh; } void RendererBackend::AddFonts() { ImGui_ImplSDLRenderer2_DestroyFontsTexture(); auto& io = ImGui::GetIO(); (void)io; io.Fonts->Clear(); std::string font_type = get_option("font_type", "default"); std::string default_font = "default"; std::string jp_font = "japanese"; auto glyph_ranges_default = io.Fonts->GetGlyphRangesDefault(); auto glyph_ranges_jp = io.Fonts->GetGlyphRangesJapanese(); FontData latin(glyph_ranges_default, {{"normal", notosans_regular_compressed_data_base85, 13}, {"title", notosans_thin_compressed_data_base85, 32}}); FontData jp(glyph_ranges_jp, {{"normal", notosansjp_regular_compressed_data_base85, 13}, {"title", notosansjp_thin_compressed_data_base85, 32}}); std::map font_map; if (font_type == jp_font) { font_map = add_font(jp, scale); } else { font_map = add_font(latin, scale); } title = font_map["title"]; io.FontDefault = font_map["normal"]; ImGui_ImplSDLRenderer2_CreateFontsTexture(); } #ifdef __EMSCRIPTEN__ static EM_BOOL resize_callback(int event_type, const EmscriptenUiEvent *event, void *userdata) { RendererBackend::resize_static(); return EM_FALSE; } #endif void RendererBackend::BackendInit() { setup_locale("neko_player"); DEBUG.writefln("Loaded locale '%s' from '%s'...", CURRENT_LANGUAGE, LOCALE_DIR); DEBUG.writefln("Locale name: %s", _TR_CTX("Language name", "English (United States)")); bool enable_kms = std::getenv("LAP_KMS") != nullptr; SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "false"); SDL_SetHint(SDL_HINT_APP_NAME, NAME); // Setup SDL if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { ERROR.writefln("Error: %s", SDL_GetError()); throw std::exception(); } if (std::string(SDL_GetCurrentVideoDriver()) == "KMSDRM") { enable_kms = true; } IMG_Init(IMG_INIT_PNG|IMG_INIT_WEBP); #ifdef __ANDROID__ prefPath = SDL_AndroidGetInternalStoragePath(); #else prefPath = SDL_GetPrefPath("Catmeow72", NAME); #endif Theme::prefPath = prefPath; // From 2.0.18: Enable native IME. #ifdef SDL_HINT_IME_SHOW_UI SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); #endif SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); SDL_CreateWindowAndRenderer(window_width, window_height, window_flags, &window, &rend); #ifndef __ANDROID__ SDL_SetWindowMinimumSize(window, window_width, window_height); if (enable_kms) { SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); } #endif SDL_EventState(SDL_DROPFILE, SDL_ENABLE); const vector icon_data = DecodeBase85(icon_compressed_data_base85); SDL_Surface* icon = IMG_Load_RW(SDL_RWFromConstMem(icon_data.data(), icon_data.size()), 1); SDL_SetWindowIcon(window, icon); // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; if (SDL_GetNumTouchDevices() > 0) { io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen; } //io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; io.IniFilename = strdup((std::string(prefPath) + "imgui.ini").c_str()); if (enable_kms) { io.MouseDrawCursor = true; } //io.ConfigViewportsNoAutoMerge = true; //io.ConfigViewportsNoTaskBarIcon = true; //ImGui::StyleColorsLight(); // Setup Platform/Renderer backends ImGui_ImplSDL2_InitForSDLRenderer(window, rend); ImGui_ImplSDLRenderer2_Init(rend); // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! //io.Fonts->AddFontDefault(); //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); //IM_ASSERT(font != nullptr); theme = new Theme(false); UpdateScale(); #ifdef __ANDROID__ userdir = SDL_AndroidGetExternalStoragePath(); #else userdir = std::getenv( #ifdef _WIN32 "UserProfile" #else "HOME" #endif ); #endif #ifndef __EMSCRIPTEN__ SDL_RenderSetVSync(rend, vsync ? 1 : 0); #endif theme->Apply(accent_color, (float)scale); Init(); SDL_ShowWindow(window); started = true; } int RendererBackend::Run() { framerate = 60; started = false; renderer_backend = this; #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(&main_loop, 0, 1); #else try { BackendInit(); } catch (std::exception) { return -1; } started = true; while (!done) { LoopFunction(); } BackendDeinit(); #endif return 0; } void RendererBackend::Init() { // Do nothing by default. } void RendererBackend::Deinit() { // Do nothing by default. } void RendererBackend::Drop(std::string file) { // Do nothing by default. }