infinipaint/src/main.cpp

902 lines
36 KiB
C++
Raw Normal View History

/*
* InfiniPaint
* Copyright (C) 2025-2026 Yousef Khadadeh
*
* This program 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 Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "CustomEvents.hpp"
2025-07-06 20:43:53 +03:00
#include "Helpers/SCollision.hpp"
#include "Helpers/StringHelpers.hpp"
#include "Helpers/FileDownloader.hpp"
#include "Screens/FileSelectScreen.hpp"
#include "Screens/DesktopDrawingProgramScreen.hpp"
#include "Screens/PhoneDrawingProgramScreen.hpp"
#include "VersionConstants.hpp"
2025-07-06 20:43:53 +03:00
#include "include/gpu/GpuTypes.h"
2026-02-24 17:21:41 +03:00
#include <SDL3/SDL_filesystem.h>
2025-07-06 20:43:53 +03:00
#include <SDL3/SDL_hints.h>
#include <SDL3/SDL_oldnames.h>
2025-09-29 17:05:53 +03:00
#include <chrono>
2025-07-06 20:43:53 +03:00
#include <filesystem>
#include <include/core/SkAlphaType.h>
#include <include/core/SkColorType.h>
#include <thread>
2025-07-06 20:43:53 +03:00
#ifdef USE_BACKEND_OPENGL
#ifndef __EMSCRIPTEN__
2025-08-09 10:45:39 +03:00
#ifdef USE_BACKEND_OPENGLES_3_0
#define GLAD_GLES2_IMPLEMENTATION
#include <glad/gles3_0.h>
#elif USE_BACKEND_OPENGL_2_1
#define GLAD_GL_IMPLEMENTATION
#include <glad/gl2_1.h>
2025-08-01 16:16:40 +03:00
#else
2025-08-09 10:45:39 +03:00
#define GLAD_GL_IMPLEMENTATION
#include <glad/gl3_3.h>
2025-08-01 16:16:40 +03:00
#endif
2025-07-06 20:43:53 +03:00
#endif
#endif
#include "cereal/archives/portable_binary.hpp"
#include <include/core/SkCanvas.h>
#include <include/core/SkSurface.h>
#include <include/core/SkSurfaceProps.h>
#ifdef USE_SKIA_BACKEND_GANESH
#include <include/gpu/ganesh/GrDirectContext.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <include/gpu/ganesh/GrBackendSurface.h>
#elif USE_SKIA_BACKEND_GRAPHITE
#include <include/gpu/graphite/Surface.h>
#endif
#include <iostream>
#include <cereal/types/string.hpp>
#include "MainProgram.hpp"
#include <include/codec/SkPngDecoder.h>
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_pen.h>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_keyboard.h>
#include <SDL3/SDL_mouse.h>
#include <SDL3/SDL_scancode.h>
#include <SDL3/SDL_video.h>
#include <SDL3/SDL.h>
2026-02-04 19:15:58 +03:00
#include "DrawingProgram/DrawingProgramCache.hpp"
2025-07-06 20:43:53 +03:00
#include <fstream>
#ifdef __EMSCRIPTEN__
#include <SDL3/SDL_opengl.h>
#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
#include <include/gpu/ganesh/gl/GrGLInterface.h>
#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
#include <include/gpu/ganesh/gl/GrGLTypes.h>
#include <emscripten/html5.h>
#include <emscripten.h>
#include <emscripten/emscripten.h>
#elif USE_BACKEND_VULKAN
#ifdef USE_SKIA_BACKEND_GRAPHITE
#include "VulkanContext/GraphiteNativeVulkanWindowContext.h"
#elif USE_SKIA_BACKEND_GANESH
#include "VulkanContext/VulkanWindowContext.h"
#endif
#include <tools/window/DisplayParams.h>
#include <SDL3/SDL_vulkan.h>
#elif USE_BACKEND_OPENGL
#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
#include <include/gpu/ganesh/gl/GrGLInterface.h>
#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
#include <include/gpu/ganesh/gl/GrGLTypes.h>
#endif
#ifdef __linux__
#include <unistd.h>
#include <pwd.h>
#elif _WIN32
#include <shlobj.h>
#endif
#include <Helpers/Logger.hpp>
#include "SwitchCWD.hpp"
#include "CustomEvents.hpp"
2025-07-06 20:43:53 +03:00
// Use dedicated graphics card on Windows
#ifdef _WIN32
extern "C"
{
__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
#include "AndroidJNICalls.hpp"
#include <unicode/udata.h>
std::string icudt; // Put this in global space so that it isn't deallocated
2025-07-06 20:43:53 +03:00
struct MainStruct {
#ifdef USE_BACKEND_VULKAN
#ifdef USE_SKIA_BACKEND_GANESH
std::unique_ptr<skwindow::internal::VulkanWindowContext> vulkanWindowContext;
#elif USE_SKIA_BACKEND_GRAPHITE
std::unique_ptr<skwindow::internal::GraphiteVulkanWindowContext> 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<GrDirectContext> ctx;
GrBackendRenderTarget target;
#endif
std::unique_ptr<MainProgram> m;
SDL_Window* window;
std::array<SDL_Cursor*, SDL_SYSTEM_CURSOR_COUNT> systemCursors;
unsigned currentCursor = 0;
SkCanvas* canvas;
SDL_Surface* iconSurface = nullptr;
std::string iconData;
SDL_Cursor* hiddenCursor = nullptr;
std::filesystem::path configPath;
std::filesystem::path homePath;
std::ofstream logFile;
2026-02-25 15:25:00 +03:00
std::chrono::steady_clock::time_point lastRenderTimePoint = std::chrono::steady_clock::now();
std::chrono::steady_clock::time_point lastUpdateTimePoint = std::chrono::steady_clock::now();
2026-02-25 15:25:00 +03:00
std::chrono::steady_clock::duration frameRefreshDuration = std::chrono::steady_clock::duration::zero();
2025-07-06 20:43:53 +03:00
} MainData;
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<size_t>(tellgResult);
file.seekg(0, std::ios_base::beg);
toRet.resize(fileSize);
file.read(toRet.data(), fileSize);
file.close();
return true;
}
2026-02-25 15:25:00 +03:00
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<int>((1.0f / displayMode->refresh_rate) * 1000000.0f));
}
void initialize_sdl(MainStruct& mS) {
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)");
2026-05-13 19:56:14 +03:00
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_COPYRIGHT_STRING, "Copyright (C) 2025-2026 Yousef Khadadeh");
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_URL_STRING, "https://infinipaint.com/");
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_TYPE_STRING, "application");
2025-07-06 20:43:53 +03:00
SDL_SetHint(SDL_HINT_APP_NAME, "InfiniPaint");
SDL_SetHint(SDL_HINT_PEN_TOUCH_EVENTS, "0");
2025-07-06 20:43:53 +03:00
SDL_SetHint(SDL_HINT_PEN_MOUSE_EVENTS, "0");
2026-02-27 16:16:51 +03:00
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
2026-04-27 17:20:16 +03:00
SDL_SetHint(SDL_HINT_ANDROID_TRAP_BACK_BUTTON, "1");
2025-07-06 20:43:53 +03:00
SDL_SetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT, "#canvas"); // Ensures that SDL only grabs input when browser is focused on canvas
2026-03-04 12:15:24 +03:00
if(!SDL_Init(SDL_INIT_VIDEO))
2025-07-06 20:43:53 +03:00
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);
2025-08-09 10:45:39 +03:00
#if defined(__EMSCRIPTEN__) || defined(USE_BACKEND_OPENGLES_3_0)
2025-07-06 20:43:53 +03:00
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);
2025-08-09 10:45:39 +03:00
#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);
2025-08-01 16:16:40 +03:00
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
2025-07-06 20:43:53 +03:00
#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
constexpr int DEFAULT_WINDOW_WIDTH = 1000;
constexpr int DEFAULT_WINDOW_HEIGHT = 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 {
mS.m->window.writtenSize.x() = mS.m->window.size.x() = sizeD.x();
mS.m->window.writtenSize.y() = mS.m->window.size.y() = sizeD.y();
std::cout << "Initial window size: " << vec_pretty(mS.m->window.size) << std::endl;
}
}
#endif
mS.window = SDL_CreateWindow("InfiniPaint", mS.m->window.size.x() >= 0 ? mS.m->window.size.x() : DEFAULT_WINDOW_WIDTH, mS.m->window.size.y() >= 0 ? mS.m->window.size.y() : DEFAULT_WINDOW_HEIGHT, window_flags);
mS.m->window.sdlWindow = mS.window;
2025-07-06 20:43:53 +03:00
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<SkData> newData = SkData::MakeWithoutCopy(mS.iconData.c_str(), mS.iconData.size());
std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(newData, {SkPngDecoder::Decoder()});
if(codec) {
sk_sp<SkImage> 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__
2025-08-09 10:45:39 +03:00
#ifdef USE_BACKEND_OPENGLES_3_0
2025-08-01 16:16:40 +03:00
if(!gladLoadGLES2(SDL_GL_GetProcAddress))
2025-08-09 10:45:39 +03:00
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");
2025-08-01 16:16:40 +03:00
#else
if(!gladLoadGL(SDL_GL_GetProcAddress))
throw std::runtime_error("[gladLoadGL] Failed to load GLAD OpenGL 3.3 Loader");
#endif
2025-08-09 10:45:39 +03:00
#ifndef USE_BACKEND_OPENGL_2_1
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mS.defaultFBO);
#endif
2026-05-16 21:56:58 +03:00
Logger::get().log(Logger::LogType::INFO, "GL Version: " + std::string(reinterpret_cast<const char*>(glGetString(GL_VERSION))));
2025-07-06 20:43:53 +03:00
#endif
#endif
#ifdef __EMSCRIPTEN__
SDL_GetWindowSize(mS.window, &mS.m->window.size.x(), &mS.m->window.size.y());
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.writtenSize.x() = mS.m->window.size.x();
mS.m->window.writtenSize.y() = mS.m->window.size.y();
#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_SetWindowPosition(mS.window, mS.m->window.pos.x(), mS.m->window.pos.y());
else {
SDL_GetWindowSize(mS.window, &mS.m->window.size.x(), &mS.m->window.size.y());
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.writtenSize.x() = mS.m->window.size.x();
mS.m->window.writtenSize.y() = mS.m->window.size.y();
}
if(mS.m->window.maximized)
SDL_MaximizeWindow(mS.window);
if(mS.m->window.fullscreen)
SDL_SetWindowFullscreen(mS.window, true);
#endif
2025-07-06 20:43:53 +03:00
SDL_ShowWindow(mS.window);
mS.m->set_vsync_value(mS.m->conf.vsyncValue);
2025-07-06 20:43:53 +03:00
for(unsigned i = 0; i < SDL_SYSTEM_CURSOR_COUNT; i++)
mS.systemCursors[i] = SDL_CreateSystemCursor((SDL_SystemCursor)i);
2026-02-25 15:25:00 +03:00
get_refresh_rate(mS);
SDL_SyncWindow(mS.window);
// Take maximize and fullscreen into account
SDL_GetWindowSize(mS.window, &mS.m->window.size.x(), &mS.m->window.size.y());
2026-06-04 09:21:53 +03:00
SDL_WindowFlags f = SDL_GetWindowFlags(mS.window);
mS.m->window.windowFocus = f & SDL_WINDOW_INPUT_FOCUS;
mS.m->window.mouseFocus = f & SDL_WINDOW_MOUSE_FOCUS;
mS.m->input.update_safe_area();
2026-06-04 09:21:53 +03:00
#ifndef __ANDROID__
mS.m->update_main_loop_call_rate((mS.m->window.windowFocus || mS.m->window.mouseFocus) ? mS.m->conf.mainCallbackRate : mS.m->conf.mainCallbackRateBackground);
#else
mS.m->update_main_loop_call_rate(mS.m->conf.mainCallbackRate);
#endif
2025-07-06 20:43:53 +03:00
}
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();
2025-07-06 20:43:53 +03:00
#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.m->window.nativeSurface = SkSurfaces::WrapBackendRenderTarget(mS.ctx.get(), mS.target, kBottomLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, SkColorSpace::MakeSRGB(), &props2);
2025-07-06 20:43:53 +03:00
if(!mS.m->window.nativeSurface)
2025-07-06 20:43:53 +03:00
throw std::runtime_error("[resize_window] Could not make surface");
mS.canvas = mS.m->window.nativeSurface->getCanvas();
2025-07-06 20:43:53 +03:00
if(!mS.canvas)
throw std::runtime_error("[resize_window] No canvas made");
#endif
}
#ifdef __EMSCRIPTEN__
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) {
2026-02-24 17:21:41 +03:00
char* homePathSDL = SDL_GetCurrentDirectory();
mS.homePath = std::filesystem::path(homePathSDL);
SDL_free(homePathSDL);
2026-04-12 17:31:32 +03:00
#ifdef CONFIG_NEXT_TO_EXECUTABLE
std::string CONFIG_FOLDER_NAME = "config";
std::filesystem::create_directory(CONFIG_FOLDER_NAME);
mS.configPath = mS.homePath / CONFIG_FOLDER_NAME;
2025-07-06 20:43:53 +03:00
#else
2026-02-24 17:21:41 +03:00
char* configPathSDL = SDL_GetPrefPath("ErrorAtLine0", "infinipaint");
mS.configPath = std::filesystem::path(configPathSDL);
SDL_free(configPathSDL);
2025-07-06 20:43:53 +03:00
#endif
mS.logFile = std::ofstream(mS.configPath / "log.txt");
2026-05-16 21:56:58 +03:00
Logger::get().set_log_function(Logger::LogType::FATAL, [&, mS = &mS](const std::string& text) {
2025-07-06 20:43:53 +03:00
mS->logFile << "[FATAL] " << text << std::endl;
std::cerr << "[FATAL] " << text << std::endl;
mS->logFile.close();
});
2026-05-16 21:56:58 +03:00
Logger::get().set_log_function(Logger::LogType::INFO, [&, mS = &mS](const std::string& text) {
2025-07-06 20:43:53 +03:00
mS->logFile << "[INFO] " << text << std::endl;
2026-02-24 17:21:41 +03:00
Logger::get().cross_platform_println("[INFO] " + text);
2025-07-06 20:43:53 +03:00
});
2026-05-16 21:56:58 +03:00
Logger::get().log(Logger::LogType::INFO, "Home Path: " + mS.homePath.string());
Logger::get().log(Logger::LogType::INFO, "Config Path: " + mS.configPath.string());
2025-07-06 20:43:53 +03:00
}
SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
std::vector<std::filesystem::path> listOfFilesToOpenFromCommand;
for(int i = 1; i < argc; i++)
listOfFilesToOpenFromCommand.emplace_back(std::filesystem::canonical(std::filesystem::path(std::u8string_view(reinterpret_cast<char8_t*>(argv[i])))));
2025-07-06 20:43:53 +03:00
switch_cwd();
UErrorCode uerr = U_ZERO_ERROR;
2025-11-15 09:48:02 +03:00
icudt = read_file_to_string("data/icudt77l-small.dat");
udata_setCommonData((void *) icudt.data(), &uerr);
if (U_FAILURE(uerr)) {
char* errorName = const_cast<char*>(u_errorName(uerr));
2025-11-15 09:48:02 +03:00
std::cout << "Failed to load icudt77l-small.dat" << std::endl;
std::cout << errorName << std::endl;
}
2025-07-06 20:43:53 +03:00
MainStruct* mSPtr = new MainStruct;
*appstate = (void*)mSPtr;
MainStruct& mS = *mSPtr;
init_logs(mS);
FileDownloader::init();
2025-07-06 20:43:53 +03:00
#ifdef NDEBUG
try {
#endif
mS.m = std::make_unique<MainProgram>();
mS.m->logFile = &mS.logFile;
2026-04-12 12:36:00 +03:00
mS.m->conf.configPath = mS.configPath;
2025-07-06 20:43:53 +03:00
mS.m->homePath = mS.homePath;
2026-04-12 17:31:32 +03:00
#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->conf.configPath;
else
mS.m->documentsPath = std::filesystem::path(documentsPathSDL);
}
2026-02-24 17:21:41 +03:00
else
mS.m->documentsPath = std::filesystem::path(documentsPathSDL);
2026-04-12 17:31:32 +03:00
#endif
mS.m->update_scale_and_density();
2025-07-06 20:43:53 +03:00
mS.m->load_config();
initialize_sdl(mS);
CustomEvents::init();
int32_t cursorData[2] = {0, 0};
mS.hiddenCursor = SDL_CreateCursor((Uint8 *)cursorData, (Uint8 *)cursorData, 8, 8, 4, 4);
2026-04-12 17:31:32 +03:00
#ifdef __EMSCRIPTEN__
emscripten_set_beforeunload_callback((void*)mSPtr, emscripten_before_unload);
#endif
2025-07-06 20:43:53 +03:00
#ifdef USE_BACKEND_VULKAN
#ifdef USE_SKIA_BACKEND_GRAPHITE
std::unique_ptr<const skwindow::DisplayParams> displayParams = skwindow::DisplayParamsBuilder().build();
mS.vulkanWindowContext = std::make_unique<skwindow::internal::GraphiteVulkanWindowContext>(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.m->window.nativeSurface = mS.vulkanWindowContext->getBackbufferSurface();
2025-07-06 20:43:53 +03:00
#elif USE_SKIA_BACKEND_GANESH
std::unique_ptr<const skwindow::DisplayParams> displayParams = skwindow::DisplayParamsBuilder().detach();
2025-07-06 20:43:53 +03:00
mS.vulkanWindowContext = std::make_unique<skwindow::internal::VulkanWindowContext>(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.m->window.nativeSurface = mS.vulkanWindowContext->getBackbufferSurface();
2025-07-06 20:43:53 +03:00
#endif
#elif USE_BACKEND_OPENGL
sk_sp<const GrGLInterface> 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
2025-07-06 20:43:53 +03:00
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
mS.m->window.canCreateSurfaces = true;
2025-07-06 20:43:53 +03:00
resize_window(mS);
2026-05-14 20:21:45 +03:00
if(mS.m->conf.mobileUI)
mS.m->set_first_screen(std::make_unique<FileSelectScreen>(*mS.m));
2025-07-06 20:43:53 +03:00
else {
2026-05-14 20:21:45 +03:00
mS.m->set_first_screen(std::make_unique<DesktopDrawingProgramScreen>(*mS.m));
for(std::filesystem::path& f : listOfFilesToOpenFromCommand) {
2026-04-05 20:26:03 +03:00
mS.m->create_new_tab({
.isClient = false,
.filePathSource = f
2026-04-05 20:26:03 +03:00
});
2025-07-06 20:43:53 +03:00
}
if(mS.m->worlds.empty()) {
mS.m->create_new_tab({
.isClient = false
});
2026-05-14 20:21:45 +03:00
}
2025-07-06 20:43:53 +03:00
}
2026-04-02 10:15:22 +03:00
mS.m->update(); // Run update once to make sure that callbacks dont crash
2025-07-06 20:43:53 +03:00
#ifdef NDEBUG
}
catch(const std::exception& e) {
2026-05-16 21:56:58 +03:00
Logger::get().log(Logger::LogType::FATAL, e.what());
2025-07-06 20:43:53 +03:00
return SDL_APP_FAILURE;
}
#endif
return SDL_APP_CONTINUE;
}
void regular_draw(MainStruct& mS) {
mS.lastRenderTimePoint = std::chrono::steady_clock::now();
if(mS.m->window.intermediateSurfaceMSAA) {
SkCanvas* intermediateCanvas = mS.m->window.intermediateSurfaceMSAA->getCanvas();
intermediateCanvas->save();
2026-05-04 18:13:47 +03:00
intermediateCanvas->translate(mS.m->input.screenOffset.x(), mS.m->input.screenOffset.y());
2026-04-17 19:32:49 +03:00
mS.m->draw(intermediateCanvas);
intermediateCanvas->restore();
2026-02-25 15:25:00 +03:00
#ifdef USE_BACKEND_VULKAN
mS.vulkanWindowContext->getBackbufferSurface()->getCanvas()->drawImage(mS.m->window.intermediateSurfaceMSAA->makeTemporaryImage(), 0, 0);
mS.ctx->flushAndSubmit();
2026-02-25 15:25:00 +03:00
mS.vulkanWindowContext->swapBuffers();
#elif USE_BACKEND_OPENGL
mS.canvas->drawImage(mS.m->window.intermediateSurfaceMSAA->makeTemporaryImage(), 0, 0);
mS.ctx->flushAndSubmit();
2026-02-25 15:25:00 +03:00
SDL_GL_SwapWindow(mS.window);
#endif
}
else {
2026-02-25 15:25:00 +03:00
#ifdef USE_BACKEND_VULKAN
2026-04-17 19:32:49 +03:00
mS.m->draw(mS.vulkanWindowContext->getBackbufferSurface()->getCanvas());
2026-02-25 15:25:00 +03:00
mS.ctx->flushAndSubmit();
mS.vulkanWindowContext->swapBuffers();
2026-02-25 15:25:00 +03:00
#elif USE_BACKEND_OPENGL
2026-05-04 18:13:47 +03:00
mS.canvas->save();
mS.canvas->translate(mS.m->input.screenOffset.x(), mS.m->input.screenOffset.y());
2026-04-17 19:32:49 +03:00
mS.m->draw(mS.canvas);
2026-05-04 18:13:47 +03:00
mS.canvas->restore();
2026-02-25 15:25:00 +03:00
mS.ctx->flushAndSubmit();
SDL_GL_SwapWindow(mS.window);
2026-02-25 15:25:00 +03:00
#endif
}
}
2025-07-06 20:43:53 +03:00
SDL_AppResult SDL_AppIterate(void *appstate) {
std::chrono::steady_clock::time_point frameTimeStart = std::chrono::steady_clock::now();
2025-07-06 20:43:53 +03:00
MainStruct& mS = *((MainStruct*)appstate);
mS.lastUpdateTimePoint = std::chrono::steady_clock::now();
2025-07-06 20:43:53 +03:00
#ifdef NDEBUG
try {
#endif
mS.m->update();
if(mS.m->setToQuit) {
#ifdef __ANDROID__
mS.m->input_app_about_to_go_to_background_callback();
#endif
2025-07-06 20:43:53 +03:00
return SDL_APP_SUCCESS;
}
2025-07-06 20:43:53 +03:00
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<unsigned>(mS.m->input.cursorIcon)]);
}
2026-02-27 16:16:51 +03:00
regular_draw(mS);
2025-07-06 20:43:53 +03:00
mS.m->input.frame_reset(mS.m->window.size);
#ifdef NDEBUG
}
catch(const std::exception& e) {
2026-05-16 21:56:58 +03:00
Logger::get().log(Logger::LogType::FATAL, e.what());
2025-07-06 20:43:53 +03:00
return SDL_APP_FAILURE;
}
#endif
mS.m->window.lastFrameTime = std::chrono::steady_clock::now() - frameTimeStart;
2025-07-06 20:43:53 +03:00
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:
2026-05-16 21:56:58 +03:00
Logger::get().log(Logger::LogType::INFO, "[SDL_AppEvent] Quit");
2026-01-18 13:39:49 +03:00
if(mS.m->app_close_requested())
2025-07-06 20:43:53 +03:00
return SDL_APP_SUCCESS;
break;
case SDL_EVENT_WILL_ENTER_BACKGROUND:
2026-05-16 21:56:58 +03:00
Logger::get().log(Logger::LogType::INFO, "[SDL_AppEvent] Entering background");
mS.m->input_app_about_to_go_to_background_callback();
break;
2026-05-14 11:53:36 +03:00
case SDL_EVENT_WILL_ENTER_FOREGROUND:
2026-05-16 21:56:58 +03:00
Logger::get().log(Logger::LogType::INFO, "[SDL_AppEvent] Entering foreground");
2026-05-14 11:53:36 +03:00
mS.m->input_app_about_to_go_to_foreground_callback();
break;
2026-01-18 13:39:49 +03:00
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;
2025-07-06 20:43:53 +03:00
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;
}
2026-02-25 15:25:00 +03:00
get_refresh_rate(mS);
2025-07-06 20:43:53 +03:00
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();
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);
2026-02-25 15:25:00 +03:00
get_refresh_rate(mS);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_WINDOW_MAXIMIZED:
mS.m->window.maximized = true;
2026-02-25 15:25:00 +03:00
get_refresh_rate(mS);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_WINDOW_RESTORED:
mS.m->window.maximized = false;
2026-02-25 15:25:00 +03:00
get_refresh_rate(mS);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: {
mS.m->input.backend_window_resize_update();
break;
}
2025-07-06 20:43:53 +03:00
case SDL_EVENT_MOUSE_MOTION:
2025-07-08 15:12:35 +03:00
#ifdef _WIN32
2026-04-11 11:17:26 +03:00
if(!mS.m->conf.tabletOptions.ignoreMouseMovementWhenPenInProximity || !mS.m->input.pen.inProximity)
2025-07-08 15:12:35 +03:00
#endif
{
2026-03-01 17:28:43 +03:00
mS.m->input.backend_mouse_motion_update(event->motion);
2025-07-08 15:12:35 +03:00
}
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
2026-03-01 17:28:43 +03:00
mS.m->input.backend_mouse_button_up_update(event->button);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
2026-03-01 17:28:43 +03:00
mS.m->input.backend_mouse_button_down_update(event->button);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_MOUSE_WHEEL:
2026-03-01 17:28:43 +03:00
mS.m->input.backend_mouse_wheel_update(event->wheel);
2025-07-06 20:43:53 +03:00
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);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_DROP_FILE:
mS.m->input.backend_drop_file_event(event->drop);
2025-07-06 20:43:53 +03:00
break;
case SDL_EVENT_DROP_TEXT:
mS.m->input.backend_drop_text_event(event->drop);
2025-07-06 20:43:53 +03:00
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: {
2026-03-03 14:59:18 +03:00
mS.m->input.backend_pen_motion_update(event->pmotion);
2025-07-06 20:43:53 +03:00
break;
}
case SDL_EVENT_PEN_AXIS: {
2026-03-03 22:31:48 +03:00
mS.m->input.backend_pen_axis_update(event->paxis);
2025-07-06 20:43:53 +03:00
break;
}
2026-03-03 14:59:18 +03:00
case SDL_EVENT_PEN_DOWN: {
mS.m->input.backend_pen_touch_down_update(event->ptouch);
break;
}
2025-07-06 20:43:53 +03:00
case SDL_EVENT_PEN_UP: {
2026-03-03 14:59:18 +03:00
mS.m->input.backend_pen_touch_up_update(event->ptouch);
2025-07-06 20:43:53 +03:00
break;
}
case SDL_EVENT_PEN_BUTTON_UP: {
2026-03-03 14:59:18 +03:00
mS.m->input.backend_pen_button_up_update(event->pbutton);
2025-07-06 20:43:53 +03:00
break;
}
case SDL_EVENT_PEN_BUTTON_DOWN: {
2026-03-03 14:59:18 +03:00
mS.m->input.backend_pen_button_down_update(event->pbutton);
2025-07-06 20:43:53 +03:00
break;
}
2026-02-27 16:16:51 +03:00
case SDL_EVENT_FINGER_DOWN: {
2026-05-15 09:51:25 +03:00
if(!mS.m->conf.tabletOptions.disableTouchWhenPenInProximity || !mS.m->input.pen.inProximity)
mS.m->input.backend_touch_finger_down_update(event->tfinger);
2026-02-27 16:16:51 +03:00
break;
}
case SDL_EVENT_FINGER_UP: {
2026-05-15 09:51:25 +03:00
if(!mS.m->conf.tabletOptions.disableTouchWhenPenInProximity || !mS.m->input.pen.inProximity)
mS.m->input.backend_touch_finger_up_update(event->tfinger);
2026-02-27 16:16:51 +03:00
break;
}
case SDL_EVENT_FINGER_MOTION: {
2026-05-15 09:51:25 +03:00
if(!mS.m->conf.tabletOptions.disableTouchWhenPenInProximity || !mS.m->input.pen.inProximity)
mS.m->input.backend_touch_finger_motion_update(event->tfinger);
2026-02-27 16:16:51 +03:00
break;
}
2026-02-25 15:25:00 +03:00
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;
}
2026-06-04 09:21:53 +03:00
case SDL_EVENT_WINDOW_MOUSE_ENTER: {
mS.m->window.mouseFocus = true;
mS.m->input_window_mouse_focus_gained();
break;
}
case SDL_EVENT_WINDOW_MOUSE_LEAVE: {
mS.m->window.mouseFocus = false;
mS.m->input_window_mouse_focus_lost();
break;
}
case SDL_EVENT_WINDOW_FOCUS_GAINED: {
mS.m->window.windowFocus = true;
mS.m->input_window_focus_gained();
break;
}
case SDL_EVENT_WINDOW_FOCUS_LOST: {
mS.m->window.windowFocus = false;
mS.m->input_window_focus_lost();
break;
}
2026-03-22 20:16:15 +03:00
case SDL_EVENT_SCREEN_KEYBOARD_SHOWN:
2026-03-07 13:00:10 +03:00
break;
2026-03-22 20:16:15 +03:00
case SDL_EVENT_SCREEN_KEYBOARD_HIDDEN:
2026-03-07 13:00:10 +03:00
break;
default: {
2026-04-17 11:12:48 +03:00
if(event->type == CustomEvents::PasteEvent::EVENT_NUM)
mS.m->input_paste_callback(*CustomEvents::get_event<CustomEvents::PasteEvent>());
else if(event->type == CustomEvents::OpenInfiniPaintFileEvent::EVENT_NUM)
mS.m->input_open_infinipaint_file_callback(*CustomEvents::get_event<CustomEvents::OpenInfiniPaintFileEvent>());
else if(event->type == CustomEvents::AddFileToCanvasEvent::EVENT_NUM)
mS.m->input_add_file_to_canvas_callback(*CustomEvents::get_event<CustomEvents::AddFileToCanvasEvent>());
else if(event->type == CustomEvents::RefreshTextBoxInputEvent::EVENT_NUM) {
CustomEvents::get_event<CustomEvents::RefreshTextBoxInputEvent>();
mS.m->input.refresh_receiving_text_box_input();
}
else if(event->type == CustomEvents::AndroidTextBoxInputEvent::EVENT_NUM)
mS.m->input_android_text_box_input_callback(*CustomEvents::get_event<CustomEvents::AndroidTextBoxInputEvent>());
2026-06-01 15:42:33 +03:00
else if(event->type == CustomEvents::MobileImportCanvasEvent::EVENT_NUM)
mS.m->input_mobile_import_canvas_callback(*CustomEvents::get_event<CustomEvents::MobileImportCanvasEvent>());
2025-07-06 20:43:53 +03:00
break;
}
2025-07-06 20:43:53 +03:00
}
#ifdef NDEBUG
}
catch(const std::exception& e) {
2026-05-16 21:56:58 +03:00
Logger::get().log(Logger::LogType::FATAL, e.what());
2025-07-06 20:43:53 +03:00
return SDL_APP_FAILURE;
}
#endif
if(mS.m->setToQuit) {
#ifdef __ANDROID__
mS.m->input_app_about_to_go_to_background_callback();
#endif
2026-05-13 14:31:35 +03:00
return SDL_APP_SUCCESS;
}
2026-05-13 14:31:35 +03:00
return SDL_APP_CONTINUE;
2025-07-06 20:43:53 +03:00
}
// NOTE: On Android, SDL_AppQuit is triggered by onDestroy. onDestroy may or may not be called, and even if it is, it may only be partially called. You should not rely on it.
2025-07-06 20:43:53 +03:00
void SDL_AppQuit(void *appstate, SDL_AppResult result) {
#ifndef __ANDROID__
2026-02-04 19:19:29 +03:00
DrawingProgramCache::delete_all_draw_cache();
2026-02-04 19:15:58 +03:00
2025-07-06 20:43:53 +03:00
MainStruct& mS = *((MainStruct*)appstate);
try {
mS.m->save_config();
mS.m->early_destroy();
sdl_terminate(mS);
}
catch(const std::exception& e) {
2026-05-16 21:56:58 +03:00
Logger::get().log(Logger::LogType::FATAL, e.what());
2025-07-06 20:43:53 +03:00
}
delete (&mS);
SDL_Quit();
FileDownloader::cleanup();
#endif
2025-07-06 20:43:53 +03:00
}