infinipaint/src/main.cpp
2026-05-13 19:56:14 +03:00

862 lines
33 KiB
C++
Executable file

/*
* 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"
#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"
#include "include/gpu/GpuTypes.h"
#include <SDL3/SDL_filesystem.h>
#include <SDL3/SDL_hints.h>
#include <SDL3/SDL_oldnames.h>
#include <chrono>
#include <filesystem>
#include <include/core/SkAlphaType.h>
#include <include/core/SkColorType.h>
#include <thread>
#ifdef USE_BACKEND_OPENGL
#ifndef __EMSCRIPTEN__
#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>
#else
#define GLAD_GL_IMPLEMENTATION
#include <glad/gl3_3.h>
#endif
#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>
#include "DrawingProgram/DrawingProgramCache.hpp"
#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"
// 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
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;
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;
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;
}
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, 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) 2025-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_ANDROID_TRAP_BACK_BUTTON, "1");
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<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__
#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<const char*>(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.m->window.nativeSurface = SkSurfaces::WrapBackendRenderTarget(mS.ctx.get(), mS.target, kBottomLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, SkColorSpace::MakeSRGB(), &props2);
if(!mS.m->window.nativeSurface)
throw std::runtime_error("[resize_window] Could not make surface");
mS.canvas = mS.m->window.nativeSurface->getCanvas();
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) {
char* homePathSDL = SDL_GetCurrentDirectory();
mS.homePath = std::filesystem::path(homePathSDL);
SDL_free(homePathSDL);
#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;
#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<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])))));
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<char*>(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<MainProgram>();
mS.m->logFile = &mS.logFile;
mS.m->conf.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->conf.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();
mS.m->load_config();
#ifdef __EMSCRIPTEN__
emscripten_set_beforeunload_callback((void*)mSPtr, emscripten_before_unload);
#endif
#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();
#elif USE_SKIA_BACKEND_GANESH
std::unique_ptr<const skwindow::DisplayParams> displayParams = skwindow::DisplayParamsBuilder().detach();
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();
#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
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->input.update_safe_area();
mS.m->window.canCreateSurfaces = true;
resize_window(mS);
mS.m->set_first_screen(std::make_unique<FileSelectScreen>(*mS.m));
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 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();
intermediateCanvas->translate(mS.m->input.screenOffset.x(), mS.m->input.screenOffset.y());
mS.m->draw(intermediateCanvas);
intermediateCanvas->restore();
#ifdef USE_BACKEND_VULKAN
mS.vulkanWindowContext->getBackbufferSurface()->getCanvas()->drawImage(mS.m->window.intermediateSurfaceMSAA->makeTemporaryImage(), 0, 0);
mS.ctx->flushAndSubmit();
mS.vulkanWindowContext->swapBuffers();
#elif USE_BACKEND_OPENGL
mS.canvas->drawImage(mS.m->window.intermediateSurfaceMSAA->makeTemporaryImage(), 0, 0);
mS.ctx->flushAndSubmit();
SDL_GL_SwapWindow(mS.window);
#endif
}
else {
#ifdef USE_BACKEND_VULKAN
mS.m->draw(mS.vulkanWindowContext->getBackbufferSurface()->getCanvas());
mS.ctx->flushAndSubmit();
mS.vulkanWindowContext->swapBuffers();
#elif USE_BACKEND_OPENGL
mS.canvas->save();
mS.canvas->translate(mS.m->input.screenOffset.x(), mS.m->input.screenOffset.y());
mS.m->draw(mS.canvas);
mS.canvas->restore();
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
mS.m->update();
if(mS.m->setToQuit) {
#ifdef __ANDROID__
mS.m->input_app_about_to_go_to_background_callback();
#endif
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<unsigned>(mS.m->input.cursorIcon)]);
}
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:
Logger::get().log("INFO", "[SDL_AppEvent] Quit");
if(mS.m->app_close_requested())
return SDL_APP_SUCCESS;
break;
case SDL_EVENT_WILL_ENTER_BACKGROUND:
Logger::get().log("INFO", "[SDL_AppEvent] Entering background");
mS.m->input_app_about_to_go_to_background_callback();
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();
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_WINDOW_SAFE_AREA_CHANGED: {
mS.m->input.backend_window_resize_update();
break;
}
case SDL_EVENT_MOUSE_MOTION:
#ifdef _WIN32
if(!mS.m->conf.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::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>());
break;
}
}
#ifdef NDEBUG
}
catch(const std::exception& e) {
Logger::get().log("FATAL", e.what());
return SDL_APP_FAILURE;
}
#endif
if(mS.m->setToQuit) {
#ifdef __ANDROID__
mS.m->input_app_about_to_go_to_background_callback();
#endif
return SDL_APP_SUCCESS;
}
return SDL_APP_CONTINUE;
}
// 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.
void SDL_AppQuit(void *appstate, SDL_AppResult result) {
#ifndef __ANDROID__
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();
#endif
}