diff options
| -rw-r--r-- | Makefile | 61 | ||||
| -rwxr-xr-x | bin/demo | bin | 0 -> 357760 bytes | |||
| -rw-r--r-- | bin/demo.d | 107 | ||||
| -rw-r--r-- | camera.c | 236 | ||||
| -rw-r--r-- | main.c | 395 | ||||
| -rw-r--r-- | nuklear_sdl3_renderer.h | 707 |
6 files changed, 1506 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2f123bd --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +# this Makefile is specific to GNU Make and GCC'compatible compilers + +PKG_CONFIG := $(or\ + $(shell pkg-config --version >/dev/null 2>/dev/null && echo pkg-config),\ + $(shell command -v pkg-config 2>/dev/null),\ + $(error missing pkg-config utility!)) + +PKG_SDL3 := $(or\ + $(and $(shell ${PKG_CONFIG} sdl3 --path 2>/dev/null),sdl3),\ + $(shell ${PKG_CONFIG} sdl3 --exists 2>/dev/null && echo sdl3),\ + $(error pkg-config was unable to find: sdl3)) + +PKG_SDL3_IMAGE := $(or\ + $(and $(shell ${PKG_CONFIG} sdl3-image --path 2>/dev/null),sdl3-image),\ + $(shell ${PKG_CONFIG} sdl3-image --exists 2>/dev/null && echo sdl3-image),\ + $(error pkg-config was unable to find: sdl3-image)) + +OSNAME := $(or\ + $(and ${EMSCRIPTEN}, Emscripten),\ + ${OS},\ + $(shell uname -s),\ + $(error could not detect OSNAME)) + +binext_Windows_NT := .exe +binext_Emscripten := .html +BINEXT := ${binext_${OSNAME}} + +TEMPDIR := ./bin +BIN := ${TEMPDIR}/demo${BINEXT} + +cflags += -std=c99 -Wall -Wextra -Wpedantic +cflags += -O2 +#cflags += -O0 -g +#cflags += -fsanitize=address +#cflags += -fsanitize=undefined +cflags += ${CFLAGS} + +cppflags += $(shell ${PKG_CONFIG} ${PKG_SDL3} ${PKG_SDL3_IMAGE} --cflags --keep-system-cflags) +cppflags += ${CPPFLAGS} + +ldflags += $(shell ${PKG_CONFIG} ${PKG_SDL3} ${PKG_SDL3_IMAGE} --libs-only-L --libs-only-other --keep-system-libs) +ldflags += ${LDFLAGS} + +ldlibs += $(shell ${PKG_CONFIG} ${PKG_SDL3} ${PKG_SDL3_IMAGE} --libs-only-l --keep-system-libs) +ldlibs += -lm -luvc +ldlibs += ${LDLIBS} +# HACK: this one is for compatibility with other demos +ldlibs += ${LIBS} + +DEP := ${TEMPDIR}/$(notdir ${BIN}).d + +SRC := main.c + +${BIN}: + mkdir -p $(dir $@) + ${CC} ${SRC} -o $@ -MD -MF ${DEP} ${cppflags} ${ldflags} ${ldlibs} ${cflags} + +${BIN}: ${SRC} + +-include ${DEP} + diff --git a/bin/demo b/bin/demo Binary files differnew file mode 100755 index 0000000..e1594c4 --- /dev/null +++ b/bin/demo diff --git a/bin/demo.d b/bin/demo.d new file mode 100644 index 0000000..7d509a5 --- /dev/null +++ b/bin/demo.d @@ -0,0 +1,107 @@ +bin/demo: main.c /usr/include/stdc-predef.h \ + /usr/include/SDL3/SDL_render.h /usr/include/SDL3/SDL_stdinc.h \ + /usr/include/SDL3/SDL_platform_defines.h \ + /usr/lib/gcc/x86_64-linux-gnu/15/include/stdarg.h /usr/include/string.h \ + /usr/include/x86_64-linux-gnu/bits/libc-header-start.h \ + /usr/include/features.h /usr/include/features-time64.h \ + /usr/include/x86_64-linux-gnu/bits/wordsize.h \ + /usr/include/x86_64-linux-gnu/bits/timesize.h \ + /usr/include/x86_64-linux-gnu/sys/cdefs.h \ + /usr/include/x86_64-linux-gnu/bits/long-double.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \ + /usr/lib/gcc/x86_64-linux-gnu/15/include/stddef.h /usr/include/wchar.h \ + /usr/include/x86_64-linux-gnu/bits/floatn.h \ + /usr/include/x86_64-linux-gnu/bits/floatn-common.h \ + /usr/include/x86_64-linux-gnu/bits/wchar.h \ + /usr/include/x86_64-linux-gnu/bits/types/wint_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/mbstate_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/__FILE.h \ + /usr/lib/gcc/x86_64-linux-gnu/15/include/stdint.h /usr/include/stdint.h \ + /usr/include/x86_64-linux-gnu/bits/types.h \ + /usr/include/x86_64-linux-gnu/bits/typesizes.h \ + /usr/include/x86_64-linux-gnu/bits/time64.h \ + /usr/include/x86_64-linux-gnu/bits/stdint-intn.h \ + /usr/include/x86_64-linux-gnu/bits/stdint-uintn.h \ + /usr/include/x86_64-linux-gnu/bits/stdint-least.h \ + /usr/include/inttypes.h \ + /usr/lib/gcc/x86_64-linux-gnu/15/include/stdbool.h \ + /usr/include/SDL3/SDL_begin_code.h /usr/include/SDL3/SDL_close_code.h \ + /usr/include/SDL3/SDL_blendmode.h /usr/include/SDL3/SDL_error.h \ + /usr/include/SDL3/SDL_events.h /usr/include/SDL3/SDL_audio.h \ + /usr/include/SDL3/SDL_endian.h /usr/include/endian.h \ + /usr/include/x86_64-linux-gnu/bits/endian.h \ + /usr/include/x86_64-linux-gnu/bits/endianness.h \ + /usr/include/SDL3/SDL_mutex.h /usr/include/SDL3/SDL_atomic.h \ + /usr/include/SDL3/SDL_thread.h /usr/include/SDL3/SDL_properties.h \ + /usr/include/SDL3/SDL_iostream.h /usr/include/SDL3/SDL_camera.h \ + /usr/include/SDL3/SDL_pixels.h /usr/include/SDL3/SDL_surface.h \ + /usr/include/SDL3/SDL_rect.h /usr/include/SDL3/SDL_gamepad.h \ + /usr/include/SDL3/SDL_guid.h /usr/include/SDL3/SDL_joystick.h \ + /usr/include/SDL3/SDL_power.h /usr/include/SDL3/SDL_sensor.h \ + /usr/include/SDL3/SDL_keyboard.h /usr/include/SDL3/SDL_keycode.h \ + /usr/include/SDL3/SDL_scancode.h /usr/include/SDL3/SDL_video.h \ + /usr/include/SDL3/SDL_mouse.h /usr/include/SDL3/SDL_pen.h \ + /usr/include/SDL3/SDL_touch.h /usr/include/SDL3/SDL_gpu.h \ + /usr/include/stdio.h /usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/FILE.h \ + /usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h \ + /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ + /usr/include/x86_64-linux-gnu/bits/stdio.h /usr/include/stdlib.h \ + /usr/include/x86_64-linux-gnu/bits/stdlib-bsearch.h \ + /usr/include/x86_64-linux-gnu/bits/stdlib-float.h /usr/include/math.h \ + /usr/include/x86_64-linux-gnu/bits/math-vector.h \ + /usr/include/x86_64-linux-gnu/bits/libm-simd-decl-stubs.h \ + /usr/include/x86_64-linux-gnu/bits/flt-eval-method.h \ + /usr/include/x86_64-linux-gnu/bits/fp-logb.h \ + /usr/include/x86_64-linux-gnu/bits/fp-fast.h \ + /usr/include/x86_64-linux-gnu/bits/mathcalls-macros.h \ + /usr/include/x86_64-linux-gnu/bits/mathcalls-helper-functions.h \ + /usr/include/x86_64-linux-gnu/bits/mathcalls.h /usr/include/assert.h \ + /usr/lib/gcc/x86_64-linux-gnu/15/include/limits.h \ + /usr/lib/gcc/x86_64-linux-gnu/15/include/syslimits.h \ + /usr/include/limits.h /usr/include/x86_64-linux-gnu/bits/posix1_lim.h \ + /usr/include/x86_64-linux-gnu/bits/local_lim.h \ + /usr/include/linux/limits.h \ + /usr/include/x86_64-linux-gnu/bits/pthread_stack_min-dynamic.h \ + /usr/include/x86_64-linux-gnu/bits/pthread_stack_min.h \ + /usr/include/x86_64-linux-gnu/bits/posix2_lim.h /usr/include/time.h \ + /usr/include/x86_64-linux-gnu/bits/time.h \ + /usr/include/x86_64-linux-gnu/bits/types/clock_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/time_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/struct_tm.h \ + /usr/include/x86_64-linux-gnu/bits/types/struct_timespec.h \ + /usr/include/x86_64-linux-gnu/bits/types/clockid_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/timer_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/struct_itimerspec.h \ + /usr/include/SDL3/SDL.h /usr/include/SDL3/SDL_assert.h \ + /usr/include/SDL3/SDL_asyncio.h /usr/include/SDL3/SDL_bits.h \ + /usr/include/SDL3/SDL_clipboard.h /usr/include/SDL3/SDL_cpuinfo.h \ + /usr/include/SDL3/SDL_dialog.h /usr/include/SDL3/SDL_dlopennote.h \ + /usr/include/SDL3/SDL_filesystem.h /usr/include/SDL3/SDL_haptic.h \ + /usr/include/SDL3/SDL_hidapi.h /usr/include/SDL3/SDL_hints.h \ + /usr/include/SDL3/SDL_init.h /usr/include/SDL3/SDL_loadso.h \ + /usr/include/SDL3/SDL_locale.h /usr/include/SDL3/SDL_log.h \ + /usr/include/SDL3/SDL_messagebox.h /usr/include/SDL3/SDL_metal.h \ + /usr/include/SDL3/SDL_misc.h /usr/include/SDL3/SDL_platform.h \ + /usr/include/SDL3/SDL_process.h /usr/include/SDL3/SDL_storage.h \ + /usr/include/SDL3/SDL_system.h /usr/include/SDL3/SDL_time.h \ + /usr/include/SDL3/SDL_timer.h /usr/include/SDL3/SDL_tray.h \ + /usr/include/SDL3/SDL_version.h /usr/include/SDL3/SDL_oldnames.h \ + /usr/include/SDL3_image/SDL_image.h /usr/include/SDL3/SDL_main.h \ + /usr/include/SDL3/SDL_main_impl.h camera.c /usr/include/libuvc/libuvc.h \ + /usr/include/x86_64-linux-gnu/sys/time.h \ + /usr/include/x86_64-linux-gnu/bits/types/struct_timeval.h \ + /usr/include/x86_64-linux-gnu/sys/select.h \ + /usr/include/x86_64-linux-gnu/bits/select.h \ + /usr/include/x86_64-linux-gnu/bits/types/sigset_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h \ + /usr/include/libuvc/libuvc_config.h /usr/include/unistd.h \ + /usr/include/x86_64-linux-gnu/bits/posix_opt.h \ + /usr/include/x86_64-linux-gnu/bits/confname.h \ + /usr/include/x86_64-linux-gnu/bits/getopt_posix.h \ + /usr/include/x86_64-linux-gnu/bits/getopt_core.h \ + /usr/include/x86_64-linux-gnu/bits/unistd_ext.h ../nuklear/nuklear.h \ + nuklear_sdl3_renderer.h diff --git a/camera.c b/camera.c new file mode 100644 index 0000000..fefa140 --- /dev/null +++ b/camera.c @@ -0,0 +1,236 @@ +#include "libuvc/libuvc.h" +#include <stdio.h> +#include <unistd.h> + +typedef struct camera { + uvc_context_t *ctx; + uvc_device_t *dev; + uvc_device_handle_t *devh; + uvc_stream_ctrl_t ctrl; + uvc_error_t res; + uvc_frame_t * latest_frame; +} camera_t; + +/* This callback function runs once per frame. Use it to perform any + * quick processing you need, or have it put the frame into your application's + * input queue. If this function takes too long, you'll start losing frames. */ +void cb(uvc_frame_t *frame, void *ptr) { + uvc_frame_t *bgr; + uvc_error_t ret; + // enum uvc_frame_format *frame_format = (enum uvc_frame_format *)ptr; + /* FILE *fp; + * static int jpeg_count = 0; + * static const char *H264_FILE = "iOSDevLog.h264"; + * static const char *MJPEG_FILE = ".jpeg"; + * char filename[16]; */ + + /* We'll convert the image from YUV/JPEG to BGR, so allocate space */ + bgr = uvc_allocate_frame(frame->width * frame->height * 3); + if (!bgr) { + printf("unable to allocate bgr frame!\n"); + return; + } + + // printf("callback! frame_format = %d, width = %d, height = %d, length = %lu, ptr = %p\n", + // frame->frame_format, frame->width, frame->height, frame->data_bytes, ptr); + + switch (frame->frame_format) { + case UVC_FRAME_FORMAT_H264: + /* use `ffplay H264_FILE` to play */ + /* fp = fopen(H264_FILE, "a"); + * fwrite(frame->data, 1, frame->data_bytes, fp); + * fclose(fp); */ + break; + case UVC_COLOR_FORMAT_MJPEG: + /* sprintf(filename, "%d%s", jpeg_count++, MJPEG_FILE); + * fp = fopen(filename, "w"); + * fwrite(frame->data, 1, frame->data_bytes, fp); + * fclose(fp); */ + break; + case UVC_COLOR_FORMAT_YUYV: + /* Do the BGR conversion */ + ret = uvc_any2bgr(frame, bgr); + if (ret) { + uvc_perror(ret, "uvc_any2bgr"); + uvc_free_frame(bgr); + return; + } + break; + default: + break; + } + + if (frame->sequence % 30 == 0) { + printf(" * got image %u, %dx%d\n", frame->sequence, frame->width, frame->height); + } + + /* Call a user function: + * + * my_type *my_obj = (*my_type) ptr; + * my_user_function(ptr, bgr); + * my_other_function(ptr, bgr->data, bgr->width, bgr->height); + */ + + /* Call a C++ method: + * + * my_type *my_obj = (*my_type) ptr; + * my_obj->my_func(bgr); + */ + // bgr->width = frame->width; + // bgr->height = frame->height; + // bgr->step = frame->step; + camera_t * camera = (camera_t *) ptr; + camera->latest_frame = frame; + + /* Use opencv.highgui to display the image: + * + * cvImg = cvCreateImageHeader( + * cvSize(bgr->width, bgr->height), + * IPL_DEPTH_8U, + * 3); + * + * cvSetData(cvImg, bgr->data, bgr->width * 3); + * + * cvNamedWindow("Test", CV_WINDOW_AUTOSIZE); + * cvShowImage("Test", cvImg); + * cvWaitKey(10); + * + * cvReleaseImageHeader(&cvImg); + */ + + uvc_free_frame(bgr); +} + +int get_camera_devices(camera_t * camera) { + /* Initialize a UVC service context. Libuvc will set up its own libusb + * context. Replace NULL with a libusb_context pointer to run libuvc + * from an existing libusb context. */ + camera->res = uvc_init(&camera->ctx, NULL); + + if (camera->res < 0) { + uvc_perror(camera->res, "uvc_init"); + return camera->res; + } + + puts("UVC initialized"); + + /* Locates the first attached UVC device, stores in dev */ + camera->res = uvc_find_device( + camera->ctx, &camera->dev, + 0, 0, NULL); /* filter devices: vendor_id, product_id, "serial_num" */ + + if (camera->res < 0) { + uvc_perror(camera->res, "uvc_find_device"); /* no devices found */ + } else { + puts("Device found"); + + /* Try to open the device: requires exclusive access */ + camera->res = uvc_open(camera->dev, &camera->devh); + + if (camera->res < 0) { + uvc_perror(camera->res, "uvc_open"); /* unable to open device */ + } else { + puts("Device opened"); + + /* Print out a message containing all the information that libuvc + * knows about the device */ + uvc_print_diag(camera->devh, stderr); + + const uvc_format_desc_t *format_desc = uvc_get_format_descs(camera->devh); + const uvc_frame_desc_t *frame_desc = format_desc->frame_descs; + enum uvc_frame_format frame_format; + int width = 640; + int height = 480; + int fps = 30; + + switch (format_desc->bDescriptorSubtype) { + case UVC_VS_FORMAT_MJPEG: + frame_format = UVC_COLOR_FORMAT_MJPEG; + break; + case UVC_VS_FORMAT_FRAME_BASED: + frame_format = UVC_FRAME_FORMAT_H264; + break; + default: + frame_format = UVC_FRAME_FORMAT_YUYV; + break; + } + + if (frame_desc) { + width = frame_desc->wWidth; + height = frame_desc->wHeight; + fps = 10000000 / frame_desc->dwDefaultFrameInterval; + } + + printf("\nFirst format: (%4s) %dx%d %dfps\n", format_desc->fourccFormat, width, height, fps); + + /* Try to negotiate first stream profile */ + camera->res = uvc_get_stream_ctrl_format_size( + camera->devh, &camera->ctrl, /* result stored in ctrl */ + frame_format, + width, height, fps /* width, height, fps */ + ); + + /* Print out the result */ + uvc_print_stream_ctrl(&camera->ctrl, stderr); + + if (camera->res < 0) { + uvc_perror(camera->res, "get_mode"); /* device doesn't provide a matching stream */ + } + } + } + return 0; +} + +void start_camera_stream(camera_t * camera) { + /* Start the video stream. The library will call user function cb: + * cb(frame, (void *) 12345) + */ + camera->res = uvc_start_streaming(camera->devh, &camera->ctrl, cb, camera, 0); + + if (camera->res < 0) { + uvc_perror(camera->res, "start_streaming"); /* unable to start stream */ + } else { + puts("Streaming..."); + + /* enable auto exposure - see uvc_set_ae_mode documentation */ + puts("Enabling auto exposure ..."); + const uint8_t UVC_AUTO_EXPOSURE_MODE_AUTO = 2; + camera->res = uvc_set_ae_mode(camera->devh, UVC_AUTO_EXPOSURE_MODE_AUTO); + if (camera->res == UVC_SUCCESS) { + puts(" ... enabled auto exposure"); + } else if (camera->res == UVC_ERROR_PIPE) { + /* this error indicates that the camera does not support the full AE mode; + * try again, using aperture priority mode (fixed aperture, variable exposure time) */ + puts(" ... full AE not supported, trying aperture priority mode"); + const uint8_t UVC_AUTO_EXPOSURE_MODE_APERTURE_PRIORITY = 8; + camera->res = uvc_set_ae_mode(camera->devh, UVC_AUTO_EXPOSURE_MODE_APERTURE_PRIORITY); + if (camera->res < 0) { + uvc_perror(camera->res, " ... uvc_set_ae_mode failed to enable aperture priority mode"); + } else { + puts(" ... enabled aperture priority auto exposure mode"); + } + } else { + uvc_perror(camera->res, " ... uvc_set_ae_mode failed to enable auto exposure mode"); + } + } +} + +void stop_camera_stream(camera_t * camera) { + /* End the stream. Blocks until last callback is serviced */ + uvc_stop_streaming(camera->devh); + puts("Done streaming."); +} + +void close_camera(camera_t * camera) { + /* Release our handle on the device */ + uvc_close(camera->devh); + puts("Device closed"); + + /* Release the device descriptor */ + uvc_unref_device(camera->dev); + + /* Close the UVC context. This closes and cleans up any existing device handles, + * and it closes the libusb context if one was not provided. */ + uvc_exit(camera->ctx); + puts("UVC exited"); +} @@ -0,0 +1,395 @@ +/* nuklear - public domain */ +#include <SDL3/SDL_render.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdarg.h> +#include <string.h> +#include <math.h> +#include <assert.h> +#include <limits.h> +#include <time.h> + +#include <SDL3/SDL.h> +#include <SDL3_image/SDL_image.h> + +/* This demo uses "main callbacks" which are new in SDL3 + * Those provide highly portable entry point and event loop for the app + * see: https://wiki.libsdl.org/SDL3/README-main-functions + * */ +#define SDL_MAIN_USE_CALLBACKS +#include <SDL3/SDL_main.h> + +#include "./camera.c" + +/* =============================================================== + * + * CONFIG + * + * ===============================================================*/ + +/* optional: sdl3_renderer does not need any of these defines + * (but some examples might need them, so be careful) */ +#define NK_INCLUDE_STANDARD_VARARGS +#define NK_INCLUDE_STANDARD_IO + +/* note that sdl3_renderer comes with nk_sdl_style_set_debug_font() + * so you may wish to use that instead of font baking */ +#define NK_INCLUDE_FONT_BAKING +#define NK_INCLUDE_DEFAULT_FONT + +/* note that sdl3_renderer comes with nk_sdl_allocator() + * and you probably want to use that allocator instead of the default ones */ +/*#define NK_INCLUDE_DEFAULT_ALLOCATOR*/ + +/* mandatory: sdl3_renderer depends on those defines */ +#define NK_INCLUDE_COMMAND_USERDATA +#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT + + +/* We can re-use the types provided by SDL which are extremely portable, + * so there is no need for Nuklear to detect those on its own */ +/*#define NK_INCLUDE_FIXED_TYPES*/ +#ifndef NK_INCLUDE_FIXED_TYPES + #define NK_INT8 Sint8 + #define NK_UINT8 Uint8 + #define NK_INT16 Sint16 + #define NK_UINT16 Uint16 + #define NK_INT32 Sint32 + #define NK_UINT32 Uint32 + /* SDL guarantees 'uintptr_t' typedef */ + #define NK_SIZE_TYPE uintptr_t + #define NK_POINTER_TYPE uintptr_t +#endif + +/* We can reuse the `bool` symbol because SDL3 guarantees its existence */ +/*#define NK_INCLUDE_STANDARD_BOOL*/ +#ifndef NK_INCLUDE_STANDARD_BOOL + #define NK_BOOL bool +#endif + +/* We can re-use various portable libc functions provided by SDL */ +#define NK_ASSERT(condition) SDL_assert(condition) +#define NK_STATIC_ASSERT(exp) SDL_COMPILE_TIME_ASSERT(, exp) +#define NK_MEMSET(dst, c, len) SDL_memset(dst, c, len) +#define NK_MEMCPY(dst, src, len) SDL_memcpy(dst, src, len) +#define NK_VSNPRINTF(s, n, f, a) SDL_vsnprintf(s, n, f, a) +#define NK_STRTOD(str, endptr) SDL_strtod(str, endptr) + +/* SDL3 does not provide "dtoa" (only integer versions) + * but we can emulate it with SDL_snprintf */ +static char* nk_sdl_dtoa(char *str, double d); +#define NK_DTOA(str, d) nk_sdl_dtoa(str, d) + +/* SDL can also provide us with math functions, but beware that Nuklear's own + * implementation can be slightly faster at the cost of some precision */ +#define NK_INV_SQRT(f) (1.0f / SDL_sqrtf(f)) +#define NK_SIN(f) SDL_sinf(f) +#define NK_COS(f) SDL_cosf(f) + +/* HACK: Nuklear pulls two stb libraries in order to use font baking + * those libraries pull in some libc headers internally, creating a linkage dependency, + * so you’ll most likely want to use SDL symbols instead */ +#define STBTT_ifloor(x) ((int)SDL_floor(x)) +#define STBTT_iceil(x) ((int)SDL_ceil(x)) +#define STBTT_sqrt(x) SDL_sqrt(x) +#define STBTT_pow(x,y) SDL_pow(x,y) +#define STBTT_fmod(x,y) SDL_fmod(x,y) +#define STBTT_cos(x) SDL_cosf(x) +#define STBTT_acos(x) SDL_acos(x) +#define STBTT_fabs(x) SDL_fabs(x) +#define STBTT_assert(x) SDL_assert(x) +#define STBTT_strlen(x) SDL_strlen(x) +#define STBTT_memcpy SDL_memcpy +#define STBTT_memset SDL_memset +#define stbtt_uint8 Uint8 +#define stbtt_int8 Sint8 +#define stbtt_uint16 Uint16 +#define stbtt_int16 Sint16 +#define stbtt_uint32 Uint32 +#define stbtt_int32 Sint32 +#define STBRP_SORT SDL_qsort +#define STBRP_ASSERT SDL_assert +/* There is no need to define STBTT_malloc/STBTT_free macros + * Nuklear will define those to user-provided nk_allocator */ + + +#define NK_IMPLEMENTATION +#include "../nuklear/nuklear.h" +#define NK_SDL3_RENDERER_IMPLEMENTATION +#include "nuklear_sdl3_renderer.h" + +#define WINDOW_WIDTH 1200 +#define WINDOW_HEIGHT 800 + +/* =============================================================== + * + * DEMO + * + * ===============================================================*/ + +struct nk_sdl_app { + SDL_Window* window; + SDL_Renderer* renderer; + struct nk_context * ctx; + struct nk_colorf bg; + enum nk_anti_aliasing AA; + camera_t* camera; +}; + +static SDL_AppResult +nk_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[]) +{ + struct nk_sdl_app* app; + struct nk_context* ctx; + camera_t * camera; + float font_scale; + NK_UNUSED(argc); + NK_UNUSED(argv); + + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { + return nk_sdl_fail(); + } + + app = SDL_malloc(sizeof(*app)); + if (app == NULL) { + return nk_sdl_fail(); + } + + app->camera = malloc(sizeof(*camera)); + app->camera->latest_frame = NULL; + get_camera_devices(app->camera); + + if (!SDL_CreateWindowAndRenderer("Nuklear: SDL3 Renderer", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE, &app->window, &app->renderer)) { + SDL_free(app); + return nk_sdl_fail(); + } + *appstate = app; + + if (!SDL_SetRenderVSync(app->renderer, 1)) { + SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "SDL_SetRenderVSync failed: %s", SDL_GetError()); + } + + app->bg.r = 0.10f; + app->bg.g = 0.18f; + app->bg.b = 0.24f; + app->bg.a = 1.0f; + + font_scale = 1; + { + /* This scaling logic was kept simple for the demo purpose. + * On some platforms, this might not be the exact scale + * that you want to use. For more information, see: + * https://wiki.libsdl.org/SDL3/README-highdpi */ + const float scale = SDL_GetWindowDisplayScale(app->window); + SDL_SetRenderScale(app->renderer, scale, scale); + font_scale = scale; + } + + ctx = nk_sdl_init(app->window, app->renderer, nk_sdl_allocator()); + app->ctx = ctx; + +#if 0 + { + /* If you don't want to use advanced Nuklear font baking API + * you can use simple ASCII debug font provided by SDL + * just change the `#if 0` above to `#if 1` */ + nk_sdl_style_set_debug_font(ctx); + + /* Note that since debug font is extremely small (only 8x8 pixels), + * scaling it does not make much sense. The font would appear blurry. */ + NK_UNUSED(font_scale); + + /* You may wish to change a few style options, here are few recommendations: */ + ctx->style.button.rounding = 0.0f; + ctx->style.menu_button.rounding = 0.0f; + ctx->style.property.rounding = 0.0f; + ctx->style.property.border = 0.0f; + ctx->style.option.border = -1.0f; + ctx->style.checkbox.border = -1.0f; + ctx->style.property.dec_button.border = -2.0f; + ctx->style.property.inc_button.border = -2.0f; + ctx->style.tab.tab_minimize_button.border = -2.0f; + ctx->style.tab.tab_maximize_button.border = -2.0f; + ctx->style.tab.node_minimize_button.border = -2.0f; + ctx->style.tab.node_maximize_button.border = -2.0f; + ctx->style.checkbox.spacing = 5.0f; + + /* It's better to disable anti-aliasing when using small fonts */ + app->AA = NK_ANTI_ALIASING_OFF; + } +#else + { + struct nk_font_atlas *atlas; + struct nk_font_config config = nk_font_config(0); + struct nk_font *font; + + /* set up the font atlas and add desired font; note that font sizes are + * multiplied by font_scale to produce better results at higher DPIs */ + atlas = nk_sdl_font_stash_begin(ctx); + font = nk_font_atlas_add_default(atlas, 16 * font_scale, &config); + /*font = nk_font_atlas_add_from_file(atlas, "../../../extra_font/DroidSans.ttf", 14 * font_scale, &config);*/ + /*font = nk_font_atlas_add_from_file(atlas, "../../../extra_font/Roboto-Regular.ttf", 16 * font_scale, &config);*/ + /*font = nk_font_atlas_add_from_file(atlas, "../../../extra_font/kenvector_future_thin.ttf", 13 * font_scale, &config);*/ + /*font = nk_font_atlas_add_from_file(atlas, "../../../extra_font/ProggyClean.ttf", 12 * font_scale, &config);*/ + /*font = nk_font_atlas_add_from_file(atlas, "../../../extra_font/ProggyTiny.ttf", 10 * font_scale, &config);*/ + /*font = nk_font_atlas_add_from_file(atlas, "../../../extra_font/Cousine-Regular.ttf", 13 * font_scale, &config);*/ + nk_sdl_font_stash_end(ctx); + + /* this hack makes the font appear to be scaled down to the desired + * size and is only necessary when font_scale > 1 */ + font->handle.height /= font_scale; + /*nk_style_load_all_cursors(ctx, atlas->cursors);*/ + nk_style_set_font(ctx, &font->handle); + + app->AA = NK_ANTI_ALIASING_ON; + } +#endif + + nk_input_begin(ctx); + + return SDL_APP_CONTINUE; +} + +SDL_AppResult +SDL_AppEvent(void *appstate, SDL_Event* event) +{ + struct nk_sdl_app* app = (struct nk_sdl_app*)appstate; + + switch (event->type) { + case SDL_EVENT_QUIT: + return SDL_APP_SUCCESS; + case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: + /* You may wish to rescale the renderer and Nuklear during this event. + * Without this the UI and Font could appear too small or too big. + * This is not handled by the demo in order to keep it simple, + * but you may wish to re-bake the Font whenever this happens. */ + SDL_Log("Unhandled scale event! Nuklear may appear blurry"); + return SDL_APP_CONTINUE; + } + + /* Remember to always rescale the event coordinates, + * if your renderer uses custom scale. */ + SDL_ConvertEventToRenderCoordinates(app->renderer, event); + + nk_sdl_handle_event(app->ctx, event); + + return SDL_APP_CONTINUE; +} + +SDL_AppResult +SDL_AppIterate(void *appstate) +{ + struct nk_sdl_app* app = (struct nk_sdl_app*)appstate; + struct nk_context* ctx = app->ctx; + int wwidth = 0; + int wheight = 0; + static char text[9][64]; + static int text_len[9]; + SDL_GetWindowSizeInPixels(app->window, &wwidth, &wheight); + + nk_input_end(ctx); + + /* GUI */ + if (nk_begin(ctx, "Recent", nk_rect(0, 0, 300, wheight), + NK_WINDOW_TITLE|NK_TEXT_CENTERED)) + { + + } + nk_end(ctx); + + /* GUI */ + if (nk_begin(ctx, "Main", nk_rect(300, 0, wwidth-200-300, wheight), + NK_WINDOW_BORDER)) + { + nk_layout_row_dynamic(ctx, 30, 2); + nk_label(ctx, "Loaf Key", 0); + nk_edit_string(ctx, NK_EDIT_SIMPLE, text[0], &text_len[0], 64, nk_filter_default); + if(nk_button_label(ctx, "Action!")) { + start_camera_stream(app->camera); + } + if(nk_button_label(ctx, "Cut!")) { + stop_camera_stream(app->camera); + } + nk_spacer(ctx); + nk_spacer(ctx); + } + + if (nk_begin(ctx, "Main", nk_rect(300, 0, wwidth-200-300, wheight), + NK_WINDOW_BORDER)) + { + if (app->camera->latest_frame != NULL && app->camera->latest_frame->data_bytes > 0) { + const uvc_frame_t * frame = app->camera->latest_frame; + printf("Texture WxH: %dx%d, Bytes: %zd\n", frame->width, frame->height, frame->data_bytes); + SDL_Texture * texture = SDL_CreateTexture(app->renderer, SDL_PIXELFORMAT_MJPG, SDL_TEXTUREACCESS_STREAMING, frame->width, frame->height); + if (texture == NULL) { + puts("ERROR Creating Texture!"); + const char * err = SDL_GetError(); + puts(err); + puts("---"); + } + bool upres = SDL_UpdateTexture(texture, NULL, frame->data, frame->data_bytes); + if (!upres) { + puts("Failed to add frame to Texture"); + const char * err = SDL_GetError(); + puts(err); + puts("---"); + } + + struct nk_image camera_image; + camera_image = nk_image_ptr(texture); + struct nk_command_buffer * canvas = nk_window_get_canvas(ctx); + struct nk_rect total_space = nk_window_get_content_region(ctx); + const struct nk_color grid_color = nk_rgba(255, 255, 255, 255); + nk_draw_image(canvas, total_space, &camera_image, grid_color); + } + } + nk_end(ctx); + + SDL_SetRenderDrawColorFloat(app->renderer, app->bg.r, app->bg.g, app->bg.b, app->bg.a); + SDL_RenderClear(app->renderer); + + nk_sdl_render(ctx, app->AA); + nk_sdl_update_TextInput(ctx); + + /* show if TextInput is active for debug purpose. Feel free to remove this. */ + SDL_SetRenderDrawColor(app->renderer, 0xFF, 0xFF, 0xFF, 0xFF); + // SDL_RenderDebugTextFormat(app->renderer, 10, 10, "APP WINDOW W:%d, H:%d", wwidth, wheight); + SDL_RenderPresent(app->renderer); + + nk_input_begin(ctx); + return SDL_APP_CONTINUE; +} + +void +SDL_AppQuit(void* appstate, SDL_AppResult result) +{ + struct nk_sdl_app* app = (struct nk_sdl_app*)appstate; + NK_UNUSED(result); + + if (app) { + close_camera(app->camera); + nk_input_end(app->ctx); + nk_sdl_shutdown(app->ctx); + SDL_DestroyRenderer(app->renderer); + SDL_DestroyWindow(app->window); + SDL_free(app); + } +} + +static char* +nk_sdl_dtoa(char *str, double d) +{ + NK_ASSERT(str); + if (!str) return NULL; + (void)SDL_snprintf(str, 99999, "%.17g", d); + return str; +} + diff --git a/nuklear_sdl3_renderer.h b/nuklear_sdl3_renderer.h new file mode 100644 index 0000000..25698ea --- /dev/null +++ b/nuklear_sdl3_renderer.h @@ -0,0 +1,707 @@ +/* nuklear - public domain */ + +/* + * ============================================================== + * + * API + * + * =============================================================== + */ + +#ifndef NK_SDL3_RENDERER_H_ +#define NK_SDL3_RENDERER_H_ + +#if SDL_MAJOR_VERSION < 3 + #error "nk_sdl3_renderer requires at least SDL 3.0.0" +#endif +#ifndef NK_INCLUDE_COMMAND_USERDATA + #error "nk_sdl3_renderer requires the NK_INCLUDE_COMMAND_USERDATA define" +#endif +#ifndef NK_INCLUDE_VERTEX_BUFFER_OUTPUT + #error "nk_sdl3_renderer requires the NK_INCLUDE_VERTEX_BUFFER_OUTPUT define" +#endif + +/* We have to redefine it because demos do not include any headers + * This is the same default value as the one from "src/nuklear_internal.h" */ +#ifndef NK_BUFFER_DEFAULT_INITIAL_SIZE + #define NK_BUFFER_DEFAULT_INITIAL_SIZE (4*1024) +#endif + +NK_API struct nk_context* nk_sdl_init(SDL_Window *win, SDL_Renderer *renderer, struct nk_allocator allocator); +#ifdef NK_INCLUDE_FONT_BAKING +NK_API struct nk_font_atlas* nk_sdl_font_stash_begin(struct nk_context* ctx); +NK_API void nk_sdl_font_stash_end(struct nk_context* ctx); +#endif +NK_API int nk_sdl_handle_event(struct nk_context* ctx, SDL_Event *evt); +NK_API void nk_sdl_render(struct nk_context* ctx, enum nk_anti_aliasing); +NK_API void nk_sdl_update_TextInput(struct nk_context* ctx); +NK_API void nk_sdl_shutdown(struct nk_context* ctx); +NK_API nk_handle nk_sdl_get_userdata(struct nk_context* ctx); +NK_API void nk_sdl_set_userdata(struct nk_context* ctx, nk_handle userdata); +NK_API void nk_sdl_style_set_debug_font(struct nk_context* ctx); +NK_API struct nk_allocator nk_sdl_allocator(void); + +#endif /* NK_SDL3_RENDERER_H_ */ + +/* + * ============================================================== + * + * IMPLEMENTATION + * + * =============================================================== + */ +#ifdef NK_SDL3_RENDERER_IMPLEMENTATION +#ifndef NK_SDL3_RENDERER_IMPLEMENTATION_ONCE +#define NK_SDL3_RENDERER_IMPLEMENTATION_ONCE + +#ifndef NK_SDL_DOUBLE_CLICK_LO +#define NK_SDL_DOUBLE_CLICK_LO 0.02 +#endif +#ifndef NK_SDL_DOUBLE_CLICK_HI +#define NK_SDL_DOUBLE_CLICK_HI 0.2 +#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]; + float col[4]; +}; + +struct nk_sdl { + SDL_Window *win; + SDL_Renderer *renderer; + struct nk_user_font* debug_font; + struct nk_sdl_device ogl; + struct nk_context ctx; +#ifdef NK_INCLUDE_FONT_BAKING + struct nk_font_atlas atlas; +#endif + struct nk_allocator allocator; + nk_handle userdata; + Uint64 last_left_click; + Uint64 last_render; + bool insert_toggle; + bool edit_was_active; +}; + +NK_API nk_handle +nk_sdl_get_userdata(struct nk_context* ctx) { + struct nk_sdl* sdl; + NK_ASSERT(ctx); + sdl = (struct nk_sdl*)ctx->userdata.ptr; + NK_ASSERT(sdl); + return sdl->userdata; +} + +NK_API void +nk_sdl_set_userdata(struct nk_context* ctx, nk_handle userdata) { + struct nk_sdl* sdl; + NK_ASSERT(ctx); + sdl = (struct nk_sdl*)ctx->userdata.ptr; + NK_ASSERT(sdl); + sdl->userdata = userdata; +} + +NK_INTERN void * +nk_sdl_alloc(nk_handle user, void *old, nk_size size) +{ + NK_UNUSED(user); + /* FIXME: nk_sdl_alloc should use SDL_realloc here, not SDL_malloc + * but this could cause a double-free due to bug within Nuklear, see: + * https://github.com/Immediate-Mode-UI/Nuklear/issues/768 + * */ +#if 0 + return SDL_realloc(old, size); +#else + NK_UNUSED(old); + return SDL_malloc(size); +#endif +} + +NK_INTERN void +nk_sdl_free(nk_handle user, void *old) +{ + NK_UNUSED(user); + SDL_free(old); +} + +NK_API struct nk_allocator +nk_sdl_allocator() +{ + struct nk_allocator allocator; + allocator.userdata.ptr = 0; + allocator.alloc = nk_sdl_alloc; + allocator.free = nk_sdl_free; + return allocator; +} + +NK_INTERN void +nk_sdl_device_upload_atlas(struct nk_context* ctx, const void *image, int width, int height) +{ + struct nk_sdl* sdl; + NK_ASSERT(ctx); + NK_ASSERT(image); + NK_ASSERT(width > 0); + NK_ASSERT(height > 0); + + sdl = (struct nk_sdl*)ctx->userdata.ptr; + NK_ASSERT(sdl); + + /* Clean up if the texture already exists. */ + if (sdl->ogl.font_tex != NULL) { + SDL_DestroyTexture(sdl->ogl.font_tex); + sdl->ogl.font_tex = NULL; + } + + sdl->ogl.font_tex = SDL_CreateTexture(sdl->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, width, height); + NK_ASSERT(sdl->ogl.font_tex); + SDL_UpdateTexture(sdl->ogl.font_tex, NULL, image, 4 * width); + SDL_SetTextureBlendMode(sdl->ogl.font_tex, SDL_BLENDMODE_BLEND); +} + +NK_API void +nk_sdl_update_TextInput(struct nk_context* ctx) +{ + struct nk_sdl* sdl; + bool active; + NK_ASSERT(ctx); + sdl = (struct nk_sdl*)ctx->userdata.ptr; + NK_ASSERT(sdl); + + /* Determine if Nuklear is using any top-level "edit" widget. + * Popups take higher priority because they block any incomming input. + * This will not work, if the widget is not updating context state properly. */ + if (!ctx->active) + active = false; + else if (ctx->active->popup.win) + active = ctx->active->popup.win->edit.active; + else + active = ctx->active->edit.active; + + /* decide, if TextInputActive should be unchanged/stoped/started + * and change its state accordingly for owned SDL Window */ + if (active != sdl->edit_was_active) + { + const bool window_edit_active = SDL_TextInputActive(sdl->win); + + /* If you ever hit this check, it means that the demo and your app + * (or something else) are all trying to manage TextInputActive state. + * This can cause subtle bugs where the state won't be what you expect. + * You can safely remove this assert and the demo will keep working, + * but make sure it does not cause any issues for you */ + NK_ASSERT(window_edit_active == sdl->edit_was_active && "something else changed TextInputActive state for this Window"); + + if (!window_edit_active && !sdl->edit_was_active && active) + SDL_StartTextInput(sdl->win); + else if (window_edit_active && sdl->edit_was_active && !active) + SDL_StopTextInput(sdl->win); + sdl->edit_was_active = active; + } + + /* FIXME: + * for full SDL3 integration, you also need to find current edit widget + * bounds and the text cursor offset, and pass this data into SDL_SetTextInputArea. + * This is currently not possible to do safely as Nuklear does not support it. + * https://wiki.libsdl.org/SDL3/SDL_SetTextInputArea + * https://github.com/Immediate-Mode-UI/Nuklear/pull/857 + */ +} + +NK_API void +nk_sdl_render(struct nk_context* ctx, enum nk_anti_aliasing AA) +{ + /* setup global state */ + struct nk_sdl* sdl; + NK_ASSERT(ctx); + sdl = (struct nk_sdl*)ctx->userdata.ptr; + NK_ASSERT(sdl); + + { /* setup internal delta time that Nuklear needs for animations */ + const Uint64 ticks = SDL_GetTicks(); + ctx->delta_time_seconds = (float)(ticks - sdl->last_render) / 1000.0f; + sdl->last_render = ticks; + } + + { + SDL_Rect saved_clip; + bool clipping_enabled; + int vs = sizeof(struct nk_sdl_vertex); + size_t vp = NK_OFFSETOF(struct nk_sdl_vertex, position); + size_t vt = NK_OFFSETOF(struct nk_sdl_vertex, uv); + size_t vc = NK_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_R32G32B32A32_FLOAT, NK_OFFSETOF(struct nk_sdl_vertex, col)}, + {NK_VERTEX_LAYOUT_END} + }; + 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 = sdl->ogl.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(&vbuf, &sdl->allocator, NK_BUFFER_DEFAULT_INITIAL_SIZE); + nk_buffer_init(&ebuf, &sdl->allocator, NK_BUFFER_DEFAULT_INITIAL_SIZE); + nk_convert(&sdl->ctx, &sdl->ogl.cmds, &vbuf, &ebuf, &config); + + /* iterate over and execute each draw command */ + offset = (const nk_draw_index*)nk_buffer_memory_const(&ebuf); + + clipping_enabled = SDL_RenderClipEnabled(sdl->renderer); + SDL_GetRenderClipRect(sdl->renderer, &saved_clip); + + nk_draw_foreach(cmd, &sdl->ctx, &sdl->ogl.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_SetRenderClipRect(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_FColor*)((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_SetRenderClipRect(sdl->renderer, &saved_clip); + if (!clipping_enabled) { + SDL_SetRenderClipRect(sdl->renderer, NULL); + } + + nk_clear(&sdl->ctx); + nk_buffer_clear(&sdl->ogl.cmds); + nk_buffer_free(&vbuf); + nk_buffer_free(&ebuf); + } +} + +NK_INTERN void +nk_sdl_clipboard_paste(nk_handle usr, struct nk_text_edit *edit) +{ + char *text; + int len; + NK_UNUSED(usr); + + /* this function returns empty string on failure, not NULL */ + text = SDL_GetClipboardText(); + NK_ASSERT(text); + + if (text[0] != '\0') { + /* FIXME: there is a bug in Nuklear that affects UTF8 clipboard handling + * "len" should be a buffer length, but due to bug it must be a glyph count + * see: https://github.com/Immediate-Mode-UI/Nuklear/pull/841 */ +#if 0 + len = nk_strlen(text); +#else + len = SDL_utf8strlen(text); +#endif + nk_textedit_paste(edit, text, len); + } + SDL_free(text); +} + +NK_INTERN void +nk_sdl_clipboard_copy(nk_handle usr, const char *text, int len) +{ + const char *ptext; + char *str; + size_t buflen; + int i; + struct nk_sdl* sdl = (struct nk_sdl*)usr.ptr; + NK_ASSERT(sdl); + if (len <= 0 || text == NULL) return; + + /* FIXME: there is a bug in Nuklear that affects UTF8 clipboard handling + * "len" is expected to be a buffer length, but due to bug it actually is a glyph count + * see: https://github.com/Immediate-Mode-UI/Nuklear/pull/841 */ +#if 0 + buflen = len + 1; + NK_UNUSED(ptext); +#else + ptext = text; + for (i = len; i > 0; i--) + (void)SDL_StepUTF8(&ptext, NULL); + buflen = (size_t)(ptext - text) + 1; +#endif + + str = sdl->allocator.alloc(sdl->allocator.userdata, 0, buflen); + if (!str) return; + SDL_strlcpy(str, text, buflen); + SDL_SetClipboardText(str); + sdl->allocator.free(sdl->allocator.userdata, str); +} + +NK_API struct nk_context* +nk_sdl_init(SDL_Window *win, SDL_Renderer *renderer, struct nk_allocator allocator) +{ + struct nk_sdl* sdl; + NK_ASSERT(win); + NK_ASSERT(renderer); + NK_ASSERT(allocator.alloc); + NK_ASSERT(allocator.free); + sdl = allocator.alloc(allocator.userdata, 0, sizeof(*sdl)); + NK_ASSERT(sdl); + SDL_zerop(sdl); + sdl->allocator.userdata = allocator.userdata; + sdl->allocator.alloc = allocator.alloc; + sdl->allocator.free = allocator.free; + sdl->win = win; + sdl->renderer = renderer; + nk_init(&sdl->ctx, &sdl->allocator, 0); + sdl->ctx.userdata = nk_handle_ptr((void*)sdl); + sdl->ctx.clip.copy = nk_sdl_clipboard_copy; + sdl->ctx.clip.paste = nk_sdl_clipboard_paste; + sdl->ctx.clip.userdata = nk_handle_ptr((void*)sdl); + nk_buffer_init(&sdl->ogl.cmds, &sdl->allocator, NK_BUFFER_DEFAULT_INITIAL_SIZE); + sdl->last_left_click = 0; + sdl->edit_was_active = false; + sdl->insert_toggle = false; + return &sdl->ctx; +} + +#ifdef NK_INCLUDE_FONT_BAKING +NK_API struct nk_font_atlas* +nk_sdl_font_stash_begin(struct nk_context* ctx) +{ + struct nk_sdl* sdl; + NK_ASSERT(ctx); + sdl = (struct nk_sdl*)ctx->userdata.ptr; + NK_ASSERT(sdl); + nk_font_atlas_init(&sdl->atlas, &sdl->allocator); + nk_font_atlas_begin(&sdl->atlas); + return &sdl->atlas; +} +#endif + +#ifdef NK_INCLUDE_FONT_BAKING +NK_API void +nk_sdl_font_stash_end(struct nk_context* ctx) +{ + struct nk_sdl* sdl; + const void *image; int w, h; + NK_ASSERT(ctx); + sdl = (struct nk_sdl*)ctx->userdata.ptr; + NK_ASSERT(sdl); + image = nk_font_atlas_bake(&sdl->atlas, &w, &h, NK_FONT_ATLAS_RGBA32); + NK_ASSERT(image); + nk_sdl_device_upload_atlas(&sdl->ctx, 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); + } +} +#endif + +NK_API int +nk_sdl_handle_event(struct nk_context* ctx, SDL_Event *evt) +{ + struct nk_sdl* sdl; + + NK_ASSERT(ctx); + NK_ASSERT(evt); + + sdl = (struct nk_sdl*)ctx->userdata.ptr; + NK_ASSERT(sdl); + + /* We only care about Window currently used by Nuklear */ + if (sdl->win != SDL_GetWindowFromEvent(evt)) { + return 0; + } + + switch(evt->type) + { + case SDL_EVENT_KEY_UP: /* KEYUP & KEYDOWN share same routine */ + case SDL_EVENT_KEY_DOWN: + { + int down = evt->type == SDL_EVENT_KEY_DOWN; + int ctrl_down = evt->key.mod & (SDL_KMOD_LCTRL | SDL_KMOD_RCTRL); + + /* In 99% of the time, you want to use scancodes, not real key codes, + * see: https://wiki.libsdl.org/SDL3/BestKeyboardPractices */ + switch(evt->key.scancode) + { + case SDL_SCANCODE_RSHIFT: /* RSHIFT & LSHIFT share same routine */ + case SDL_SCANCODE_LSHIFT: nk_input_key(ctx, NK_KEY_SHIFT, down); break; + case SDL_SCANCODE_DELETE: nk_input_key(ctx, NK_KEY_DEL, down); break; + case SDL_SCANCODE_RETURN: nk_input_key(ctx, NK_KEY_ENTER, down); break; + case SDL_SCANCODE_TAB: nk_input_key(ctx, NK_KEY_TAB, down); break; + case SDL_SCANCODE_BACKSPACE: nk_input_key(ctx, NK_KEY_BACKSPACE, down); break; + case SDL_SCANCODE_HOME: nk_input_key(ctx, NK_KEY_TEXT_START, down); + nk_input_key(ctx, NK_KEY_SCROLL_START, down); break; + case SDL_SCANCODE_END: nk_input_key(ctx, NK_KEY_TEXT_END, down); + nk_input_key(ctx, NK_KEY_SCROLL_END, down); break; + case SDL_SCANCODE_PAGEDOWN: nk_input_key(ctx, NK_KEY_SCROLL_DOWN, down); break; + case SDL_SCANCODE_PAGEUP: nk_input_key(ctx, NK_KEY_SCROLL_UP, down); break; + case SDL_SCANCODE_A: nk_input_key(ctx, NK_KEY_TEXT_SELECT_ALL, down && ctrl_down); break; + case SDL_SCANCODE_Z: nk_input_key(ctx, NK_KEY_TEXT_UNDO, down && ctrl_down); break; + case SDL_SCANCODE_R: nk_input_key(ctx, NK_KEY_TEXT_REDO, down && ctrl_down); break; + case SDL_SCANCODE_C: nk_input_key(ctx, NK_KEY_COPY, down && ctrl_down); break; + case SDL_SCANCODE_V: nk_input_key(ctx, NK_KEY_PASTE, down && ctrl_down); break; + case SDL_SCANCODE_X: nk_input_key(ctx, NK_KEY_CUT, down && ctrl_down); break; + case SDL_SCANCODE_B: nk_input_key(ctx, NK_KEY_TEXT_LINE_START, down && ctrl_down); break; + case SDL_SCANCODE_E: nk_input_key(ctx, NK_KEY_TEXT_LINE_END, down && ctrl_down); break; + case SDL_SCANCODE_UP: nk_input_key(ctx, NK_KEY_UP, down); break; + case SDL_SCANCODE_DOWN: nk_input_key(ctx, NK_KEY_DOWN, down); break; + case SDL_SCANCODE_ESCAPE: nk_input_key(ctx, NK_KEY_TEXT_RESET_MODE, down); break; + case SDL_SCANCODE_INSERT: + if (down) sdl->insert_toggle = !sdl->insert_toggle; + if (sdl->insert_toggle) { + nk_input_key(ctx, NK_KEY_TEXT_INSERT_MODE, down); + } else { + nk_input_key(ctx, NK_KEY_TEXT_REPLACE_MODE, down); + } + break; + case SDL_SCANCODE_LEFT: + if (ctrl_down) + nk_input_key(ctx, NK_KEY_TEXT_WORD_LEFT, down); + else + nk_input_key(ctx, NK_KEY_LEFT, down); + break; + case SDL_SCANCODE_RIGHT: + if (ctrl_down) + nk_input_key(ctx, NK_KEY_TEXT_WORD_RIGHT, down); + else + nk_input_key(ctx, NK_KEY_RIGHT, down); + break; + default: + return 0; + } + return 1; + } + + case SDL_EVENT_MOUSE_BUTTON_UP: /* MOUSEBUTTONUP & MOUSEBUTTONDOWN share same routine */ + case SDL_EVENT_MOUSE_BUTTON_DOWN: + { + const int x = evt->button.x, y = evt->button.y; + const int down = evt->button.down; + const double dt = (double)(evt->button.timestamp - sdl->last_left_click) / 1000000000.0; + switch(evt->button.button) + { + case SDL_BUTTON_LEFT: + nk_input_button(ctx, NK_BUTTON_LEFT, x, y, down); + nk_input_button(ctx, NK_BUTTON_DOUBLE, x, y, + down && dt > NK_SDL_DOUBLE_CLICK_LO && dt < NK_SDL_DOUBLE_CLICK_HI); + sdl->last_left_click = evt->button.timestamp; + 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; + default: + return 0; + } + } + return 1; + + case SDL_EVENT_MOUSE_MOTION: + ctx->input.mouse.pos.x = evt->motion.x; + ctx->input.mouse.pos.y = evt->motion.y; + ctx->input.mouse.delta.x = ctx->input.mouse.pos.x - ctx->input.mouse.prev.x; + ctx->input.mouse.delta.y = ctx->input.mouse.pos.y - ctx->input.mouse.prev.y; + return 1; + + case SDL_EVENT_TEXT_INPUT: + { + nk_glyph glyph; + nk_size len; + NK_ASSERT(evt->text.text); + len = SDL_strlen(evt->text.text); + NK_ASSERT(len <= NK_UTF_SIZE); + NK_MEMCPY(glyph, evt->text.text, len); + nk_input_glyph(ctx, glyph); + } + return 1; + + case SDL_EVENT_MOUSE_WHEEL: + nk_input_scroll(ctx, nk_vec2(evt->wheel.x, evt->wheel.y)); + return 1; + } + return 0; +} + +NK_API +void nk_sdl_shutdown(struct nk_context* ctx) +{ + struct nk_sdl* sdl; + NK_ASSERT(ctx); + sdl = (struct nk_sdl*)ctx->userdata.ptr; + NK_ASSERT(sdl); + +#ifdef NK_INCLUDE_FONT_BAKING + if (sdl->atlas.font_num > 0) + nk_font_atlas_clear(&sdl->atlas); +#endif + + nk_buffer_free(&sdl->ogl.cmds); + + if (sdl->ogl.font_tex != NULL) { + SDL_DestroyTexture(sdl->ogl.font_tex); + sdl->ogl.font_tex = NULL; + } + + nk_free(ctx); + sdl->allocator.free(sdl->allocator.userdata, sdl->debug_font); + sdl->allocator.free(sdl->allocator.userdata, sdl); +} + +/* Debug Font Width/Height of internal texture atlas + * This is a result of: ceil(sqrt('~' - ' ')) + * There is a sanity check for this value in nk_sdl_style_set_debug_font */ +#define NK_SDL_DFWH (10) + +NK_INTERN float +nk_sdl_query_debug_font_width(nk_handle handle, float height, + const char *text, int len) +{ + NK_UNUSED(handle); + return nk_utf_len(text, len) * height; +} + +NK_INTERN void +nk_sdl_query_debug_font_glypth(nk_handle handle, float height, + struct nk_user_font_glyph *glyph, + nk_rune codepoint, nk_rune next_codepoint) +{ + char ascii; + int idx, x, y; + NK_UNUSED(next_codepoint); + NK_UNUSED(handle); + + /* replace non-ASCII characters with question mark */ + ascii = (codepoint < (nk_rune)' ' || codepoint > (nk_rune)'~') + ? '?' : (char)codepoint; + NK_ASSERT(ascii >= ' ' && ascii <= '~'); + + idx = (int)(ascii - ' '); + x = idx / NK_SDL_DFWH; + y = idx % NK_SDL_DFWH; + NK_ASSERT(x >= 0 && x < NK_SDL_DFWH); + NK_ASSERT(y >= 0 && y < NK_SDL_DFWH); + + glyph->height = height; + glyph->width = height; + glyph->xadvance = height; + glyph->uv[0].x = (float)(x + 0) / NK_SDL_DFWH; + glyph->uv[0].y = (float)(y + 0) / NK_SDL_DFWH; + glyph->uv[1].x = (float)(x + 1) / NK_SDL_DFWH; + glyph->uv[1].y = (float)(y + 1) / NK_SDL_DFWH; + glyph->offset.x = 0.0f; + glyph->offset.y = 0.0f; +} + +NK_API void +nk_sdl_style_set_debug_font(struct nk_context* ctx) +{ + struct nk_user_font* font; + struct nk_sdl* sdl; + SDL_Surface *surface; + SDL_Renderer *renderer; + char buf[2]; + int x, y; + bool success; + NK_ASSERT(ctx); + + sdl = (struct nk_sdl*)ctx->userdata.ptr; + NK_ASSERT(sdl); + + if (sdl->debug_font) { + sdl->allocator.free(sdl->allocator.userdata, sdl->debug_font); + sdl->debug_font = 0; + } + + /* sanity check: formal proof of NK_SDL_DFWH value (which is 10) */ + NK_ASSERT(SDL_ceil(SDL_sqrt('~' - ' ')) == NK_SDL_DFWH); + + /* We use another Software Renderer just to make sure + * that we won't mutate any state in the main Renderer. */ + surface = SDL_CreateSurface( + NK_SDL_DFWH * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE, + NK_SDL_DFWH * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE, + SDL_PIXELFORMAT_RGBA32); + NK_ASSERT(surface); + renderer = SDL_CreateSoftwareRenderer(surface); + NK_ASSERT(renderer); + success = SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); + NK_ASSERT(success); + + /* SPACE is the first printable ASCII character */ + NK_MEMCPY(buf, " ", sizeof(buf)); + for (x = 0; x < NK_SDL_DFWH; x++) + { + for (y = 0; y < NK_SDL_DFWH; y++) + { + success = SDL_RenderDebugText( + renderer, + (float)(x * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE), + (float)(y * SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE), + buf); + NK_ASSERT(success); + buf[0]++; + + /* TILDE is the last printable ASCII character */ + if (buf[0] > '~') + break; + } + } + success = SDL_RenderPresent(renderer); + NK_ASSERT(success); + + font = sdl->allocator.alloc(sdl->allocator.userdata, 0, sizeof(*font)); + NK_ASSERT(font); + font->userdata.ptr = sdl; + font->height = SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE; + font->width = &nk_sdl_query_debug_font_width; + font->query = &nk_sdl_query_debug_font_glypth; + + /* HACK: nk_sdl_device_upload_atlas turns pixels into SDL_Texture + * and sets said Texture into sdl->ogl.font_tex + * then nk_sdl_render expects same Texture at font->texture */ + nk_sdl_device_upload_atlas(ctx, surface->pixels, surface->w, surface->h); + font->texture.ptr = sdl->ogl.font_tex; + + sdl->debug_font = font; + nk_style_set_font(ctx, font); + + SDL_DestroyRenderer(renderer); + SDL_DestroySurface(surface); +} + +#endif /* NK_SDL3_RENDERER_IMPLEMENTATION_ONCE */ +#endif /* NK_SDL3_RENDERER_IMPLEMENTATION */ + |
