2025-07-06 20:43:53 +03:00
# include "Helpers/SCollision.hpp"
# include "Helpers/StringHelpers.hpp"
2025-10-01 17:54:23 +03:00
# include "Helpers/FileDownloader.hpp"
2025-07-06 20:43:53 +03:00
# include "include/gpu/GpuTypes.h"
# include "sago/platform_folders.h"
# 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>
2025-08-20 11:17:05 +03:00
# include <include/core/SkAlphaType.h>
# include <include/core/SkColorType.h>
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"
2025-09-07 09:50:56 +03:00
// Use dedicated graphics card on Windows
# ifdef _WIN32
extern " C "
{
__declspec ( dllexport ) unsigned long NvOptimusEnablement = 0x00000001 ;
__declspec ( dllexport ) int AmdPowerXpressRequestHighPerformance = 1 ;
}
# endif
2025-10-13 18:54:33 +03:00
# 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 ;
sk_sp < SkSurface > surface ;
sk_sp < SkSurface > intermediateSurface ;
SkCanvas * intermediateCanvas ;
SDL_Surface * iconSurface = nullptr ;
std : : string iconData ;
SDL_Cursor * hiddenCursor = nullptr ;
std : : filesystem : : path configPath ;
std : : filesystem : : path homePath ;
std : : ofstream logFile ;
} MainData ;
# ifdef __EMSCRIPTEN__
bool emscriptenFilesystemReadReady = false ;
bool emscriptenFilesystemLoadConfigDone = false ;
# endif
bool load_file_to_string ( std : : string & toRet , std : : string_view fileName ) {
// https://nullptr.org/cpp-read-file-into-string/
std : : ifstream file ( fileName . data ( ) , std : : ios : : in | std : : ios : : binary | std : : ios : : ate ) ;
if ( ! file . is_open ( ) ) {
std : : cout < < " [load_file_to_string] Could not open file " < < fileName < < std : : endl ;
return false ;
}
size_t fileSize ;
auto tellgResult = file . tellg ( ) ;
if ( tellgResult = = - 1 ) {
std : : cout < < " [load_file_to_string] tellg failed for file " < < fileName < < std : : endl ;
return false ;
}
fileSize = static_cast < size_t > ( tellgResult ) ;
file . seekg ( 0 , std : : ios_base : : beg ) ;
toRet . resize ( fileSize ) ;
file . read ( toRet . data ( ) , fileSize ) ;
file . close ( ) ;
return true ;
}
void initialize_sdl ( MainStruct & mS , int wWidth , int wHeight ) {
SDL_SetHint ( SDL_HINT_APP_NAME , " InfiniPaint " ) ;
SDL_SetHint ( SDL_HINT_PEN_MOUSE_EVENTS , " 0 " ) ;
SDL_SetHint ( SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT , " #canvas " ) ; // Ensures that SDL only grabs input when browser is focused on canvas
if ( ! SDL_Init ( SDL_INIT_VIDEO | SDL_INIT_GAMEPAD ) )
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
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__
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
2025-08-01 16:16:40 +03:00
Logger : : get ( ) . log ( " INFO " , " GL Version: " + std : : string ( reinterpret_cast < const char * > ( glGetString ( GL_VERSION ) ) ) ) ;
2025-07-06 20:43:53 +03:00
# 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 ) ;
}
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
2026-02-16 21:27:42 +03:00
mS . intermediateSurface = mS . m - > create_native_surface ( mS . m - > window . size , true ) ;
2025-07-06 20:43:53 +03:00
mS . intermediateCanvas = mS . intermediateSurface - > getCanvas ( ) ;
if ( ! mS . intermediateCanvas )
throw std : : runtime_error ( " [resize_window] Could not create intermediate canvas " ) ;
mS . m - > window . surface = mS . intermediateSurface ;
# 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 ) ;
2025-11-20 12:38:28 +03:00
mS . surface = 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 . surface )
throw std : : runtime_error ( " [resize_window] Could not make surface " ) ;
mS . canvas = mS . surface - > getCanvas ( ) ;
if ( ! mS . canvas )
throw std : : runtime_error ( " [resize_window] No canvas made " ) ;
# endif
}
# ifdef __EMSCRIPTEN__
extern " C " {
EMSCRIPTEN_KEEPALIVE inline void emscripten_filesystem_loaded ( ) {
emscriptenFilesystemReadReady = true ;
}
}
const char * emscripten_before_unload ( int eventType , const void * reserved , void * userData ) {
MainStruct & mS = * ( ( MainStruct * ) userData ) ;
mS . m - > save_config ( ) ;
return " " ;
}
# endif
void init_logs ( MainStruct & mS ) {
mS . homePath = std : : filesystem : : current_path ( ) ;
# ifdef __EMSCRIPTEN__
EM_ASM (
FS . mkdir ( ' / infinipaint ' ) ;
FS . mount ( FS . filesystems . IDBFS , { } , ' / infinipaint ' ) ;
FS . syncfs ( true , ( err ) = > {
if ( err )
console . log ( " Error syncing IDBFS: " , err ) ;
else {
console . log ( " Synced to IDBFS. Read Ready " ) ;
Module [ " ccall " ] ( ' emscripten_filesystem_loaded ' , null , [ ] , [ ] ) ;
}
} ) ;
) ;
mS . homePath = std : : filesystem : : path ( " / " ) ;
mS . configPath = std : : filesystem : : path ( " /infinipaint " ) ;
2025-10-02 14:30:11 +03:00
# elif CONFIG_NEXT_TO_EXECUTABLE
std : : string CONFIG_FOLDER_NAME = " config " ;
std : : filesystem : : create_directory ( CONFIG_FOLDER_NAME ) ;
mS . configPath = mS . homePath / CONFIG_FOLDER_NAME ;
2025-07-06 20:43:53 +03:00
# else
2025-10-02 14:30:11 +03:00
std : : string CONFIG_FOLDER_NAME = " infinipaint " ;
2025-07-06 20:43:53 +03:00
std : : filesystem : : path configHome ( sago : : getConfigHome ( ) ) ;
std : : filesystem : : create_directory ( configHome ) ;
mS . configPath = configHome / CONFIG_FOLDER_NAME ;
# endif
if ( ! std : : filesystem : : create_directory ( mS . configPath ) )
std : : cout < < " Failed to create config directory. " < < std : : endl ;
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 ;
std : : cout < < " [INFO] " < < text < < std : : endl ;
} ) ;
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 + + )
2025-11-21 08:35:47 +03:00
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 ( ) ;
2025-10-13 18:54:33 +03:00
UErrorCode uerr = U_ZERO_ERROR ;
2025-11-15 09:48:02 +03:00
icudt = read_file_to_string ( " data/icudt77l-small.dat " ) ;
2025-10-13 18:54:33 +03:00
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 ;
2025-10-13 18:54:33 +03:00
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 ) ;
2025-10-01 17:54:23 +03:00
FileDownloader : : init ( ) ;
2025-07-06 20:43:53 +03:00
# 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 ) ;
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 - > window . defaultMSAASampleCount = 0 ;
mS . m - > window . defaultMSAASurfaceProps = SkSurfaceProps ( SkSurfaceProps : : kDynamicMSAA_Flag , kUnknown_SkPixelGeometry ) ;
mS . m - > logFile = & mS . logFile ;
mS . m - > configPath = mS . configPath ;
mS . m - > homePath = mS . homePath ;
2026-01-18 13:39:49 +03:00
# ifndef __EMSCRIPTEN__
mS . m - > documentsPath = sago : : getDocumentsFolder ( ) ;
if ( ! std : : filesystem : : exists ( mS . m - > documentsPath ) | | ! std : : filesystem : : is_directory ( mS . m - > documentsPath ) ) {
mS . m - > documentsPath = sago : : getDesktopFolder ( ) ;
if ( ! std : : filesystem : : exists ( mS . m - > documentsPath ) | | ! std : : filesystem : : is_directory ( mS . m - > documentsPath ) ) {
mS . m - > documentsPath = mS . m - > configPath ;
}
}
# endif
2025-07-06 20:43:53 +03:00
mS . m - > window . sdlWindow = mS . window ;
2025-11-23 12:53:50 +03:00
mS . m - > update_scale_and_density ( ) ;
2025-07-06 20:43:53 +03:00
# ifdef __EMSCRIPTEN__
emscripten_set_beforeunload_callback ( ( void * ) mSPtr , emscripten_before_unload ) ;
# else
mS . m - > load_config ( ) ;
# endif
# ifdef USE_BACKEND_VULKAN
# ifdef USE_SKIA_BACKEND_GRAPHITE
std : : unique_ptr < 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 . surface = mS . vulkanWindowContext - > getBackbufferSurface ( ) ;
# elif USE_SKIA_BACKEND_GANESH
2026-02-04 10:53:23 +03:00
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 . surface = mS . vulkanWindowContext - > getBackbufferSurface ( ) ;
# endif
# elif USE_BACKEND_OPENGL
sk_sp < const GrGLInterface > iface = GrGLMakeNativeInterface ( ) ;
2026-02-04 12:32:07 +03:00
GrContextOptions opts ;
opts . fSuppressPrints = true ;
2026-02-08 19:07:55 +03:00
opts . fDisableDriverCorrectnessWorkarounds = mS . m - > window . 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
2026-02-04 12:32:07 +03:00
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
# 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
resize_window ( mS ) ;
if ( listOfFilesToOpenFromCommand . empty ( ) ) {
mS . m - > new_tab ( {
2026-01-01 22:46:02 +03:00
. isClient = false
2025-07-06 20:43:53 +03:00
} , true ) ;
}
else {
2025-11-21 08:35:47 +03:00
for ( std : : filesystem : : path & f : listOfFilesToOpenFromCommand ) {
2025-07-06 20:43:53 +03:00
mS . m - > new_tab ( {
2026-01-01 22:46:02 +03:00
. isClient = false ,
2025-11-21 08:35:47 +03:00
. filePathSource = f
2025-07-06 20:43:53 +03:00
} , true ) ;
}
}
# ifdef NDEBUG
}
catch ( const std : : exception & e ) {
Logger : : get ( ) . log ( " FATAL " , e . what ( ) ) ;
return SDL_APP_FAILURE ;
}
# endif
return SDL_APP_CONTINUE ;
}
SDL_AppResult SDL_AppIterate ( void * appstate ) {
2025-08-26 12:03:09 +03:00
std : : chrono : : steady_clock : : time_point frameTimeStart = std : : chrono : : steady_clock : : now ( ) ;
2025-07-06 20:43:53 +03:00
MainStruct & mS = * ( ( MainStruct * ) appstate ) ;
# ifdef NDEBUG
try {
# endif
2025-11-23 12:53:50 +03:00
mS . m - > update_scale_and_density ( ) ;
2025-07-06 20:43:53 +03:00
# ifdef __EMSCRIPTEN__
if ( emscriptenFilesystemReadReady & & ! emscriptenFilesystemLoadConfigDone ) {
mS . m - > load_config ( ) ;
emscriptenFilesystemLoadConfigDone = true ;
}
# endif
mS . m - > update ( ) ;
if ( mS . m - > setToQuit )
return SDL_APP_SUCCESS ;
if ( mS . m - > input . toggleFullscreen ) {
SDL_SetWindowFullscreen ( mS . window , ! mS . m - > window . fullscreen ) ;
mS . m - > window . fullscreen = ! mS . m - > window . fullscreen ;
}
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 ) ] ) ;
}
if ( mS . m - > input . text . get_accepting_input ( ) & & ! mS . m - > input . text . lastAcceptingTextInputVal )
SDL_StartTextInput ( mS . window ) ;
else if ( ! mS . m - > input . text . get_accepting_input ( ) & & mS . m - > input . text . lastAcceptingTextInputVal )
SDL_StopTextInput ( mS . window ) ;
mS . m - > input . text . lastAcceptingTextInputVal = mS . m - > input . text . get_accepting_input ( ) ;
mS . intermediateCanvas - > save ( ) ;
mS . m - > draw ( mS . intermediateCanvas ) ;
mS . intermediateCanvas - > restore ( ) ;
# ifdef USE_BACKEND_VULKAN
mS . vulkanWindowContext - > getBackbufferSurface ( ) - > getCanvas ( ) - > drawImage ( mS . intermediateSurface - > makeTemporaryImage ( ) , 0 , 0 ) ;
2026-02-04 11:57:04 +03:00
mS . ctx - > flushAndSubmit ( ) ;
2025-07-06 20:43:53 +03:00
mS . vulkanWindowContext - > swapBuffers ( ) ;
# elif USE_BACKEND_OPENGL
mS . canvas - > drawImage ( mS . intermediateSurface - > makeTemporaryImage ( ) , 0 , 0 ) ;
2026-01-27 12:40:39 +03:00
mS . ctx - > flushAndSubmit ( ) ;
2025-07-06 20:43:53 +03:00
SDL_GL_SwapWindow ( mS . window ) ;
# endif
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
2025-08-26 12:03:09 +03:00
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-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 ;
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 ;
}
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 ) ;
break ;
case SDL_EVENT_WINDOW_MAXIMIZED :
mS . m - > window . maximized = true ;
break ;
case SDL_EVENT_WINDOW_RESTORED :
mS . m - > window . maximized = false ;
break ;
case SDL_EVENT_MOUSE_MOTION :
2025-11-23 12:53:50 +03:00
mS . m - > update_scale_and_density ( ) ;
2025-07-08 15:12:35 +03:00
# ifdef _WIN32
if ( ! mS . m - > toolbar . tabletOptions . ignoreMouseMovementWhenPenInProximity | | ! mS . m - > input . pen . inProximity )
# endif
{
2025-11-23 12:53:50 +03:00
mS . m - > input . mouse . set_pos ( { event - > motion . x * mS . m - > window . density , event - > motion . y * mS . m - > window . density } ) ;
2025-07-08 15:12:35 +03:00
}
2025-07-06 20:43:53 +03:00
break ;
case SDL_EVENT_MOUSE_BUTTON_UP :
if ( event - > button . button = = 1 )
mS . m - > input . mouse . leftDown = false ;
else if ( event - > button . button = = 2 )
mS . m - > input . mouse . middleDown = false ;
else if ( event - > button . button = = 3 )
mS . m - > input . mouse . rightDown = false ;
break ;
case SDL_EVENT_MOUSE_BUTTON_DOWN :
if ( event - > button . button = = 1 ) {
mS . m - > input . mouse . leftDown = true ;
mS . m - > input . mouse . leftClicks = event - > button . clicks ;
}
else if ( event - > button . button = = 2 ) {
mS . m - > input . mouse . middleDown = true ;
mS . m - > input . mouse . middleClicks = event - > button . clicks ;
}
else if ( event - > button . button = = 3 ) {
mS . m - > input . mouse . rightDown = true ;
mS . m - > input . mouse . rightClicks = event - > button . clicks ;
}
break ;
case SDL_EVENT_MOUSE_WHEEL :
mS . m - > input . mouse . scrollAmount . x ( ) + = event - > wheel . x ;
mS . m - > input . mouse . scrollAmount . y ( ) + = event - > wheel . y ;
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 :
2025-10-12 10:17:08 +03:00
mS . m - > input . text . newInput + = event - > text . text ;
2025-10-16 21:22:08 +03:00
mS . m - > input . text . add_text_to_textbox ( event - > text . text ) ;
2025-07-06 20:43:53 +03:00
break ;
case SDL_EVENT_DROP_FILE :
2025-11-23 12:53:50 +03:00
mS . m - > update_scale_and_density ( ) ;
2025-07-06 20:43:53 +03:00
mS . m - > input . droppedItems . emplace_back ( InputManager : : DroppedItem {
2025-11-23 12:53:50 +03:00
. pos = Vector2f { event - > drop . x , event - > drop . y } * mS . m - > window . density ,
2025-11-21 08:35:47 +03:00
. dataPath = std : : u8string_view ( reinterpret_cast < const char8_t * > ( event - > drop . data ) )
2025-07-06 20:43:53 +03:00
} ) ;
break ;
case SDL_EVENT_DROP_TEXT :
2025-11-23 12:53:50 +03:00
mS . m - > update_scale_and_density ( ) ;
2025-07-06 20:43:53 +03:00
mS . m - > input . droppedItems . emplace_back ( InputManager : : DroppedItem {
2025-11-23 12:53:50 +03:00
. pos = Vector2f { event - > drop . x , event - > drop . y } * mS . m - > window . density ,
2025-11-21 08:35:47 +03:00
. dataText = event - > drop . data
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 : {
2025-11-23 12:53:50 +03:00
mS . m - > update_scale_and_density ( ) ;
2025-07-06 20:43:53 +03:00
mS . m - > input . pen . isEraser = event - > pmotion . pen_state & SDL_PEN_INPUT_ERASER_TIP ;
2025-11-23 12:53:50 +03:00
mS . m - > input . mouse . set_pos ( { event - > pmotion . x * mS . m - > window . density , event - > pmotion . y * mS . m - > window . density } ) ;
2025-07-06 20:43:53 +03:00
break ;
}
case SDL_EVENT_PEN_DOWN : {
mS . m - > input . pen . isDown = true ;
mS . m - > input . mouse . leftDown = true ;
2025-09-29 17:05:53 +03:00
if ( ( std : : chrono : : steady_clock : : now ( ) - mS . m - > input . pen . lastPenLeftClickTime ) > std : : chrono : : milliseconds ( 300 ) )
mS . m - > input . pen . leftClicksSaved = 0 ;
mS . m - > input . pen . leftClicksSaved + + ;
mS . m - > input . mouse . leftClicks = mS . m - > input . pen . leftClicksSaved ;
mS . m - > input . pen . lastPenLeftClickTime = std : : chrono : : steady_clock : : now ( ) ;
2025-07-06 20:43:53 +03:00
mS . m - > input . pen . isEraser = event - > ptouch . eraser ;
2025-07-08 06:39:52 -05:00
//mS.m->input.mouse.set_pos({event->ptouch.x, event->ptouch.y});
2025-07-06 20:43:53 +03:00
break ;
}
case SDL_EVENT_PEN_AXIS : {
if ( event - > paxis . axis = = SDL_PEN_AXIS_PRESSURE )
mS . m - > input . pen . pressure = event - > paxis . value ;
2025-07-08 06:39:52 -05:00
//mS.m->input.mouse.set_pos({event->paxis.x, event->paxis.y});
2025-07-06 20:43:53 +03:00
break ;
}
case SDL_EVENT_PEN_UP : {
mS . m - > input . pen . isDown = false ;
mS . m - > input . mouse . leftDown = false ;
mS . m - > input . pen . isEraser = event - > ptouch . eraser ;
mS . m - > input . pen . pressure = 0.0 ;
2025-07-08 06:39:52 -05:00
//mS.m->input.mouse.set_pos({event->ptouch.x, event->ptouch.y});
2025-07-06 20:43:53 +03:00
break ;
}
case SDL_EVENT_PEN_BUTTON_UP : {
mS . m - > input . set_pen_button_up ( event - > pbutton ) ;
2025-07-08 06:39:52 -05:00
//mS.m->input.mouse.set_pos({event->pbutton.x, event->pbutton.y});
2025-07-06 20:43:53 +03:00
break ;
}
case SDL_EVENT_PEN_BUTTON_DOWN : {
mS . m - > input . set_pen_button_down ( event - > pbutton ) ;
2025-07-08 06:39:52 -05:00
//mS.m->input.mouse.set_pos({event->pbutton.x, event->pbutton.y});
2025-07-06 20:43:53 +03:00
break ;
}
default :
break ;
}
# ifdef NDEBUG
}
catch ( const std : : exception & e ) {
Logger : : get ( ) . log ( " FATAL " , e . what ( ) ) ;
return SDL_APP_FAILURE ;
}
# endif
return SDL_APP_CONTINUE ;
}
void SDL_AppQuit ( void * appstate , SDL_AppResult result ) {
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 ) {
Logger : : get ( ) . log ( " FATAL " , e . what ( ) ) ;
}
delete ( & mS ) ;
SDL_Quit ( ) ;
2025-11-20 13:26:09 +03:00
FileDownloader : : cleanup ( ) ;
2025-07-06 20:43:53 +03:00
}