875 lines
35 KiB
C++
Executable file
875 lines
35 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) {
|
|
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_TOUCH_EVENTS, "0");
|
|
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
|
|
|
|
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;
|
|
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(Logger::LogType::INFO, "GL Version: " + std::string(reinterpret_cast<const char*>(glGetString(GL_VERSION))));
|
|
#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
|
|
|
|
SDL_ShowWindow(mS.window);
|
|
|
|
mS.m->set_vsync_value(mS.m->conf.vsyncValue);
|
|
|
|
for(unsigned i = 0; i < SDL_SYSTEM_CURSOR_COUNT; i++)
|
|
mS.systemCursors[i] = SDL_CreateSystemCursor((SDL_SystemCursor)i);
|
|
|
|
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());
|
|
mS.m->input.update_safe_area();
|
|
}
|
|
|
|
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().set_log_function(Logger::LogType::FATAL, [&, mS = &mS](const std::string& text) {
|
|
mS->logFile << "[FATAL] " << text << std::endl;
|
|
std::cerr << "[FATAL] " << text << std::endl;
|
|
mS->logFile.close();
|
|
});
|
|
Logger::get().set_log_function(Logger::LogType::INFO, [&, mS = &mS](const std::string& text) {
|
|
mS->logFile << "[INFO] " << text << std::endl;
|
|
Logger::get().cross_platform_println("[INFO] " + text);
|
|
});
|
|
|
|
Logger::get().log(Logger::LogType::INFO, "Home Path: " + mS.homePath.string());
|
|
Logger::get().log(Logger::LogType::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
|
|
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->update_scale_and_density();
|
|
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);
|
|
#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
|
|
|
|
mS.m->window.canCreateSurfaces = true;
|
|
resize_window(mS);
|
|
|
|
if(mS.m->conf.mobileUI)
|
|
mS.m->set_first_screen(std::make_unique<FileSelectScreen>(*mS.m));
|
|
else {
|
|
mS.m->set_first_screen(std::make_unique<DesktopDrawingProgramScreen>(*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(Logger::LogType::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(Logger::LogType::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(Logger::LogType::INFO, "[SDL_AppEvent] Quit");
|
|
if(mS.m->app_close_requested())
|
|
return SDL_APP_SUCCESS;
|
|
break;
|
|
case SDL_EVENT_WILL_ENTER_BACKGROUND:
|
|
Logger::get().log(Logger::LogType::INFO, "[SDL_AppEvent] Entering background");
|
|
mS.m->input_app_about_to_go_to_background_callback();
|
|
break;
|
|
case SDL_EVENT_WILL_ENTER_FOREGROUND:
|
|
Logger::get().log(Logger::LogType::INFO, "[SDL_AppEvent] Entering foreground");
|
|
mS.m->input_app_about_to_go_to_foreground_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: {
|
|
if(!mS.m->conf.tabletOptions.disableTouchWhenPenInProximity || !mS.m->input.pen.inProximity)
|
|
mS.m->input.backend_touch_finger_down_update(event->tfinger);
|
|
break;
|
|
}
|
|
case SDL_EVENT_FINGER_UP: {
|
|
if(!mS.m->conf.tabletOptions.disableTouchWhenPenInProximity || !mS.m->input.pen.inProximity)
|
|
mS.m->input.backend_touch_finger_up_update(event->tfinger);
|
|
break;
|
|
}
|
|
case SDL_EVENT_FINGER_MOTION: {
|
|
if(!mS.m->conf.tabletOptions.disableTouchWhenPenInProximity || !mS.m->input.pen.inProximity)
|
|
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>());
|
|
else if(event->type == CustomEvents::MobileImportCanvasEvent::EVENT_NUM)
|
|
mS.m->input_mobile_import_canvas_callback(*CustomEvents::get_event<CustomEvents::MobileImportCanvasEvent>());
|
|
break;
|
|
}
|
|
}
|
|
#ifdef NDEBUG
|
|
}
|
|
catch(const std::exception& e) {
|
|
Logger::get().log(Logger::LogType::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(Logger::LogType::FATAL, e.what());
|
|
}
|
|
|
|
delete (&mS);
|
|
|
|
SDL_Quit();
|
|
|
|
FileDownloader::cleanup();
|
|
#endif
|
|
}
|