diff --git a/demo/sdl3_renderer/.gitignore b/demo/sdl3_renderer/.gitignore new file mode 100644 index 00000000..db334178 --- /dev/null +++ b/demo/sdl3_renderer/.gitignore @@ -0,0 +1,2 @@ +SDL +build diff --git a/demo/sdl3_renderer/CMakeLists.txt b/demo/sdl3_renderer/CMakeLists.txt new file mode 100644 index 00000000..8294652f --- /dev/null +++ b/demo/sdl3_renderer/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.16) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$") +set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}" CACHE INTERNAL "") + +# CMAKE Modules +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +project(nuklear_sdl3_renderer) + +set(EXECUTABLE_NAME ${PROJECT_NAME}) + +# The SDL java code is hardcoded to load libmain.so on android, so we need to change EXECUTABLE_NAME +if (ANDROID) + set(EXECUTABLE_NAME main) + add_library(${EXECUTABLE_NAME} SHARED) +else() + add_executable(${EXECUTABLE_NAME}) +endif() + +# Add your sources to the target +target_sources(${EXECUTABLE_NAME} PRIVATE main.c) + +find_package(SDL3 REQUIRED) + +target_link_libraries(${EXECUTABLE_NAME} PUBLIC SDL3::SDL3) + +# Nuklear Include Directory +target_include_directories(${EXECUTABLE_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../..) + +# on Visual Studio, set our app as the default project +set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT "${EXECUTABLE_NAME}") diff --git a/demo/sdl3_renderer/cmake/FindSDL3.cmake b/demo/sdl3_renderer/cmake/FindSDL3.cmake new file mode 100644 index 00000000..54a3bde7 --- /dev/null +++ b/demo/sdl3_renderer/cmake/FindSDL3.cmake @@ -0,0 +1,8 @@ +include(FetchContent) +FetchContent_Declare( + SDL3 + GIT_REPOSITORY https://github.com/libsdl-org/SDL.git + GIT_TAG eb3fc06 + #GIT_SHALLOW 1 +) +FetchContent_MakeAvailable(SDL3) diff --git a/demo/sdl3_renderer/main.c b/demo/sdl3_renderer/main.c new file mode 100644 index 00000000..8c015488 --- /dev/null +++ b/demo/sdl3_renderer/main.c @@ -0,0 +1,71 @@ +#define SDL_MAIN_USE_CALLBACKS +#include +#include + +#define NK_SDL_RENDERER_IMPLEMENTATION +#include "nuklear_sdl3_renderer.h" + +typedef struct AppContext { + SDL_Window* window; + SDL_Renderer* renderer; +} AppContext; + +SDL_AppResult SDL_Fail(){ + SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Error %s", SDL_GetError()); + return SDL_APP_FAILURE; +} + +SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { + return SDL_Fail(); + } + + AppContext* appContext = (AppContext*)SDL_malloc(sizeof(AppContext)); + if (appContext == NULL) { + return SDL_Fail(); + } + + if (!SDL_CreateWindowAndRenderer("Nuklear: SDL3 Renderer", 1200, 800, SDL_WINDOW_RESIZABLE, &appContext->window, &appContext->renderer)) { + SDL_free(appContext); + return SDL_Fail(); + } + + *appstate = appContext; + + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event* event) { + AppContext* app = (AppContext*)appstate; + + switch (event->type) { + case SDL_EVENT_QUIT: + return SDL_APP_SUCCESS; + } + + nk_sdl_handle_event(event); + + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppIterate(void *appstate) { + AppContext* app = (AppContext*)appstate; + + SDL_SetRenderDrawColor(app->renderer, 255, 0, 255, SDL_ALPHA_OPAQUE); + SDL_RenderClear(app->renderer); + SDL_RenderPresent(app->renderer); + + return SDL_APP_CONTINUE; +} + +void SDL_AppQuit(void* appstate, SDL_AppResult result) { + AppContext* app = (AppContext*)appstate; + + if (app) { + SDL_DestroyRenderer(app->renderer); + SDL_DestroyWindow(app->window); + SDL_free(app); + } + + SDL_Quit(); +} diff --git a/demo/sdl3_renderer/nuklear_sdl3_renderer.h b/demo/sdl3_renderer/nuklear_sdl3_renderer.h new file mode 100644 index 00000000..4e26e38a --- /dev/null +++ b/demo/sdl3_renderer/nuklear_sdl3_renderer.h @@ -0,0 +1,355 @@ +/* + * ============================================================== + * + * API + * + * =============================================================== + */ +#ifndef NK_SDL3_RENDERER_H_ +#define NK_SDL3_RENDERER_H_ + +#ifndef NK_SDL3_RENDERER_SDL_H +#define NK_SDL3_RENDERER_SDL_H +#endif +#include NK_SDL3_RENDERER_SDL_H + +#define NK_SIZE_TYPE size_t + +#ifndef NUKLEAR_H +#define NUKLEAR_H "nuklear.h" +#endif +#include NUKLEAR_H + +NK_API struct nk_context* nk_sdl_init(SDL_Window *win, SDL_Renderer *renderer); +NK_API void nk_sdl_font_stash_begin(struct nk_font_atlas **atlas); +NK_API void nk_sdl_font_stash_end(void); +NK_API int nk_sdl_handle_event(SDL_Event *evt); +NK_API void nk_sdl_render(enum nk_anti_aliasing); +NK_API void nk_sdl_shutdown(void); +NK_API void nk_sdl_handle_grab(void); + +#endif /* NK_SDL_RENDERER_H_ */ + +/* + * ============================================================== + * + * IMPLEMENTATION + * + * =============================================================== + */ +#ifdef NK_SDL_RENDERER_IMPLEMENTATION + +/* Nuklear SDL3 Renderer defines some macros for Nuklear SDL use */ +#define NK_MEMCPY SDL_memcpy +#define NK_IMPLEMENTATION + +#include + +#if SDL_MAJOR_VERSION < 3 +#error "nuklear_sdl3_renderer requires at least SDL 3.0.0" +#endif + +struct nk_sdl_device { + struct nk_buffer cmds; + struct nk_draw_null_texture tex_null; + SDL_Texture *font_tex; +}; + +struct nk_sdl_vertex { + float position[2]; + float uv[2]; + nk_byte col[4]; +}; + +struct nk_sdl { + SDL_Window *win; + SDL_Renderer *renderer; + struct nk_sdl_device ogl; + struct nk_context ctx; + struct nk_font_atlas atlas; + Uint64 time_of_last_frame; +} nk_sdl; + +NK_INTERN void +nk_sdl_device_upload_atlas(const void *image, int width, int height) +{ + struct nk_sdl_device *dev = &sdl.ogl; + + SDL_Texture *g_SDLFontTexture = SDL_CreateTexture(sdl.renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, width, height); + if (g_SDLFontTexture == NULL) { + SDL_Log("error creating texture"); + return; + } + SDL_UpdateTexture(g_SDLFontTexture, NULL, image, 4 * width); + SDL_SetTextureBlendMode(g_SDLFontTexture, SDL_BLENDMODE_BLEND); + dev->font_tex = g_SDLFontTexture; +} + +NK_API void +nk_sdl_render(enum nk_anti_aliasing AA) +{ + /* setup global state */ + struct nk_sdl_device *dev = &sdl.ogl; + + { + SDL_Rect saved_clip; + SDL_bool clipping_enabled; + int vs = sizeof(struct nk_sdl_vertex); + size_t vp = offsetof(struct nk_sdl_vertex, position); + size_t vt = offsetof(struct nk_sdl_vertex, uv); + size_t vc = offsetof(struct nk_sdl_vertex, col); + + /* convert from command queue into draw list and draw to screen */ + const struct nk_draw_command *cmd; + const nk_draw_index *offset = NULL; + struct nk_buffer vbuf, ebuf; + + /* fill converting configuration */ + struct nk_convert_config config; + static const struct nk_draw_vertex_layout_element vertex_layout[] = { + {NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_sdl_vertex, position)}, + {NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_sdl_vertex, uv)}, + {NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(struct nk_sdl_vertex, col)}, + {NK_VERTEX_LAYOUT_END} + }; + + Uint64 now = SDL_GetTicks64(); + sdl.ctx.delta_time_seconds = (float)(now - sdl.time_of_last_frame) / 1000; + sdl.time_of_last_frame = now; + + NK_MEMSET(&config, 0, sizeof(config)); + config.vertex_layout = vertex_layout; + config.vertex_size = sizeof(struct nk_sdl_vertex); + config.vertex_alignment = NK_ALIGNOF(struct nk_sdl_vertex); + config.tex_null = dev->tex_null; + config.circle_segment_count = 22; + config.curve_segment_count = 22; + config.arc_segment_count = 22; + config.global_alpha = 1.0f; + config.shape_AA = AA; + config.line_AA = AA; + + /* convert shapes into vertexes */ + nk_buffer_init_default(&vbuf); + nk_buffer_init_default(&ebuf); + nk_convert(&sdl.ctx, &dev->cmds, &vbuf, &ebuf, &config); + + /* iterate over and execute each draw command */ + offset = (const nk_draw_index*)nk_buffer_memory_const(&ebuf); + + clipping_enabled = SDL_RenderIsClipEnabled(sdl.renderer); + SDL_RenderGetClipRect(sdl.renderer, &saved_clip); + + nk_draw_foreach(cmd, &sdl.ctx, &dev->cmds) + { + if (!cmd->elem_count) continue; + + { + SDL_Rect r; + r.x = cmd->clip_rect.x; + r.y = cmd->clip_rect.y; + r.w = cmd->clip_rect.w; + r.h = cmd->clip_rect.h; + SDL_RenderSetClipRect(sdl.renderer, &r); + } + + { + const void *vertices = nk_buffer_memory_const(&vbuf); + + SDL_RenderGeometryRaw(sdl.renderer, + (SDL_Texture *)cmd->texture.ptr, + (const float*)((const nk_byte*)vertices + vp), vs, + (const SDL_Color*)((const nk_byte*)vertices + vc), vs, + (const float*)((const nk_byte*)vertices + vt), vs, + (vbuf.needed / vs), + (void *) offset, cmd->elem_count, 2); + + offset += cmd->elem_count; + } + } + + SDL_RenderSetClipRect(sdl.renderer, &saved_clip); + if (!clipping_enabled) { + SDL_RenderSetClipRect(sdl.renderer, NULL); + } + + nk_clear(&sdl.ctx); + nk_buffer_clear(&dev->cmds); + nk_buffer_free(&vbuf); + nk_buffer_free(&ebuf); + } +} + +static void +nk_sdl_clipboard_paste(nk_handle usr, struct nk_text_edit *edit) +{ + const char *text = SDL_GetClipboardText(); + if (text) nk_textedit_paste(edit, text, nk_strlen(text)); + (void)usr; +} + +static void +nk_sdl_clipboard_copy(nk_handle usr, const char *text, int len) +{ + char *str = 0; + (void)usr; + if (!len) return; + str = (char*)SDL_malloc((size_t)len+1); + if (!str) return; + SDL_memcpy(str, text, (size_t)len); + str[len] = '\0'; + SDL_SetClipboardText(str); + SDL_free(str); +} + +NK_API struct nk_context* +nk_sdl_init(SDL_Window *win, SDL_Renderer *renderer) +{ + sdl.win = win; + sdl.renderer = renderer; + sdl.time_of_last_frame = SDL_GetTicks64(); + nk_init_default(&sdl.ctx, 0); + sdl.ctx.clip.copy = nk_sdl_clipboard_copy; + sdl.ctx.clip.paste = nk_sdl_clipboard_paste; + sdl.ctx.clip.userdata = nk_handle_ptr(0); + nk_buffer_init_default(&sdl.ogl.cmds); + return &sdl.ctx; +} + +NK_API void +nk_sdl_font_stash_begin(struct nk_font_atlas **atlas) +{ + nk_font_atlas_init_default(&sdl.atlas); + nk_font_atlas_begin(&sdl.atlas); + *atlas = &sdl.atlas; +} + +NK_API void +nk_sdl_font_stash_end(void) +{ + const void *image; int w, h; + image = nk_font_atlas_bake(&sdl.atlas, &w, &h, NK_FONT_ATLAS_RGBA32); + nk_sdl_device_upload_atlas(image, w, h); + nk_font_atlas_end(&sdl.atlas, nk_handle_ptr(sdl.ogl.font_tex), &sdl.ogl.tex_null); + if (sdl.atlas.default_font) + nk_style_set_font(&sdl.ctx, &sdl.atlas.default_font->handle); +} + +NK_API void +nk_sdl_handle_grab(void) +{ + struct nk_context *ctx = &sdl.ctx; + if (ctx->input.mouse.grab) { + SDL_SetRelativeMouseMode(SDL_TRUE); + } else if (ctx->input.mouse.ungrab) { + /* better support for older SDL by setting mode first; causes an extra mouse motion event */ + SDL_SetRelativeMouseMode(SDL_FALSE); + SDL_WarpMouseInWindow(sdl.win, (int)ctx->input.mouse.prev.x, (int)ctx->input.mouse.prev.y); + } else if (ctx->input.mouse.grabbed) { + ctx->input.mouse.pos.x = ctx->input.mouse.prev.x; + ctx->input.mouse.pos.y = ctx->input.mouse.prev.y; + } +} + +NK_API int +nk_sdl_handle_event(SDL_Event *evt) +{ + struct nk_context *ctx = &sdl.ctx; + + switch(evt->type) + { + case SDL_EVENT_KEY_UP: + case SDL_EVENT_KEY_DOWN: + { + int down = evt->type == SDL_KEYDOWN; + const Uint8* state = SDL_GetKeyboardState(0); + switch(evt->key.keysym.sym) + { + case SDLK_RSHIFT: /* RSHIFT & LSHIFT share same routine */ + case SDLK_LSHIFT: nk_input_key(ctx, NK_KEY_SHIFT, down); break; + case SDLK_DELETE: nk_input_key(ctx, NK_KEY_DEL, down); break; + case SDLK_RETURN: nk_input_key(ctx, NK_KEY_ENTER, down); break; + case SDLK_TAB: nk_input_key(ctx, NK_KEY_TAB, down); break; + case SDLK_BACKSPACE: nk_input_key(ctx, NK_KEY_BACKSPACE, down); break; + case SDLK_HOME: nk_input_key(ctx, NK_KEY_TEXT_START, down); + nk_input_key(ctx, NK_KEY_SCROLL_START, down); break; + case SDLK_END: nk_input_key(ctx, NK_KEY_TEXT_END, down); + nk_input_key(ctx, NK_KEY_SCROLL_END, down); break; + case SDLK_PAGEDOWN: nk_input_key(ctx, NK_KEY_SCROLL_DOWN, down); break; + case SDLK_PAGEUP: nk_input_key(ctx, NK_KEY_SCROLL_UP, down); break; + case SDLK_Z: nk_input_key(ctx, NK_KEY_TEXT_UNDO, down && state[SDL_SCANCODE_LCTRL]); break; + case SDLK_R: nk_input_key(ctx, NK_KEY_TEXT_REDO, down && state[SDL_SCANCODE_LCTRL]); break; + case SDLK_C: nk_input_key(ctx, NK_KEY_COPY, down && state[SDL_SCANCODE_LCTRL]); break; + case SDLK_V: nk_input_key(ctx, NK_KEY_PASTE, down && state[SDL_SCANCODE_LCTRL]); break; + case SDLK_X: nk_input_key(ctx, NK_KEY_CUT, down && state[SDL_SCANCODE_LCTRL]); break; + case SDLK_B: nk_input_key(ctx, NK_KEY_TEXT_LINE_START, down && state[SDL_SCANCODE_LCTRL]); break; + case SDLK_E: nk_input_key(ctx, NK_KEY_TEXT_LINE_END, down && state[SDL_SCANCODE_LCTRL]); break; + case SDLK_UP: nk_input_key(ctx, NK_KEY_UP, down); break; + case SDLK_DOWN: nk_input_key(ctx, NK_KEY_DOWN, down); break; + case SDLK_LEFT: + if (state[SDL_SCANCODE_LCTRL]) + nk_input_key(ctx, NK_KEY_TEXT_WORD_LEFT, down); + else nk_input_key(ctx, NK_KEY_LEFT, down); + break; + case SDLK_RIGHT: + if (state[SDL_SCANCODE_LCTRL]) + nk_input_key(ctx, NK_KEY_TEXT_WORD_RIGHT, down); + else nk_input_key(ctx, NK_KEY_RIGHT, down); + break; + } + } + return 1; + + case SDL_EVENT_MOUSE_BUTTON_UP: + case SDL_EVENT_MOUSE_BUTTON_DOWN: + { + int down = evt->type == SDL_MOUSEBUTTONDOWN; + const int x = evt->button.x, y = evt->button.y; + switch(evt->button.button) + { + case SDL_BUTTON_LEFT: + if (evt->button.clicks > 1) + nk_input_button(ctx, NK_BUTTON_DOUBLE, x, y, down); + nk_input_button(ctx, NK_BUTTON_LEFT, x, y, down); break; + case SDL_BUTTON_MIDDLE: nk_input_button(ctx, NK_BUTTON_MIDDLE, x, y, down); break; + case SDL_BUTTON_RIGHT: nk_input_button(ctx, NK_BUTTON_RIGHT, x, y, down); break; + } + } + return 1; + + case SDL_EVENT_MOUSE_MOTION: + if (ctx->input.mouse.grabbed) { + int x = (int)ctx->input.mouse.prev.x, y = (int)ctx->input.mouse.prev.y; + nk_input_motion(ctx, x + evt->motion.xrel, y + evt->motion.yrel); + } + else nk_input_motion(ctx, evt->motion.x, evt->motion.y); + return 1; + + case SDL_EVENT_TEXT_INPUT: + { + nk_glyph glyph; + SDL_memcpy(glyph, evt->text.text, NK_UTF_SIZE); + nk_input_glyph(ctx, glyph); + } + return 1; + + case SDL_EVENT_MOUSE_WHEEL: + nk_input_scroll(ctx,nk_vec2((float)evt->wheel.x,(float)evt->wheel.y)); + return 1; + } + return 0; +} + +NK_API +void nk_sdl_shutdown(struct nk_sdl* sdl) +{ + struct nk_sdl_device *dev = &sdl->ogl; + nk_font_atlas_clear(&sdl->atlas); + SDL_free(&sdl->ctx); + SDL_DestroyTexture(dev->font_tex); + /* glDeleteTextures(1, &dev->font_tex); */ + nk_buffer_free(&dev->cmds); + NK_MEMSET(sdl, 0, sizeof(sdl)); +} + +#endif /* NK_SDL_RENDERER_IMPLEMENTATION */