From fca14eac32e671aef2ab0c19ed30e255da551d3f Mon Sep 17 00:00:00 2001 From: Jake Turner Date: Fri, 8 Jul 2022 06:04:28 +0100 Subject: [PATCH] Apple minimal Nuklear implementation using AppKit --- util/test/demos/CMakeLists.txt | 2 + util/test/demos/apple/nuklear_appkit.h | 1155 +++++++++++++++++++++++ util/test/demos/apple/nuklear_appkit.mm | 27 + util/test/demos/main.cpp | 50 +- 4 files changed, 1228 insertions(+), 6 deletions(-) create mode 100644 util/test/demos/apple/nuklear_appkit.h create mode 100644 util/test/demos/apple/nuklear_appkit.mm diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt index b7f62165e..4e87c9d81 100644 --- a/util/test/demos/CMakeLists.txt +++ b/util/test/demos/CMakeLists.txt @@ -135,6 +135,8 @@ endif() if(APPLE) list(APPEND SRC + apple/nuklear_appkit.mm + apple/nuklear_appkit.h apple/official/metal-cpp.cpp apple/official/metal-cpp.h apple/apple_platform.cpp diff --git a/util/test/demos/apple/nuklear_appkit.h b/util/test/demos/apple/nuklear_appkit.h new file mode 100644 index 000000000..bc32dcad7 --- /dev/null +++ b/util/test/demos/apple/nuklear_appkit.h @@ -0,0 +1,1155 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2022 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +// This is the minimum required to get the RenderDoc demo UI to work +// The keyboard handling for delete is not perfect + +/* + * ============================================================== + * + * API + * + * =============================================================== + */ + +#ifndef NK_APPKIT_H_ +#define NK_APPKIT_H_ + +struct AppkitFont; +struct nk_appkit_window; + +NK_API struct nk_context *nk_appkit_create(nk_appkit_window *win); +NK_API void nk_appkit_init(AppkitFont *font); +NK_API void nk_appkit_shutdown(void); +NK_API void nk_appkit_new_frame(void); +NK_API void nk_appkit_render(struct nk_color clear); + +NK_API AppkitFont *nk_appkit_create_font(const char *name, int size); +NK_API void nk_appkit_delete_font(AppkitFont *font); + +#endif // #ifndef NK_APPKIT_H_ + +#if defined(NK_APPKIT_IMPLEMENTATION) || defined(NK_APPKIT_OBJC_IMPLEMENTATION) + +#define nk_appkit_release 0 +#define nk_appkit_press 1 + +#define nk_appkit_key_unknown -1 + +#define nk_appkit_key_0 48 +#define nk_appkit_key_1 49 +#define nk_appkit_key_2 50 +#define nk_appkit_key_3 51 +#define nk_appkit_key_4 52 +#define nk_appkit_key_5 53 +#define nk_appkit_key_6 54 +#define nk_appkit_key_7 55 +#define nk_appkit_key_8 56 +#define nk_appkit_key_9 57 + +#define nk_appkit_key_A 65 +#define nk_appkit_key_B 66 +#define nk_appkit_key_C 67 +#define nk_appkit_key_D 68 +#define nk_appkit_key_E 69 +#define nk_appkit_key_F 70 +#define nk_appkit_key_G 71 +#define nk_appkit_key_H 72 +#define nk_appkit_key_I 73 +#define nk_appkit_key_J 74 +#define nk_appkit_key_K 75 +#define nk_appkit_key_L 76 +#define nk_appkit_key_M 77 +#define nk_appkit_key_N 78 +#define nk_appkit_key_O 79 +#define nk_appkit_key_P 80 +#define nk_appkit_key_Q 81 +#define nk_appkit_key_R 82 +#define nk_appkit_key_S 83 +#define nk_appkit_key_T 84 +#define nk_appkit_key_U 85 +#define nk_appkit_key_V 86 +#define nk_appkit_key_W 87 +#define nk_appkit_key_X 88 +#define nk_appkit_key_Y 89 +#define nk_appkit_key_Z 90 + +#define nk_appkit_key_enter 301 +#define nk_appkit_key_tab 302 +#define nk_appkit_key_backspace 303 +#define nk_appkit_key_delete 305 +#define nk_appkit_key_right 306 +#define nk_appkit_key_left 307 +#define nk_appkit_key_down 308 +#define nk_appkit_key_up 309 +#define nk_appkit_key_page_up 310 +#define nk_appkit_key_page_down 311 +#define nk_appkit_key_home 312 +#define nk_appkit_key_end 313 + +#define nk_appkit_key_left_shift 400 +#define nk_appkit_key_left_control 401 +#define nk_appkit_key_right_shift 402 +#define nk_appkit_key_right_control 403 + +#define nk_appkit_key_first nk_appkit_key_0 +#define nk_appkit_key_last nk_appkit_key_right_control + +#define nk_appkit_mouse_button_left 0 +#define nk_appkit_mouse_button_right 1 +#define nk_appkit_mouse_button_middle 2 + +struct nk_appkit_window; + +typedef void (*nk_appkit_mouse_button_cb)(nk_appkit_window *, int, int, int); +typedef void (*nk_appkit_character_cb)(nk_appkit_window *, unsigned int); +typedef void (*nk_appkit_key_cb)(nk_appkit_window *, int, int); +typedef void (*nk_appkit_scroll_cb)(nk_appkit_window *, double, double); + +#endif // #if defined(NK_APPKIT_IMPLEMENTATION) || defined(NK_APPKIT_OBJC_IMPLEMENTATION) + +/* + * ============================================================== + * + * IMPLEMENTATION + * + * =============================================================== + */ + +#ifdef NK_APPKIT_IMPLEMENTATION + +#include + +#ifndef NK_APPKIT_TEXT_MAX +#define NK_APPKIT_TEXT_MAX 256 +#endif +#ifndef NK_APPKIT_DOUBLE_CLICK_LO +#define NK_APPKIT_DOUBLE_CLICK_LO 0.02 +#endif +#ifndef NK_APPKIT_DOUBLE_CLICK_HI +#define NK_APPKIT_DOUBLE_CLICK_HI 0.2 +#endif + +struct AppkitFont +{ + struct nk_user_font nk; + float height; +}; + +static struct nk_appkit +{ + nk_appkit_window *win; + struct nk_context ctx; + unsigned int text[NK_APPKIT_TEXT_MAX]; + int text_len; + struct nk_vec2 scroll; + double last_button_click; + int is_double_click_down; + struct nk_vec2 double_click_pos; + uint64_t timerFrequency; +} nk_appkit; + +bool nk_appkit_core_initialize(void); +void nk_appkit_core_shutdown(void); + +nk_appkit_window *nk_appkit_window_create(int width, int height, const char *title); +void nk_appkit_window_delete(nk_appkit_window *window); +bool nk_appkit_window_is_closed(nk_appkit_window *window); +void nk_appkit_window_get_mouse_position(nk_appkit_window *window, double *xpos, double *ypos); +int nk_appkit_window_get_mouse_button_state(nk_appkit_window *window, int button); +int nk_appkit_window_get_key_state(nk_appkit_window *window, int key); + +void nk_appkit_window_set_mouse_button_callback(nk_appkit_window *window, + nk_appkit_mouse_button_cb callback); +void nk_appkit_window_set_scroll_callback(nk_appkit_window *window, nk_appkit_scroll_cb callback); +void nk_appkit_window_set_character_callback(nk_appkit_window *window, + nk_appkit_character_cb callback); +void nk_appkit_window_set_key_callback(nk_appkit_window *window, nk_appkit_key_cb callback); + +void nk_appkit_drawing_begin(nk_appkit_window *window, u_int8_t r, u_int8_t g, u_int8_t b, + u_int8_t a); +void nk_appkit_drawing_end(nk_appkit_window *window); + +void nk_appkit_drawing_filled_rect(nk_appkit_window *window, short x, short y, u_int16_t w, + u_int16_t h, u_int8_t r, u_int8_t g, u_int8_t b, u_int8_t a, + int rounding); +void nk_appkit_drawing_rect(nk_appkit_window *window, short x, short y, u_int16_t w, u_int16_t h, + u_int8_t r, u_int8_t g, u_int8_t b, u_int8_t a, int rounding, + int lineThickness); +void nk_appkit_drawing_scissor(nk_appkit_window *window, short x, short y, u_int16_t w, u_int16_t h); +void nk_appkit_drawing_text(nk_appkit_window *window, short x, short y, u_int16_t w, u_int16_t h, + const char *text, int len, void *font, u_int8_t bgR, u_int8_t bgG, + u_int8_t bgB, u_int8_t bgA, u_int8_t fgR, u_int8_t fgG, u_int8_t fgB, + u_int8_t fgA); + +float nk_appkit_drawing_set_font(nk_appkit_window *window, const char *name, float size); +float nk_appkit_drawing_get_text_width(nk_appkit_window *window, const char *text, int len); + +static float nk_appkit_font_get_text_width(nk_handle handle, float height, const char *text, int len) +{ + AppkitFont *font = (AppkitFont *)handle.ptr; + if(!font || !text) + return 0.0f; + + return nk_appkit_drawing_get_text_width(nk_appkit.win, text, len); +} + +static void nk_appkit_char_callback(nk_appkit_window *window, unsigned int codepoint) +{ + if(nk_appkit.text_len < NK_APPKIT_TEXT_MAX) + nk_appkit.text[nk_appkit.text_len++] = codepoint; +} + +static void nk_appkit_scroll_callback(nk_appkit_window *window, double xoff, double yoff) +{ + nk_appkit.scroll.x += (float)xoff; + nk_appkit.scroll.y += (float)yoff; +} + +static void nk_appkit_mouse_button_callback(nk_appkit_window *window, int button, int action, int mods) +{ + double x, y; + if(button != nk_appkit_mouse_button_left) + return; + nk_appkit_window_get_mouse_position(window, &x, &y); + if(action == nk_appkit_press) + { + double now = (double)mach_absolute_time() / nk_appkit.timerFrequency; + double dt = now - nk_appkit.last_button_click; + if(dt > NK_APPKIT_DOUBLE_CLICK_LO && dt < NK_APPKIT_DOUBLE_CLICK_HI) + { + nk_appkit.is_double_click_down = nk_true; + nk_appkit.double_click_pos = nk_vec2((float)x, (float)y); + } + nk_appkit.last_button_click = now; + } + else + nk_appkit.is_double_click_down = nk_false; +} + +NK_API struct nk_context *nk_appkit_create(nk_appkit_window *win) +{ + nk_appkit.win = win; + nk_appkit_window_set_character_callback(win, nk_appkit_char_callback); + nk_appkit_window_set_scroll_callback(win, nk_appkit_scroll_callback); + nk_appkit_window_set_mouse_button_callback(win, nk_appkit_mouse_button_callback); + + mach_timebase_info_data_t info; + mach_timebase_info(&info); + nk_appkit.timerFrequency = (info.denom * 1e9) / info.numer; + + nk_appkit.is_double_click_down = nk_false; + nk_appkit.double_click_pos = nk_vec2(0, 0); + nk_appkit.last_button_click = 0; + + return &nk_appkit.ctx; +} + +NK_API void nk_appkit_init(AppkitFont *font) +{ + struct nk_user_font *nkFont = &font->nk; + nkFont->userdata = nk_handle_ptr(font); + nkFont->height = font->height; + nkFont->width = nk_appkit_font_get_text_width; + + nk_init_default(&nk_appkit.ctx, nkFont); + nk_appkit.ctx.clip.userdata = nk_handle_ptr(0); +} + +NK_API void nk_appkit_shutdown(void) +{ + nk_free(&nk_appkit.ctx); + memset(&nk_appkit, 0, sizeof(nk_appkit)); +} + +NK_API AppkitFont *nk_appkit_create_font(const char *name, int size) +{ + AppkitFont *font = (AppkitFont *)calloc(1, sizeof(AppkitFont)); + if(!font) + return NULL; + + font->height = nk_appkit_drawing_set_font(nk_appkit.win, name, size); + return font; +} + +NK_API void nk_appkit_delete_font(AppkitFont *font) +{ + if(!font) + return; + free(font); +} + +NK_API void nk_appkit_render(struct nk_color clear) +{ + const struct nk_command *cmd; + struct nk_context *ctx = &nk_appkit.ctx; + + nk_appkit_drawing_begin(nk_appkit.win, clear.r, clear.g, clear.b, clear.a); + nk_foreach(cmd, &nk_appkit.ctx) + { + switch(cmd->type) + { + case NK_COMMAND_NOP: break; + case NK_COMMAND_SCISSOR: + { + const struct nk_command_scissor *s = (const struct nk_command_scissor *)cmd; + nk_appkit_drawing_scissor(nk_appkit.win, s->x, s->y, s->w, s->h); + break; + } + case NK_COMMAND_LINE: assert(false); break; + case NK_COMMAND_CURVE: assert(false); break; + case NK_COMMAND_RECT: + { + const struct nk_command_rect *r = (const struct nk_command_rect *)cmd; + nk_appkit_drawing_rect(nk_appkit.win, r->x, r->y, r->w, r->h, r->color.r, r->color.g, + r->color.b, r->color.a, r->rounding, r->line_thickness); + break; + } + case NK_COMMAND_RECT_FILLED: + { + const struct nk_command_rect_filled *r = (const struct nk_command_rect_filled *)cmd; + nk_appkit_drawing_filled_rect(nk_appkit.win, r->x, r->y, r->w, r->h, r->color.r, r->color.g, + r->color.b, r->color.a, r->rounding); + break; + } + case NK_COMMAND_RECT_MULTI_COLOR: assert(false); break; + case NK_COMMAND_CIRCLE: assert(false); break; + case NK_COMMAND_CIRCLE_FILLED: assert(false); break; + case NK_COMMAND_ARC: assert(false); break; + case NK_COMMAND_ARC_FILLED: assert(false); break; + case NK_COMMAND_TRIANGLE: assert(false); break; + case NK_COMMAND_TRIANGLE_FILLED: assert(false); break; + case NK_COMMAND_POLYGON: assert(false); break; + case NK_COMMAND_POLYGON_FILLED: assert(false); break; + case NK_COMMAND_POLYLINE: assert(false); break; + case NK_COMMAND_TEXT: + { + const struct nk_command_text *t = (const struct nk_command_text *)cmd; + nk_appkit_drawing_text(nk_appkit.win, t->x, t->y, t->w, t->h, (const char *)t->string, + t->length, (AppkitFont *)t->font->userdata.ptr, t->background.r, + t->background.g, t->background.b, t->background.a, t->foreground.r, + t->foreground.g, t->foreground.b, t->foreground.a); + break; + } + case NK_COMMAND_IMAGE: assert(false); break; + case NK_COMMAND_CUSTOM: assert(false); break; + } + } + nk_appkit_drawing_end(nk_appkit.win); + nk_clear(ctx); +} + +NK_API void nk_appkit_new_frame(void) +{ + int i; + double x, y; + struct nk_context *ctx = &nk_appkit.ctx; + nk_appkit_window *win = nk_appkit.win; + + nk_input_begin(ctx); + for(i = 0; i < nk_appkit.text_len; ++i) + nk_input_unicode(ctx, nk_appkit.text[i]); + + nk_input_key(ctx, NK_KEY_DEL, + nk_appkit_window_get_key_state(win, nk_appkit_key_delete) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_ENTER, + nk_appkit_window_get_key_state(win, nk_appkit_key_enter) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_TAB, + nk_appkit_window_get_key_state(win, nk_appkit_key_tab) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_BACKSPACE, + nk_appkit_window_get_key_state(win, nk_appkit_key_backspace) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_UP, + nk_appkit_window_get_key_state(win, nk_appkit_key_up) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_DOWN, + nk_appkit_window_get_key_state(win, nk_appkit_key_down) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_TEXT_START, + nk_appkit_window_get_key_state(win, nk_appkit_key_home) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_TEXT_END, + nk_appkit_window_get_key_state(win, nk_appkit_key_end) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_SCROLL_START, + nk_appkit_window_get_key_state(win, nk_appkit_key_home) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_SCROLL_END, + nk_appkit_window_get_key_state(win, nk_appkit_key_end) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_SCROLL_DOWN, + nk_appkit_window_get_key_state(win, nk_appkit_key_page_down) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_SCROLL_UP, + nk_appkit_window_get_key_state(win, nk_appkit_key_page_up) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_SHIFT, + nk_appkit_window_get_key_state(win, nk_appkit_key_left_shift) == nk_appkit_press || + nk_appkit_window_get_key_state(win, nk_appkit_key_right_shift) == nk_appkit_press); + + if(nk_appkit_window_get_key_state(win, nk_appkit_key_left_control) == nk_appkit_press || + nk_appkit_window_get_key_state(win, nk_appkit_key_right_control) == nk_appkit_press) + { + nk_input_key(ctx, NK_KEY_COPY, + nk_appkit_window_get_key_state(win, nk_appkit_key_C) == nk_appkit_press); + + nk_input_key(ctx, NK_KEY_PASTE, + nk_appkit_window_get_key_state(win, nk_appkit_key_V) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_CUT, + nk_appkit_window_get_key_state(win, nk_appkit_key_X) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_TEXT_UNDO, + nk_appkit_window_get_key_state(win, nk_appkit_key_Z) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_TEXT_REDO, + nk_appkit_window_get_key_state(win, nk_appkit_key_R) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_TEXT_WORD_LEFT, + nk_appkit_window_get_key_state(win, nk_appkit_key_left) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_TEXT_WORD_RIGHT, + nk_appkit_window_get_key_state(win, nk_appkit_key_right) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_TEXT_LINE_START, + nk_appkit_window_get_key_state(win, nk_appkit_key_B) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_TEXT_LINE_END, + nk_appkit_window_get_key_state(win, nk_appkit_key_E) == nk_appkit_press); + } + else + { + nk_input_key(ctx, NK_KEY_LEFT, + nk_appkit_window_get_key_state(win, nk_appkit_key_left) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_RIGHT, + nk_appkit_window_get_key_state(win, nk_appkit_key_right) == nk_appkit_press); + nk_input_key(ctx, NK_KEY_COPY, 0); + nk_input_key(ctx, NK_KEY_PASTE, 0); + nk_input_key(ctx, NK_KEY_CUT, 0); + nk_input_key(ctx, NK_KEY_SHIFT, 0); + } + + nk_appkit_window_get_mouse_position(win, &x, &y); + nk_input_motion(ctx, (int)x, (int)y); + nk_input_button( + ctx, NK_BUTTON_LEFT, (int)x, (int)y, + nk_appkit_window_get_mouse_button_state(win, nk_appkit_mouse_button_left) == nk_appkit_press); + nk_input_button(ctx, NK_BUTTON_MIDDLE, (int)x, (int)y, + nk_appkit_window_get_mouse_button_state(win, nk_appkit_mouse_button_middle) == + nk_appkit_press); + nk_input_button(ctx, NK_BUTTON_RIGHT, (int)x, (int)y, + nk_appkit_window_get_mouse_button_state(win, nk_appkit_mouse_button_right) == + nk_appkit_press); + nk_input_button(ctx, NK_BUTTON_DOUBLE, (int)nk_appkit.double_click_pos.x, + (int)nk_appkit.double_click_pos.y, nk_appkit.is_double_click_down); + nk_input_scroll(ctx, nk_appkit.scroll); + nk_input_end(&nk_appkit.ctx); + nk_appkit.text_len = 0; + nk_appkit.scroll = nk_vec2(0, 0); +} + +#endif // #ifdef NK_APPKIT_IMPLEMENTATION + +#ifdef NK_APPKIT_OBJC_IMPLEMENTATION + +#import + +typedef struct nk_appkit_window +{ + NSWindow *nsWindow; + id delegate; + NSView *nsView; + NSImage *nsImage; + bool shouldClose; + char mouseButtons[3]; + char keys[nk_appkit_key_last + 1]; + + nk_appkit_character_cb characterCallback; + nk_appkit_key_cb keyCallback; + nk_appkit_mouse_button_cb mouseButtonCallback; + nk_appkit_scroll_cb scrollCallback; +} nk_appkit_window; + +typedef struct nk_appkit_state +{ + short int keycodes[256]; + nk_appkit_window *window; + NSFont *nsFont; + float fontHeight; + CGEventSourceRef eventSource; + id nsAppDelegate; + id keyUpMonitor; +} nk_appkit_state; + +static nk_appkit_state s_state = {0}; + +static void CreateKeyTables(void) +{ + memset(s_state.keycodes, -1, sizeof(s_state.keycodes)); + + s_state.keycodes[0x1D] = nk_appkit_key_0; + s_state.keycodes[0x12] = nk_appkit_key_1; + s_state.keycodes[0x13] = nk_appkit_key_2; + s_state.keycodes[0x14] = nk_appkit_key_3; + s_state.keycodes[0x15] = nk_appkit_key_4; + s_state.keycodes[0x17] = nk_appkit_key_5; + s_state.keycodes[0x16] = nk_appkit_key_6; + s_state.keycodes[0x1A] = nk_appkit_key_7; + s_state.keycodes[0x1C] = nk_appkit_key_8; + s_state.keycodes[0x19] = nk_appkit_key_9; + + s_state.keycodes[0x00] = nk_appkit_key_A; + s_state.keycodes[0x0B] = nk_appkit_key_B; + s_state.keycodes[0x08] = nk_appkit_key_C; + s_state.keycodes[0x02] = nk_appkit_key_D; + s_state.keycodes[0x0E] = nk_appkit_key_E; + s_state.keycodes[0x03] = nk_appkit_key_F; + s_state.keycodes[0x05] = nk_appkit_key_G; + s_state.keycodes[0x04] = nk_appkit_key_H; + s_state.keycodes[0x22] = nk_appkit_key_I; + s_state.keycodes[0x26] = nk_appkit_key_J; + s_state.keycodes[0x28] = nk_appkit_key_K; + s_state.keycodes[0x25] = nk_appkit_key_L; + s_state.keycodes[0x2E] = nk_appkit_key_M; + s_state.keycodes[0x2D] = nk_appkit_key_N; + s_state.keycodes[0x1F] = nk_appkit_key_O; + s_state.keycodes[0x23] = nk_appkit_key_P; + s_state.keycodes[0x0C] = nk_appkit_key_Q; + s_state.keycodes[0x0F] = nk_appkit_key_R; + s_state.keycodes[0x01] = nk_appkit_key_S; + s_state.keycodes[0x11] = nk_appkit_key_T; + s_state.keycodes[0x20] = nk_appkit_key_U; + s_state.keycodes[0x09] = nk_appkit_key_V; + s_state.keycodes[0x0D] = nk_appkit_key_W; + s_state.keycodes[0x07] = nk_appkit_key_X; + s_state.keycodes[0x10] = nk_appkit_key_Y; + s_state.keycodes[0x06] = nk_appkit_key_Z; + + s_state.keycodes[0x33] = nk_appkit_key_backspace; + s_state.keycodes[0x75] = nk_appkit_key_delete; + s_state.keycodes[0x7D] = nk_appkit_key_down; + s_state.keycodes[0x77] = nk_appkit_key_end; + s_state.keycodes[0x24] = nk_appkit_key_enter; + s_state.keycodes[0x73] = nk_appkit_key_home; + s_state.keycodes[0x7B] = nk_appkit_key_left; + s_state.keycodes[0x79] = nk_appkit_key_page_down; + s_state.keycodes[0x74] = nk_appkit_key_page_up; + s_state.keycodes[0x7C] = nk_appkit_key_right; + s_state.keycodes[0x30] = nk_appkit_key_tab; + s_state.keycodes[0x7E] = nk_appkit_key_up; + + s_state.keycodes[0x33] = nk_appkit_key_backspace; + s_state.keycodes[0x75] = nk_appkit_key_delete; + s_state.keycodes[0x7D] = nk_appkit_key_down; + s_state.keycodes[0x77] = nk_appkit_key_end; + s_state.keycodes[0x24] = nk_appkit_key_enter; + s_state.keycodes[0x73] = nk_appkit_key_home; + s_state.keycodes[0x7B] = nk_appkit_key_left; + s_state.keycodes[0x3B] = nk_appkit_key_left_control; + s_state.keycodes[0x38] = nk_appkit_key_left_shift; + s_state.keycodes[0x79] = nk_appkit_key_page_down; + s_state.keycodes[0x74] = nk_appkit_key_page_up; + s_state.keycodes[0x7C] = nk_appkit_key_right; + s_state.keycodes[0x3E] = nk_appkit_key_right_control; + s_state.keycodes[0x3C] = nk_appkit_key_right_shift; + s_state.keycodes[0x30] = nk_appkit_key_tab; + s_state.keycodes[0x7E] = nk_appkit_key_up; +} + +static int TranslateKey(unsigned int key) +{ + if(key >= sizeof(s_state.keycodes) / sizeof(s_state.keycodes[0])) + return nk_appkit_key_unknown; + + return s_state.keycodes[key]; +} + +static void InputMouseClick(nk_appkit_window *window, int button, int action) +{ + if(button < 0 || button > 2) + return; + + window->mouseButtons[button] = (char)action; + if(window->mouseButtonCallback) + window->mouseButtonCallback(window, button, action, 0); +} + +static void InputKey(nk_appkit_window *window, int key, int action) +{ + if(key >= 0 && key <= nk_appkit_key_last) + { + if(action == nk_appkit_release && window->keys[key] == nk_appkit_release) + return; + + window->keys[key] = (char)action; + } +} + +@interface nk_appkit_windowDelegate : NSObject +{ + nk_appkit_window *window; +} + +- (instancetype)init_with_window:(nk_appkit_window *)initWindow; + +@end + +@implementation nk_appkit_windowDelegate + +- (instancetype)init_with_window:(nk_appkit_window *)initWindow +{ + self = [super init]; + window = initWindow; + return self; +} + +- (BOOL)windowShouldClose:(id)sender +{ + window->shouldClose = true; + return NO; +} +@end + +@interface nk_appkit_windowView : NSView +{ + nk_appkit_window *window; +} + +- (instancetype)init_with_window:(nk_appkit_window *)initWindow; + +@end + +@implementation nk_appkit_windowView + +- (instancetype)init_with_window:(nk_appkit_window *)initWindow +{ + self = [super init]; + window = initWindow; + return self; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + [window->nsImage drawInRect:dirtyRect]; +} + +- (void)mouseDown:(NSEvent *)event +{ + InputMouseClick(window, nk_appkit_mouse_button_left, nk_appkit_press); +} + +- (void)mouseUp:(NSEvent *)event +{ + InputMouseClick(window, nk_appkit_mouse_button_left, nk_appkit_release); +} + +- (void)rightMouseDown:(NSEvent *)event +{ + InputMouseClick(window, nk_appkit_mouse_button_right, nk_appkit_press); +} + +- (void)rightMouseUp:(NSEvent *)event +{ + InputMouseClick(window, nk_appkit_mouse_button_right, nk_appkit_release); +} + +- (void)otherMouseDown:(NSEvent *)event +{ + InputMouseClick(window, (int)[event buttonNumber], nk_appkit_press); +} + +- (void)otherMouseUp:(NSEvent *)event +{ + InputMouseClick(window, (int)[event buttonNumber], nk_appkit_release); +} + +- (void)keyDown:(NSEvent *)event +{ + const int key = TranslateKey([event keyCode]); + InputKey(window, key, nk_appkit_press); + [self interpretKeyEvents:@[ event ]]; + if(window->keyCallback) + window->keyCallback(window, key, nk_appkit_press); +} + +- (void)flagsChanged:(NSEvent *)event +{ + int action; + const unsigned int modifierFlags = + [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; + const int key = TranslateKey([event keyCode]); + NSUInteger keyFlag = 0; + switch(key) + { + case nk_appkit_key_left_shift: + case nk_appkit_key_right_shift: keyFlag = NSEventModifierFlagShift; break; + case nk_appkit_key_left_control: + case nk_appkit_key_right_control: keyFlag = NSEventModifierFlagControl; break; + } + + if(keyFlag & modifierFlags) + { + if(window->keys[key] == nk_appkit_press) + action = nk_appkit_release; + else + action = nk_appkit_press; + } + else + action = nk_appkit_release; + + InputKey(window, key, action); +} + +- (void)keyUp:(NSEvent *)event +{ + const int key = TranslateKey([event keyCode]); + InputKey(window, key, nk_appkit_release); + if(window->keyCallback) + window->keyCallback(window, key, nk_appkit_release); +} + +- (void)scrollWheel:(NSEvent *)event +{ + double deltaX = [event scrollingDeltaX]; + double deltaY = [event scrollingDeltaY]; + + if([event hasPreciseScrollingDeltas]) + { + deltaX *= 0.1; + deltaY *= 0.1; + } + + if(fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0) + { + if(window->scrollCallback) + window->scrollCallback(window, deltaX, deltaY); + } +} + +static const NSRange s_NSRange_Empty = {NSNotFound, 0}; + +- (BOOL)hasMarkedText +{ + return NO; +} + +- (NSRange)markedRange +{ + return s_NSRange_Empty; +} + +- (NSRange)selectedRange +{ + return s_NSRange_Empty; +} + +- (void)setMarkedText:(id)string + selectedRange:(NSRange)selectedRange + replacementRange:(NSRange)replacementRange +{ +} + +- (void)unmarkText +{ +} + +- (NSArray *)validAttributesForMarkedText +{ + return [NSArray array]; +} + +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range + actualRange:(NSRangePointer)actualRange +{ + return nil; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)point +{ + return 0; +} + +- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange +{ + const NSRect frame = [window->nsView frame]; + return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0); +} + +- (void)insertText:(id)string replacementRange:(NSRange)replacementRange +{ + if(!window->characterCallback) + return; + + NSString *characters; + NSEvent *event = [NSApp currentEvent]; + + if([string isKindOfClass:[NSAttributedString class]]) + characters = [string string]; + else + characters = (NSString *)string; + + NSRange range = NSMakeRange(0, [characters length]); + while(range.length) + { + uint32_t codepoint = 0; + + if([characters getBytes:&codepoint + maxLength:sizeof(codepoint) + usedLength:nil + encoding:NSUTF32StringEncoding + options:0 + range:range + remainingRange:&range]) + { + if(codepoint >= 0xf700 && codepoint <= 0xf7ff) + continue; + + if(window->characterCallback) + window->characterCallback(window, codepoint); + } + } +} + +@end + +void nk_appkit_window_get_mouse_position(nk_appkit_window *window, double *xpos, double *ypos) +{ + @autoreleasepool + { + const NSRect contentRect = [window->nsView frame]; + const NSPoint pos = [window->nsWindow mouseLocationOutsideOfEventStream]; + + if(xpos) + *xpos = pos.x; + if(ypos) + *ypos = contentRect.size.height - pos.y; + } +} + +@interface nk_appkit_ApplicationDelegate : NSObject +@end + +@implementation nk_appkit_ApplicationDelegate + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender +{ + if(s_state.window) + s_state.window->shouldClose = true; + return NSTerminateCancel; +} + +- (void)applicationDidFinishLaunching:(NSNotification *)notification +{ + @autoreleasepool + { + NSEvent *event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined + location:NSMakePoint(0, 0) + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + subtype:0 + data1:0 + data2:0]; + [NSApp postEvent:event atStart:YES]; + [NSApp stop:nil]; + } +} + +@end // nk_appkit_ApplicationDelegate + +bool nk_appkit_window_is_closed(nk_appkit_window *window) +{ + return window->shouldClose; +} + +void nk_appkit_window_set_character_callback(nk_appkit_window *window, nk_appkit_character_cb callback) +{ + window->characterCallback = callback; +} + +void nk_appkit_window_set_key_callback(nk_appkit_window *window, nk_appkit_key_cb callback) +{ + window->keyCallback = callback; +} + +void nk_appkit_window_set_scroll_callback(nk_appkit_window *window, nk_appkit_scroll_cb callback) +{ + window->scrollCallback = callback; +} + +void nk_appkit_window_set_mouse_button_callback(nk_appkit_window *window, + nk_appkit_mouse_button_cb callback) +{ + window->mouseButtonCallback = callback; +} + +int nk_appkit_window_get_key_state(nk_appkit_window *window, int key) +{ + assert(key >= nk_appkit_key_first && key <= nk_appkit_key_last); + return (int)window->keys[key]; +} + +int nk_appkit_window_get_mouse_button_state(nk_appkit_window *window, int button) +{ + assert(button >= 0 && button <= 2); + return (int)window->mouseButtons[button]; +} + +void nk_appkit_window_delete(nk_appkit_window *window) +{ + assert(window == s_state.window); + + window->characterCallback = nil; + window->keyCallback = nil; + window->mouseButtonCallback = nil; + window->scrollCallback = nil; + + @autoreleasepool + { + [window->nsWindow orderOut:nil]; + [window->nsWindow setDelegate:nil]; + + [window->delegate release]; + window->delegate = nil; + + [window->nsImage release]; + window->nsImage = nil; + + [window->nsView release]; + window->nsView = nil; + + [window->nsWindow close]; + window->nsWindow = nil; + } + + free(window); + s_state.window = NULL; +} + +void nk_appkit_core_shutdown(void) +{ + if(s_state.window) + nk_appkit_window_delete(s_state.window); + + @autoreleasepool + { + if(s_state.eventSource) + { + CFRelease(s_state.eventSource); + s_state.eventSource = nil; + } + + if(s_state.nsAppDelegate) + { + [NSApp setDelegate:nil]; + [s_state.nsAppDelegate release]; + s_state.nsAppDelegate = nil; + } + + if(s_state.keyUpMonitor) + [NSEvent removeMonitor:s_state.keyUpMonitor]; + + if(s_state.nsFont) + { + [s_state.nsFont release]; + s_state.nsFont = nil; + } + } + + memset(&s_state, 0, sizeof(s_state)); +} + +bool nk_appkit_core_initialize(void) +{ + memset(&s_state, 0, sizeof(s_state)); + + @autoreleasepool + { + [NSApplication sharedApplication]; + + s_state.nsAppDelegate = [[nk_appkit_ApplicationDelegate alloc] init]; + [NSApp setDelegate:s_state.nsAppDelegate]; + + NSEvent * (^block)(NSEvent *) = ^NSEvent *(NSEvent *event) + { + if([event modifierFlags] & NSEventModifierFlagCommand) + [[NSApp keyWindow] sendEvent:event]; + return event; + }; + + s_state.keyUpMonitor = + [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp handler:block]; + + CreateKeyTables(); + + s_state.eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + if(!s_state.eventSource) + { + nk_appkit_core_shutdown(); + return false; + } + + CGEventSourceSetLocalEventsSuppressionInterval(s_state.eventSource, 0.0); + + if(![[NSRunningApplication currentApplication] isFinishedLaunching]) + [NSApp run]; + + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + } + + return true; +} + +nk_appkit_window *nk_appkit_window_create(int width, int height, const char *title) +{ + nk_appkit_window *window = (nk_appkit_window *)calloc(1, sizeof(nk_appkit_window)); + s_state.window = window; + + window->delegate = [[nk_appkit_windowDelegate alloc] init_with_window:window]; + + window->nsWindow = [[NSWindow alloc] + initWithContentRect:NSMakeRect(0, 0, width, height) + styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable + backing:NSBackingStoreBuffered + defer:NO]; + + [(NSWindow *)window->nsWindow center]; + + window->nsView = [[nk_appkit_windowView alloc] init_with_window:window]; + + [window->nsWindow setContentView:window->nsView]; + [window->nsWindow makeFirstResponder:window->nsView]; + [window->nsWindow setTitle:@(title)]; + [window->nsWindow setDelegate:window->delegate]; + [window->nsWindow setAcceptsMouseMovedEvents:YES]; + [window->nsWindow setRestorable:NO]; + + [window->nsWindow orderFront:nil]; + [NSApp activateIgnoringOtherApps:YES]; + [window->nsWindow makeKeyAndOrderFront:nil]; + + window->nsImage = [[NSImage alloc] initWithSize:NSMakeSize(width, height)]; + + return (nk_appkit_window *)window; +} + +void nk_appkit_drawing_begin(nk_appkit_window *window, u_int8_t r, u_int8_t g, u_int8_t b, u_int8_t a) +{ + [NSGraphicsContext saveGraphicsState]; + [window->nsImage lockFocus]; + [[NSColor colorWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:a / 255.0f] setFill]; + NSRectFill(NSMakeRect(0, 0, window->nsImage.size.width, window->nsImage.size.height)); +} + +void nk_appkit_drawing_end(nk_appkit_window *window) +{ + [window->nsImage unlockFocus]; + [window->nsView setNeedsDisplay:TRUE]; + [NSGraphicsContext restoreGraphicsState]; +} + +float nk_appkit_drawing_set_font(nk_appkit_window *window, const char *name, float size) +{ + @autoreleasepool + { + s_state.nsFont = [NSFont fontWithName:[NSString stringWithUTF8String:name] size:size]; + [s_state.nsFont retain]; + float height = [[[NSLayoutManager alloc] init] defaultLineHeightForFont:s_state.nsFont]; + s_state.fontHeight = height + 1; + return s_state.fontHeight; + } +} + +float nk_appkit_drawing_get_text_width(nk_appkit_window *window, const char *text, int len) +{ + @autoreleasepool + { + NSString *nsString = + [[NSString alloc] initWithBytes:(void *)text length:len encoding:NSASCIIStringEncoding]; + NSMutableDictionary *atts = [[NSMutableDictionary alloc] init]; + [atts setObject:s_state.nsFont forKey:NSFontAttributeName]; + NSSize bounds = [nsString sizeWithAttributes:atts]; + float width = floor(bounds.width); + return width; + } +} + +void nk_appkit_drawing_filled_rect(nk_appkit_window *window, short x, short y, u_int16_t w, + u_int16_t h, u_int8_t r, u_int8_t g, u_int8_t b, u_int8_t a, + int rounding) +{ + @autoreleasepool + { + [[NSColor colorWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:a / 255.0f] setFill]; + float H = window->nsImage.size.height; + float y0 = H - y - h; + NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(x, y0, w, h) + xRadius:rounding + yRadius:rounding]; + [path fill]; + } +} + +void nk_appkit_drawing_rect(nk_appkit_window *window, short x, short y, u_int16_t w, u_int16_t h, + u_int8_t r, u_int8_t g, u_int8_t b, u_int8_t a, int rounding, + int lineThickness) +{ + @autoreleasepool + { + [[NSColor colorWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:a / 255.0f] setStroke]; + float H = window->nsImage.size.height; + float y0 = H - y - h; + NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSMakeRect(x, y0, w, h) + xRadius:rounding + yRadius:rounding]; + path.lineWidth = lineThickness; + [path stroke]; + } +} + +void nk_appkit_drawing_scissor(nk_appkit_window *window, short x, short y, u_int16_t w, u_int16_t h) +{ + @autoreleasepool + { + [NSGraphicsContext restoreGraphicsState]; + [NSGraphicsContext saveGraphicsState]; + float H = window->nsImage.size.height; + float y0 = H - y - h; + NSRectClip(NSMakeRect(x, y0, w, h)); + } +} + +void nk_appkit_drawing_text(nk_appkit_window *window, short x, short y, u_int16_t w, u_int16_t h, + const char *text, int len, void *font, u_int8_t bgR, u_int8_t bgG, + u_int8_t bgB, u_int8_t bgA, u_int8_t fgR, u_int8_t fgG, u_int8_t fgB, + u_int8_t fgA) +{ + @autoreleasepool + { + float H = window->nsImage.size.height; + float y0 = H - y - s_state.fontHeight; + NSString *nsString = + [[NSString alloc] initWithBytes:(void *)text length:len encoding:NSASCIIStringEncoding]; + NSMutableDictionary *atts = [[NSMutableDictionary alloc] init]; + [atts setObject:s_state.nsFont forKey:NSFontAttributeName]; + [atts setObject:[NSColor colorWithRed:bgR / 255.0f + green:bgG / 255.0f + blue:bgB / 255.0f + alpha:bgA / 255.0f] + forKey:NSBackgroundColorAttributeName]; + [atts setObject:[NSColor colorWithRed:fgR / 255.0f + green:fgG / 255.0f + blue:fgB / 255.0f + alpha:fgA / 255.0f] + forKey:NSForegroundColorAttributeName]; + [nsString drawAtPoint:NSMakePoint(x, y0) withAttributes:atts]; + } +} +#endif // #ifdef NK_APPKIT_OBJC_IMPLEMENTATION diff --git a/util/test/demos/apple/nuklear_appkit.mm b/util/test/demos/apple/nuklear_appkit.mm new file mode 100644 index 000000000..a3b1aa4cd --- /dev/null +++ b/util/test/demos/apple/nuklear_appkit.mm @@ -0,0 +1,27 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2022 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#define NK_APPKIT_OBJC_IMPLEMENTATION +#define NK_API +#include "nuklear_appkit.h" diff --git a/util/test/demos/main.cpp b/util/test/demos/main.cpp index f3d457f13..1f85353c0 100644 --- a/util/test/demos/main.cpp +++ b/util/test/demos/main.cpp @@ -216,26 +216,64 @@ void NuklearShutdown() #elif defined(__APPLE__) +#include "apple/official/metal-cpp.h" +extern "C" void *const NSDefaultRunLoopMode; + +#define NK_APPKIT_IMPLEMENTATION +#include "apple/nuklear_appkit.h" + +static nk_appkit_window *window; +static AppkitFont *font; +static NS::Application *pSharedApplication = NULL; + nk_context *NuklearInit(int width, int height, const char *title) { - TEST_ERROR("NuklearInit: Not Implemented"); - return NULL; + if(!nk_appkit_core_initialize()) + exit(EXIT_FAILURE); + + pSharedApplication = NS::Application::sharedApplication(); + + window = nk_appkit_window_create(width, height, title); + if(!window) + exit(EXIT_FAILURE); + + nk_context *ctx = nk_appkit_create(window); + font = nk_appkit_create_font("Menlo Regular", 9); + nk_appkit_init(font); + return ctx; } bool NuklearTick(nk_context *ctx) { - TEST_ERROR("NuklearTick: Not Implemented"); - return false; + NS::AutoreleasePool *pAutoreleasePool = NS::AutoreleasePool::alloc()->init(); + while(true) + { + NS::Event *event = pSharedApplication->nextEventMatchingMask( + (int)NS::EventMaskAny, NS::Date::distantPast(), (NS::String *)NSDefaultRunLoopMode, true); + if(event == NULL) + break; + pSharedApplication->sendEvent(event); + } + pAutoreleasePool->release(); + nk_appkit_new_frame(); + + if(nk_appkit_window_is_closed(window)) + return false; + + return true; } void NuklearRender() { - TEST_ERROR("NuklearRender: Not Implemented"); + nk_appkit_render(nk_rgb(30, 30, 30)); } void NuklearShutdown() { - TEST_ERROR("NuklearShutdown: Not Implemented"); + nk_appkit_delete_font(font); + nk_appkit_shutdown(); + nk_appkit_window_delete(window); + nk_appkit_core_shutdown(); } #else