#include "CustomEvents.hpp" #include "Helpers/SCollision.hpp" #include "Helpers/StringHelpers.hpp" #include "Helpers/FileDownloader.hpp" #include "VersionConstants.hpp" #include "include/gpu/GpuTypes.h" #include #include #include #include #include #include #include #include #ifdef USE_BACKEND_OPENGL #ifndef __EMSCRIPTEN__ #ifdef USE_BACKEND_OPENGLES_3_0 #define GLAD_GLES2_IMPLEMENTATION #include #elif USE_BACKEND_OPENGL_2_1 #define GLAD_GL_IMPLEMENTATION #include #else #define GLAD_GL_IMPLEMENTATION #include #endif #endif #endif #include "cereal/archives/portable_binary.hpp" #include #include #include #ifdef USE_SKIA_BACKEND_GANESH #include #include #include #elif USE_SKIA_BACKEND_GRAPHITE #include #endif #include #include #include "MainProgram.hpp" #include #define SDL_MAIN_USE_CALLBACKS #include #include #include #include #include #include #include #include #include #include "DrawingProgram/DrawingProgramCache.hpp" #include #ifdef __EMSCRIPTEN__ #include #include #include #include #include #include #include #include #elif USE_BACKEND_VULKAN #ifdef USE_SKIA_BACKEND_GRAPHITE #include "VulkanContext/GraphiteNativeVulkanWindowContext.h" #elif USE_SKIA_BACKEND_GANESH #include "VulkanContext/VulkanWindowContext.h" #endif #include #include #elif USE_BACKEND_OPENGL #include #include #include #include #endif #ifdef __linux__ #include #include #elif _WIN32 #include #endif #include #include "SwitchCWD.hpp" #include "CustomEvents.hpp" // Use dedicated graphics card on Windows #ifdef _WIN32 extern "C" { __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif #include std::string icudt; // Put this in global space so that it isn't deallocated struct MainStruct { #ifdef USE_BACKEND_VULKAN #ifdef USE_SKIA_BACKEND_GANESH std::unique_ptr vulkanWindowContext; #elif USE_SKIA_BACKEND_GRAPHITE std::unique_ptr vulkanWindowContext; #endif #elif USE_BACKEND_OPENGL unsigned kStencilBits = 8; SDL_GLContext gl_context; GLint defaultFBO = 0; #endif #ifdef USE_SKIA_BACKEND_GRAPHITE #elif USE_SKIA_BACKEND_GANESH sk_sp ctx; GrBackendRenderTarget target; #endif std::unique_ptr m; SDL_Window* window; std::array systemCursors; unsigned currentCursor = 0; SkCanvas* canvas; sk_sp surface; SDL_Surface* iconSurface = nullptr; std::string iconData; SDL_Cursor* hiddenCursor = nullptr; std::filesystem::path configPath; std::filesystem::path homePath; std::ofstream logFile; std::chrono::steady_clock::time_point lastRenderTimePoint = std::chrono::steady_clock::now(); std::chrono::steady_clock::time_point lastUpdateTimePoint = std::chrono::steady_clock::now(); std::chrono::steady_clock::duration frameRefreshDuration = std::chrono::steady_clock::duration::zero(); } MainData; #ifdef __EMSCRIPTEN__ bool emscriptenFilesystemReadReady = false; bool emscriptenFilesystemLoadConfigDone = false; #endif bool load_file_to_string(std::string& toRet, std::string_view fileName) { // https://nullptr.org/cpp-read-file-into-string/ std::ifstream file(fileName.data(), std::ios::in | std::ios::binary | std::ios::ate); if(!file.is_open()) { std::cout << "[load_file_to_string] Could not open file " << fileName << std::endl; return false; } size_t fileSize; auto tellgResult = file.tellg(); if(tellgResult == -1) { std::cout << "[load_file_to_string] tellg failed for file " << fileName << std::endl; return false; } fileSize = static_cast(tellgResult); file.seekg(0, std::ios_base::beg); toRet.resize(fileSize); file.read(toRet.data(), fileSize); file.close(); return true; } void get_refresh_rate(MainStruct& mS) { mS.frameRefreshDuration = std::chrono::seconds(0); SDL_DisplayID displayID = SDL_GetDisplayForWindow(mS.window); if(displayID == 0) return; const SDL_DisplayMode* displayMode = SDL_GetCurrentDisplayMode(displayID); if(!displayMode) return; if(displayMode->refresh_rate != 0.0f) mS.frameRefreshDuration = std::chrono::microseconds(static_cast((1.0f / displayMode->refresh_rate) * 1000000.0f)); } void initialize_sdl(MainStruct& mS, int wWidth, int wHeight) { SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING, "InfiniPaint"); SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_VERSION_STRING, VersionConstants::CURRENT_VERSION_STRING.c_str()); SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING, "com.infinipaint.infinipaint"); SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_CREATOR_STRING, "Yousef Khadadeh (ErrorAtLine0)"); SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_COPYRIGHT_STRING, "Copyright (c) 2026 Yousef Khadadeh"); SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_URL_STRING, "https://infinipaint.com/"); SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_TYPE_STRING, "application"); SDL_SetHint(SDL_HINT_APP_NAME, "InfiniPaint"); SDL_SetHint(SDL_HINT_PEN_MOUSE_EVENTS, "0"); SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0"); SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); SDL_SetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT, "#canvas"); // Ensures that SDL only grabs input when browser is focused on canvas if(!SDL_Init(SDL_INIT_VIDEO)) throw std::runtime_error("[SDL_Init] " + std::string(SDL_GetError())); Uint32 window_flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; #ifdef USE_BACKEND_VULKAN window_flags |= SDL_WINDOW_VULKAN; #elif USE_BACKEND_OPENGL window_flags |= SDL_WINDOW_OPENGL; SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); #if defined(__EMSCRIPTEN__) || defined(USE_BACKEND_OPENGLES_3_0) SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #elif defined(USE_BACKEND_OPENGL_2_1) SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); #else SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); #endif SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, mS.kStencilBits); SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); #endif mS.window = SDL_CreateWindow("InfiniPaint", wWidth, wHeight, window_flags); if(mS.window == nullptr) throw std::runtime_error("[SDL_CreateWindow] " + std::string(SDL_GetError())); if(load_file_to_string(mS.iconData, "data/progicons/icon.png")) { sk_sp newData = SkData::MakeWithoutCopy(mS.iconData.c_str(), mS.iconData.size()); std::unique_ptr codec = SkCodec::MakeFromData(newData, {SkPngDecoder::Decoder()}); if(codec) { sk_sp newImage = std::get<0>(codec->getImage()); SkPixmap px; newImage->peekPixels(&px); mS.iconSurface = SDL_CreateSurfaceFrom(newImage->width(), newImage->height(), SDL_PIXELFORMAT_RGBA32, px.writable_addr(), px.rowBytes()); SDL_SetWindowIcon(mS.window, mS.iconSurface); } } SDL_SetWindowPosition(mS.window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); #ifdef USE_BACKEND_OPENGL mS.gl_context = SDL_GL_CreateContext(mS.window); if(mS.gl_context == nullptr) throw std::runtime_error("[SDL_GL_CreateContext] " + std::string(SDL_GetError())); SDL_GL_MakeCurrent(mS.window, mS.gl_context); #ifndef __EMSCRIPTEN__ #ifdef USE_BACKEND_OPENGLES_3_0 if(!gladLoadGLES2(SDL_GL_GetProcAddress)) throw std::runtime_error("[gladLoadGLES2] Failed to load GLAD OpenGLES 3.0 Loader"); #elif defined(USE_BACKEND_OPENGL_2_1) if(!gladLoadGL(SDL_GL_GetProcAddress)) throw std::runtime_error("[gladLoadGL] Failed to load GLAD OpenGL 2.1 Loader"); #else if(!gladLoadGL(SDL_GL_GetProcAddress)) throw std::runtime_error("[gladLoadGL] Failed to load GLAD OpenGL 3.3 Loader"); #endif #ifndef USE_BACKEND_OPENGL_2_1 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mS.defaultFBO); #endif Logger::get().log("INFO", "GL Version: " + std::string(reinterpret_cast(glGetString(GL_VERSION)))); #endif #endif SDL_SetWindowSize(mS.window, wWidth, wHeight); SDL_ShowWindow(mS.window); for(unsigned i = 0; i < SDL_SYSTEM_CURSOR_COUNT; i++) mS.systemCursors[i] = SDL_CreateSystemCursor((SDL_SystemCursor)i); get_refresh_rate(mS); } void sdl_terminate(MainStruct& mS) { for(unsigned i = 0; i < SDL_SYSTEM_CURSOR_COUNT; i++) SDL_DestroyCursor(mS.systemCursors[i]); if(mS.iconSurface) SDL_DestroySurface(mS.iconSurface); if(mS.hiddenCursor) SDL_DestroyCursor(mS.hiddenCursor); //SDL_GL_DestroyContext(mS.gl_context); //SDL_DestroyWindow(mS.window); //SDL_Quit(); } void resize_window(MainStruct& mS) { // Having intermediate surface allows for changing options more easily mS.m->refresh_draw_surfaces(); #ifdef USE_BACKEND_VULKAN mS.vulkanWindowContext->resize(mS.m->window.size.x(), mS.m->window.size.y()); #elif USE_BACKEND_OPENGL GrGLFramebufferInfo info; info.fFBOID = (GrGLuint)mS.defaultFBO; info.fFormat = GL_RGBA8; SkSurfaceProps props2; mS.target = GrBackendRenderTargets::MakeGL(mS.m->window.size.x(), mS.m->window.size.y(), 0, mS.kStencilBits, info); mS.surface = SkSurfaces::WrapBackendRenderTarget(mS.ctx.get(), mS.target, kBottomLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, SkColorSpace::MakeSRGB(), &props2); if(!mS.surface) throw std::runtime_error("[resize_window] Could not make surface"); mS.canvas = mS.surface->getCanvas(); if(!mS.canvas) throw std::runtime_error("[resize_window] No canvas made"); #endif } #ifdef __EMSCRIPTEN__ extern "C" { EMSCRIPTEN_KEEPALIVE inline void emscripten_filesystem_loaded() { emscriptenFilesystemReadReady = true; } } const char* emscripten_before_unload(int eventType, const void *reserved, void *userData) { MainStruct& mS = *((MainStruct*)userData); mS.m->save_config(); return ""; } #endif void init_logs(MainStruct& mS) { char* homePathSDL = SDL_GetCurrentDirectory(); mS.homePath = std::filesystem::path(homePathSDL); SDL_free(homePathSDL); #ifdef __EMSCRIPTEN__ EM_ASM( FS.mkdir('/infinipaint'); FS.mount(FS.filesystems.IDBFS, {}, '/infinipaint'); FS.syncfs(true, (err) => { if(err) console.log("Error syncing IDBFS: ", err); else { console.log("Synced to IDBFS. Read Ready"); Module["ccall"]('emscripten_filesystem_loaded', null, [], []); } }); ); mS.homePath = std::filesystem::path("/"); mS.configPath = std::filesystem::path("/infinipaint"); #elif CONFIG_NEXT_TO_EXECUTABLE std::string CONFIG_FOLDER_NAME = "config"; std::filesystem::create_directory(CONFIG_FOLDER_NAME); mS.configPath = mS.homePath / CONFIG_FOLDER_NAME; #else char* configPathSDL = SDL_GetPrefPath("ErrorAtLine0", "infinipaint"); mS.configPath = std::filesystem::path(configPathSDL); SDL_free(configPathSDL); #endif mS.logFile = std::ofstream(mS.configPath / "log.txt"); Logger::get().add_log("FATAL", [&, mS = &mS](const std::string& text) { mS->logFile << "[FATAL] " << text << std::endl; std::cerr << "[FATAL] " << text << std::endl; mS->logFile.close(); }); Logger::get().add_log("INFO", [&, mS = &mS](const std::string& text) { mS->logFile << "[INFO] " << text << std::endl; Logger::get().cross_platform_println("[INFO] " + text); }); Logger::get().log("INFO", "Home Path: " + mS.homePath.string()); Logger::get().log("INFO", "Config Path: " + mS.configPath.string()); } SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { std::vector listOfFilesToOpenFromCommand; for(int i = 1; i < argc; i++) listOfFilesToOpenFromCommand.emplace_back(std::filesystem::canonical(std::filesystem::path(std::u8string_view(reinterpret_cast(argv[i]))))); switch_cwd(); UErrorCode uerr = U_ZERO_ERROR; icudt = read_file_to_string("data/icudt77l-small.dat"); udata_setCommonData((void *) icudt.data(), &uerr); if (U_FAILURE(uerr)) { char* errorName = const_cast(u_errorName(uerr)); std::cout << "Failed to load icudt77l-small.dat" << std::endl; std::cout << errorName << std::endl; } MainStruct* mSPtr = new MainStruct; *appstate = (void*)mSPtr; MainStruct& mS = *mSPtr; init_logs(mS); FileDownloader::init(); #ifdef NDEBUG try { #endif int initWidth = 1000; int initHeight = 900; #ifdef __EMSCRIPTEN__ Vector2d sizeD; EMSCRIPTEN_RESULT result = emscripten_get_element_css_size("#canvas", &sizeD.x(), &sizeD.y()); if(result != EMSCRIPTEN_RESULT_SUCCESS) std::cout << "Failed to get canvas size" << std::endl; else { initWidth = sizeD.x(); initHeight = sizeD.y(); std::cout << "Initial window size: " << initWidth << " " << initHeight << std::endl; } #endif initialize_sdl(mS, initWidth, initHeight); CustomEvents::init(); int32_t cursorData[2] = {0, 0}; mS.hiddenCursor = SDL_CreateCursor((Uint8 *)cursorData, (Uint8 *)cursorData, 8, 8, 4, 4); mS.m = std::make_unique(); mS.m->logFile = &mS.logFile; mS.m->configPath = mS.configPath; mS.m->homePath = mS.homePath; #ifndef __EMSCRIPTEN__ const char* documentsPathSDL = SDL_GetUserFolder(SDL_FOLDER_DOCUMENTS); if(!documentsPathSDL) { documentsPathSDL = SDL_GetUserFolder(SDL_FOLDER_DESKTOP); if(!documentsPathSDL) mS.m->documentsPath = mS.m->configPath; else mS.m->documentsPath = std::filesystem::path(documentsPathSDL); } else mS.m->documentsPath = std::filesystem::path(documentsPathSDL); #endif mS.m->window.sdlWindow = mS.window; mS.m->update_scale_and_density(); #ifdef __EMSCRIPTEN__ emscripten_set_beforeunload_callback((void*)mSPtr, emscripten_before_unload); #else mS.m->load_config(); #endif #ifdef USE_BACKEND_VULKAN #ifdef USE_SKIA_BACKEND_GRAPHITE std::unique_ptr displayParams = skwindow::DisplayParamsBuilder().build(); mS.vulkanWindowContext = std::make_unique(std::move(displayParams), [window = mS.window](VkInstance instance) { VkSurfaceKHR toRet; if(!SDL_Vulkan_CreateSurface(window, instance, nullptr, &toRet)) throw std::runtime_error("Failed to create SDL vulkan surface"); else std::cout << "Successfully created surface!" << std::endl; return toRet; }, SDL_Vulkan_GetPresentationSupport, (PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr()); mS.surface = mS.vulkanWindowContext->getBackbufferSurface(); #elif USE_SKIA_BACKEND_GANESH std::unique_ptr displayParams = skwindow::DisplayParamsBuilder().detach(); mS.vulkanWindowContext = std::make_unique(std::move(displayParams), [window = mS.window](VkInstance instance) { VkSurfaceKHR toRet; if(!SDL_Vulkan_CreateSurface(window, instance, nullptr, &toRet)) throw std::runtime_error("Failed to create SDL vulkan surface"); else std::cout << "Successfully created surface!" << std::endl; return toRet; }, SDL_Vulkan_GetPresentationSupport, (PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr()); mS.ctx = mS.vulkanWindowContext->fContext; // fContext is supposed to be private, but put it in public for convenience for this app mS.surface = mS.vulkanWindowContext->getBackbufferSurface(); #endif #elif USE_BACKEND_OPENGL sk_sp iface = GrGLMakeNativeInterface(); GrContextOptions opts; opts.fSuppressPrints = true; opts.fDisableDriverCorrectnessWorkarounds = mS.m->conf.disableGraphicsDriverWorkarounds; // Driver workarounds causing glitches on intel drivers on windows, but are required in the Emscripten version. Good idea to have a toggle for it mS.ctx = GrDirectContexts::MakeGL(iface, opts); if(!mS.ctx) throw std::runtime_error("[GrDirectContexts::MakeGL] Could not make context"); #endif #ifdef USE_SKIA_BACKEND_GRAPHITE mS.m->window.recorder = [&]() { return mS.vulkanWindowContext->graphiteRecorder(); }; #elif USE_SKIA_BACKEND_GANESH mS.m->window.ctx = mS.ctx; #endif #ifdef __EMSCRIPTEN__ SDL_GetWindowPosition(mS.window, &mS.m->window.pos.x(), &mS.m->window.pos.y()); mS.m->window.writtenPos.x() = mS.m->window.pos.x(); mS.m->window.writtenPos.y() = mS.m->window.pos.y(); mS.m->window.size.x() = initWidth; mS.m->window.size.y() = initHeight; mS.m->window.writtenSize.x() = initWidth; mS.m->window.writtenSize.y() = initHeight; #else if(mS.m->window.pos.x() >= 0 && mS.m->window.pos.y() >= 0 && mS.m->window.size.x() >= 0 && mS.m->window.size.y() >= 0) { SDL_SetWindowSize(mS.window, mS.m->window.size.x(), mS.m->window.size.y()); SDL_SetWindowPosition(mS.window, mS.m->window.pos.x(), mS.m->window.pos.y()); } else { SDL_GetWindowPosition(mS.window, &mS.m->window.pos.x(), &mS.m->window.pos.y()); mS.m->window.writtenPos.x() = mS.m->window.pos.x(); mS.m->window.writtenPos.y() = mS.m->window.pos.y(); mS.m->window.size.x() = initWidth; mS.m->window.size.y() = initHeight; mS.m->window.writtenSize.x() = initWidth; mS.m->window.writtenSize.y() = initHeight; } if(mS.m->window.maximized) SDL_MaximizeWindow(mS.window); if(mS.m->window.fullscreen) SDL_SetWindowFullscreen(mS.window, true); #endif mS.m->window.canCreateSurfaces = true; resize_window(mS); if(listOfFilesToOpenFromCommand.empty()) { mS.m->create_new_tab({ .isClient = false }); } else { for(std::filesystem::path& f : listOfFilesToOpenFromCommand) { mS.m->create_new_tab({ .isClient = false, .filePathSource = f }); } } mS.m->update(); // Run update once to make sure that callbacks dont crash #ifdef NDEBUG } catch(const std::exception& e) { Logger::get().log("FATAL", e.what()); return SDL_APP_FAILURE; } #endif return SDL_APP_CONTINUE; } void cpu_save_sleep(MainStruct& mS) { int swapInterval = 0; constexpr std::chrono::duration MINIMUM_UPDATE_FRAMETIME = std::chrono::milliseconds(3); SDL_GL_GetSwapInterval(&swapInterval); if(swapInterval != 0) { std::chrono::time_point point1 = mS.lastRenderTimePoint + mS.frameRefreshDuration; std::chrono::time_point point2 = mS.lastUpdateTimePoint + MINIMUM_UPDATE_FRAMETIME; std::this_thread::sleep_until(std::min(point1, point2)); } } void attempt_redraw_and_swap_buffers(MainStruct& mS) { int swapInterval = 0; SDL_GL_GetSwapInterval(&swapInterval); if(std::chrono::steady_clock::now() - mS.lastRenderTimePoint > mS.frameRefreshDuration || swapInterval == 0) { #ifdef USE_BACKEND_VULKAN mS.vulkanWindowContext->swapBuffers(); #elif USE_BACKEND_OPENGL SDL_GL_SwapWindow(mS.window); #endif mS.lastRenderTimePoint = std::chrono::steady_clock::now(); mS.m->window.intermediateCanvas->save(); mS.m->draw(mS.m->window.intermediateCanvas, mS.m->world, mS.m->world->drawData); mS.m->window.intermediateCanvas->restore(); #ifdef USE_BACKEND_VULKAN mS.vulkanWindowContext->getBackbufferSurface()->getCanvas()->drawImage(mS.intermediateSurface->makeTemporaryImage(), 0, 0); mS.ctx->flushAndSubmit(); #elif USE_BACKEND_OPENGL mS.canvas->drawImage(mS.m->window.intermediateSurface->makeTemporaryImage(), 0, 0); mS.ctx->flushAndSubmit(); #endif } } void regular_draw(MainStruct& mS) { mS.lastRenderTimePoint = std::chrono::steady_clock::now(); mS.m->window.intermediateCanvas->save(); mS.m->draw(mS.m->window.intermediateCanvas, mS.m->world, mS.m->world->drawData); mS.m->window.intermediateCanvas->restore(); #ifdef USE_BACKEND_VULKAN mS.vulkanWindowContext->getBackbufferSurface()->getCanvas()->drawImage(mS.intermediateSurface->makeTemporaryImage(), 0, 0); mS.ctx->flushAndSubmit(); mS.vulkanWindowContext->swapBuffers(); #elif USE_BACKEND_OPENGL mS.canvas->drawImage(mS.m->window.intermediateSurface->makeTemporaryImage(), 0, 0); mS.ctx->flushAndSubmit(); SDL_GL_SwapWindow(mS.window); #endif } SDL_AppResult SDL_AppIterate(void *appstate) { std::chrono::steady_clock::time_point frameTimeStart = std::chrono::steady_clock::now(); MainStruct& mS = *((MainStruct*)appstate); mS.lastUpdateTimePoint = std::chrono::steady_clock::now(); #ifdef NDEBUG try { #endif #ifdef __EMSCRIPTEN__ if(emscriptenFilesystemReadReady && !emscriptenFilesystemLoadConfigDone) { mS.m->load_config(); emscriptenFilesystemLoadConfigDone = true; } #endif mS.m->update(); if(mS.m->setToQuit) return SDL_APP_SUCCESS; if(mS.m->input.hideCursor) { SDL_HideCursor(); SDL_SetCursor(mS.hiddenCursor); // Using this because SDL hidden cursor sometimes doesnt work } else { SDL_ShowCursor(); SDL_SetCursor(mS.systemCursors[static_cast(mS.m->input.cursorIcon)]); } //cpu_save_sleep(mS); //attempt_redraw_and_swap_buffers(mS); regular_draw(mS); mS.m->input.frame_reset(mS.m->window.size); #ifdef NDEBUG } catch(const std::exception& e) { Logger::get().log("FATAL", e.what()); return SDL_APP_FAILURE; } #endif mS.m->window.lastFrameTime = std::chrono::steady_clock::now() - frameTimeStart; return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { MainStruct& mS = *((MainStruct*)appstate); #ifdef NDEBUG try { #endif switch(event->type) { case SDL_EVENT_QUIT: if(mS.m->app_close_requested()) return SDL_APP_SUCCESS; break; case SDL_EVENT_WINDOW_CLOSE_REQUESTED: if(event->window.windowID == SDL_GetWindowID(mS.window)) { if(mS.m->app_close_requested()) return SDL_APP_SUCCESS; } break; case SDL_EVENT_WINDOW_MOVED: mS.m->window.pos.x() = event->window.data1; mS.m->window.pos.y() = event->window.data2; if(!mS.m->window.fullscreen && !mS.m->window.maximized) { mS.m->window.writtenPos.x() = event->window.data1; mS.m->window.writtenPos.y() = event->window.data2; } get_refresh_rate(mS); break; case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: mS.m->window.size.x() = event->window.data1; mS.m->window.size.y() = event->window.data2; if(!mS.m->window.fullscreen && !mS.m->window.maximized) { mS.m->window.writtenSize.x() = event->window.data1; mS.m->window.writtenSize.y() = event->window.data2; } resize_window(mS); mS.m->update_scale_and_density(); mS.m->input.backend_window_resize_update(event->window); get_refresh_rate(mS); break; case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: mS.m->update_scale_and_density(); mS.m->input.backend_window_scale_update(event->window); get_refresh_rate(mS); break; case SDL_EVENT_WINDOW_MAXIMIZED: mS.m->window.maximized = true; get_refresh_rate(mS); break; case SDL_EVENT_WINDOW_RESTORED: mS.m->window.maximized = false; get_refresh_rate(mS); break; case SDL_EVENT_MOUSE_MOTION: #ifdef _WIN32 if(!mS.m->toolbar.tabletOptions.ignoreMouseMovementWhenPenInProximity || !mS.m->input.pen.inProximity) #endif { mS.m->input.backend_mouse_motion_update(event->motion); } break; case SDL_EVENT_MOUSE_BUTTON_UP: mS.m->input.backend_mouse_button_up_update(event->button); break; case SDL_EVENT_MOUSE_BUTTON_DOWN: mS.m->input.backend_mouse_button_down_update(event->button); break; case SDL_EVENT_MOUSE_WHEEL: mS.m->input.backend_mouse_wheel_update(event->wheel); break; case SDL_EVENT_KEY_DOWN: mS.m->input.backend_key_down_update(event->key); break; case SDL_EVENT_KEY_UP: mS.m->input.backend_key_up_update(event->key); break; case SDL_EVENT_TEXT_INPUT: mS.m->input.backend_input_text_event(event->text.text); break; case SDL_EVENT_DROP_FILE: mS.m->input.backend_drop_file_event(event->drop); break; case SDL_EVENT_DROP_TEXT: mS.m->input.backend_drop_text_event(event->drop); break; case SDL_EVENT_PEN_PROXIMITY_IN: { mS.m->input.pen.inProximity = true; break; } case SDL_EVENT_PEN_PROXIMITY_OUT: { mS.m->input.pen.inProximity = false; break; } case SDL_EVENT_PEN_MOTION: { mS.m->input.backend_pen_motion_update(event->pmotion); break; } case SDL_EVENT_PEN_AXIS: { mS.m->input.backend_pen_axis_update(event->paxis); break; } case SDL_EVENT_PEN_DOWN: { mS.m->input.backend_pen_touch_down_update(event->ptouch); break; } case SDL_EVENT_PEN_UP: { mS.m->input.backend_pen_touch_up_update(event->ptouch); break; } case SDL_EVENT_PEN_BUTTON_UP: { mS.m->input.backend_pen_button_up_update(event->pbutton); break; } case SDL_EVENT_PEN_BUTTON_DOWN: { mS.m->input.backend_pen_button_down_update(event->pbutton); break; } case SDL_EVENT_FINGER_DOWN: { mS.m->input.backend_touch_finger_down_update(event->tfinger); break; } case SDL_EVENT_FINGER_UP: { mS.m->input.backend_touch_finger_up_update(event->tfinger); break; } case SDL_EVENT_FINGER_MOTION: { mS.m->input.backend_touch_finger_motion_update(event->tfinger); break; } case SDL_EVENT_DISPLAY_ORIENTATION: case SDL_EVENT_DISPLAY_ADDED: case SDL_EVENT_DISPLAY_REMOVED: case SDL_EVENT_DISPLAY_MOVED: case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED: case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED: case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED: case SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED: { get_refresh_rate(mS); break; } case SDL_EVENT_SCREEN_KEYBOARD_SHOWN: break; case SDL_EVENT_SCREEN_KEYBOARD_HIDDEN: break; default: { if(event->type == CustomEvents::PASTE_EVENT) mS.m->input.backend_paste_event(); else if(event->type == CustomEvents::OPEN_INFINIPAINT_FILE_EVENT) mS.m->input.backend_open_infinipaint_file_event(); break; } } #ifdef NDEBUG } catch(const std::exception& e) { Logger::get().log("FATAL", e.what()); return SDL_APP_FAILURE; } #endif return mS.m->setToQuit ? SDL_APP_SUCCESS : SDL_APP_CONTINUE; } void SDL_AppQuit(void *appstate, SDL_AppResult result) { DrawingProgramCache::delete_all_draw_cache(); MainStruct& mS = *((MainStruct*)appstate); try { mS.m->save_config(); mS.m->early_destroy(); sdl_terminate(mS); } catch(const std::exception& e) { Logger::get().log("FATAL", e.what()); } delete (&mS); SDL_Quit(); FileDownloader::cleanup(); }