From 2a7ab5b1a98969973d10e0f288cc9814e35793cf Mon Sep 17 00:00:00 2001 From: Daijiro Fukuda Date: Tue, 6 Dec 2022 10:36:19 +0900 Subject: [PATCH] tests: Add tests for IME features Co-authored-by: Takuro Ashie --- tests/CMakeLists.txt | 14 +- tests/events.c | 55 +++ tests/input_text.c | 781 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 848 insertions(+), 2 deletions(-) create mode 100644 tests/input_text.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f81cfeb9..f28a53b6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -32,6 +32,7 @@ add_executable(cursor cursor.c ${GLAD_GL}) add_executable(empty WIN32 MACOSX_BUNDLE empty.c ${TINYCTHREAD} ${GLAD_GL}) add_executable(gamma WIN32 MACOSX_BUNDLE gamma.c ${GLAD_GL}) add_executable(icon WIN32 MACOSX_BUNDLE icon.c ${GLAD_GL}) +add_executable(input_text WIN32 MACOSX_BUNDLE input_text.c ${GETOPT} ${GLAD_GL}) add_executable(inputlag WIN32 MACOSX_BUNDLE inputlag.c ${GETOPT} ${GLAD_GL}) add_executable(joysticks WIN32 MACOSX_BUNDLE joysticks.c ${GLAD_GL}) add_executable(tearing WIN32 MACOSX_BUNDLE tearing.c ${GLAD_GL}) @@ -48,8 +49,16 @@ if (RT_LIBRARY) target_link_libraries(threads "${RT_LIBRARY}") endif() -set(GUI_ONLY_BINARIES empty gamma icon inputlag joysticks tearing threads - timeout title triangle-vulkan window) +if (GLFW_BUILD_X11 OR GLFW_BUILD_WAYLAND) + find_package(Fontconfig) + if (FONTCONFIG_FOUND) + target_compile_definitions(input_text PRIVATE FONTCONFIG_ENABLED) + target_link_libraries(input_text fontconfig) + endif() +endif() + +set(GUI_ONLY_BINARIES empty gamma icon input_text inputlag joysticks tearing + threads timeout title triangle-vulkan window) set(CONSOLE_BINARIES allocator clipboard events msaa glfwinfo iconify monitors reopen cursor) @@ -70,6 +79,7 @@ endif() if (APPLE) set_target_properties(empty PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Empty Event") set_target_properties(gamma PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Gamma") + set_target_properties(input_text PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Input Text") set_target_properties(inputlag PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Input Lag") set_target_properties(joysticks PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Joysticks") set_target_properties(tearing PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Tearing") diff --git a/tests/events.c b/tests/events.c index ab3b99a7..0ea3a957 100644 --- a/tests/events.c +++ b/tests/events.c @@ -464,6 +464,59 @@ static void char_callback(GLFWwindow* window, unsigned int codepoint) counter++, slot->number, glfwGetTime(), codepoint, string); } +static void preedit_callback(GLFWwindow* window, int preeditCount, + unsigned int* preeditString, int blockCount, + int* blockSizes, int focusedBlock, int caret) +{ + Slot* slot = glfwGetWindowUserPointer(window); + int i, blockIndex = -1, remainingBlockSize = 0; + int width, height; + char encoded[5] = ""; + size_t encodedCount = 0; + printf("%08x to %i at %0.3f: Preedit text ", + counter++, slot->number, glfwGetTime()); + if (preeditCount == 0 || blockCount == 0) + { + printf("(empty)\n"); + } + else + { + for (i = 0; i < preeditCount; i++) + { + if (remainingBlockSize == 0) + { + if (blockIndex == focusedBlock) + printf("]"); + blockIndex++; + remainingBlockSize = blockSizes[blockIndex]; + printf("\n block %d: ", blockIndex); + if (blockIndex == focusedBlock) + printf("["); + } + if (i == caret) + printf("|"); + encodedCount = encode_utf8(encoded, preeditString[i]); + encoded[encodedCount] = '\0'; + printf("%s", encoded); + remainingBlockSize--; + } + if (blockIndex == focusedBlock) + printf("]"); + if (caret == preeditCount) + printf("|"); + printf("\n"); + glfwGetWindowSize(window, &width, &height); + glfwSetPreeditCursorRectangle(window, width/2, height/2, 1, 20); + } +} + +static void ime_callback(GLFWwindow* window) +{ + Slot* slot = glfwGetWindowUserPointer(window); + printf("%08x to %i at %0.3f: IME switched\n", + counter++, slot->number, glfwGetTime()); +} + static void drop_callback(GLFWwindow* window, int count, const char* paths[]) { int i; @@ -649,6 +702,8 @@ int main(int argc, char** argv) glfwSetScrollCallback(slots[i].window, scroll_callback); glfwSetKeyCallback(slots[i].window, key_callback); glfwSetCharCallback(slots[i].window, char_callback); + glfwSetPreeditCallback(slots[i].window, preedit_callback); + glfwSetIMEStatusCallback(slots[i].window, ime_callback); glfwSetDropCallback(slots[i].window, drop_callback); glfwMakeContextCurrent(slots[i].window); diff --git a/tests/input_text.c b/tests/input_text.c new file mode 100644 index 00000000..3cce0227 --- /dev/null +++ b/tests/input_text.c @@ -0,0 +1,781 @@ +//======================================================================== +// Input Test +// Copyright (c) Camilla Löwy +// Copyright (c) Daijiro Fukuda +// Copyright (c) Takuro Ashie +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== +// +// For font handiling, I reffered to https://github.com/Immediate-Mode-UI/Nuklear/wiki/Complete-font-guide. +// For nuklear handling, I reffered to tests/window.c. +// +// Currently, it is made for Japanese input only. +// You have to select the correct font to display Japanese texts. +// To handle other languages, you need to add correct ranges to nk_font_config. +// +// On X11 or Wayland, you can choose a font by GUI if "fontconfig" libarary is enabled. +// +// On Win32, "Yu Mincho" is selected by default if it is installed. This font is +// included in the FOD packages, so it will be installed automatically when you +// enable Japanese input on your environment, or you can install it by +// "Manage optional features" in "Apps & features". +// Refer: https://learn.microsoft.com/en-us/typography/fonts/windows_10_font_list#japanese-supplemental-fonts +// +// On macOS, "Arial Unicode MS" is selected by default if it is installed. +// I assume that this font is usually installed, but if it is not installed, +// please install it manually. +// +// You can also specify a TTF filepath and use your own favorite font by setting +// TTF_FONT_FILEPATH below. +// +//======================================================================== + +// Please comment out and set font filepath here to change default font +// #define TTF_FONT_FILEPATH "" + +#define GLAD_GL_IMPLEMENTATION +#include +#define GLFW_INCLUDE_NONE +#include + +#include + +#define NK_IMPLEMENTATION +#define NK_INCLUDE_STANDARD_IO +#define NK_KEYSTATE_BASED_INPUT +#define NK_INCLUDE_FIXED_TYPES +#define NK_INCLUDE_FONT_BAKING +#define NK_INCLUDE_DEFAULT_FONT +#define NK_INCLUDE_DEFAULT_ALLOCATOR +#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT +#define NK_INCLUDE_STANDARD_VARARGS +#define NK_BUTTON_TRIGGER_ON_RELEASE + +// To increase the number of characters that can be entered at one time +#define NK_INPUT_MAX 64 + +#include + +#define NK_GLFW_GL2_IMPLEMENTATION +#include + +#include +#include +#include +#include + +#include "getopt.h" + +#if defined(FONTCONFIG_ENABLED) + #include +#endif + +#define MAX_BUFFER_LEN 1024 + +// https://github.com/Immediate-Mode-UI/Nuklear/wiki/Complete-font-guide +// https://unicode-table.com +// To handle other languages, you need to fix these ranges. +static nk_rune rangesJapan[] = { + 0x0020, 0x007E, // Basic Latin + 0x2000, 0x206F, // General Punctuation + 0x3000, 0x303F, // CJK Symbols and Punctuation + 0x3041, 0x309F, // Hiragana + 0x30A0, 0x30FF, // Katakana + 0x4E00, 0x9FFF, // All Kanji + 0xFF01, 0xFFEF, // Halfwidth and Fullwidth Forms + 0 +}; + +#define MAX_FONTS_LEN 512 +#define MAX_FONT_FAMILY_NAME_LEN 128 +#define MAX_FONT_FILEPATH_LEN 256 + +static struct nk_font* currentFont; +static char** fontFamilyNames; +static char** fontFilePaths; +static int fontNum = 0; +static int currentFontIndex = 0; + +static int currentIMEStatus = GLFW_FALSE; +#define MAX_PREEDIT_LEN 128 +static char preeditBuf[MAX_PREEDIT_LEN] = ""; + +void usage(void) +{ + printf("Usage: input_text [-h] [-s]\n"); + printf("Options:\n"); + printf(" -s Use on-the-spot sytle on X11. This is ignored on other platforms.\n"); + printf(" -h Show this help\n"); +} + +static size_t encode_utf8(char* s, unsigned int ch) +{ + size_t count = 0; + + if (ch < 0x80) + s[count++] = (char) ch; + else if (ch < 0x800) + { + s[count++] = (ch >> 6) | 0xc0; + s[count++] = (ch & 0x3f) | 0x80; + } + else if (ch < 0x10000) + { + s[count++] = (ch >> 12) | 0xe0; + s[count++] = ((ch >> 6) & 0x3f) | 0x80; + s[count++] = (ch & 0x3f) | 0x80; + } + else if (ch < 0x110000) + { + s[count++] = (ch >> 18) | 0xf0; + s[count++] = ((ch >> 12) & 0x3f) | 0x80; + s[count++] = ((ch >> 6) & 0x3f) | 0x80; + s[count++] = (ch & 0x3f) | 0x80; + } + + return count; +} + +static int add_font(const char* familyName, const char* ttfFilePath, int checkExistence) +{ + if (MAX_FONTS_LEN <= fontNum) + return GLFW_FALSE; + + if (MAX_FONT_FAMILY_NAME_LEN <= strlen(familyName) || MAX_FONT_FILEPATH_LEN <= strlen(ttfFilePath)) + return GLFW_FALSE; + + if (checkExistence) + { + FILE* fp = fopen(ttfFilePath, "rb"); + if (!fp) + return GLFW_FALSE; + fclose(fp); + } + + fontFamilyNames[fontNum] = (char*) malloc(1 + strlen(familyName)); + assert(fontFamilyNames[fontNum]); + strcpy(fontFamilyNames[fontNum], familyName); + + fontFilePaths[fontNum] = (char*) malloc(1 + strlen(ttfFilePath)); + assert(fontFilePaths[fontNum]); + strcpy(fontFilePaths[fontNum], ttfFilePath); + + fontNum++; + + return GLFW_TRUE; +} + +static int replace_font(int index, const char* familyName, const char* ttfFilePath, int checkExistence) +{ + if (index == 0 || fontNum <= index) + return GLFW_FALSE; + if (MAX_FONT_FAMILY_NAME_LEN <= strlen(familyName) || MAX_FONT_FILEPATH_LEN <= strlen(ttfFilePath)) + return GLFW_FALSE; + + if (checkExistence) + { + FILE* fp = fopen(ttfFilePath, "rb"); + if (!fp) + return GLFW_FALSE; + fclose(fp); + } + + free(fontFamilyNames[index]); + free(fontFilePaths[index]); + + fontFamilyNames[index] = (char*) malloc(1 + strlen(familyName)); + assert(fontFamilyNames[index]); + strcpy(fontFamilyNames[index], familyName); + + fontFilePaths[index] = (char*) malloc(1 + strlen(ttfFilePath)); + assert(fontFilePaths[index]); + strcpy(fontFilePaths[index], ttfFilePath); + + return GLFW_TRUE; +} + +#if defined(TTF_FONT_FILEPATH) +static int load_custom_font() +{ + if (MAX_FONTS_LEN <= fontNum) + return GLFW_FALSE; + if (!(TTF_FONT_FILEPATH && *TTF_FONT_FILEPATH)) + return GLFW_FALSE; + + return add_font("Custom", TTF_FONT_FILEPATH, GLFW_TRUE); +} +#endif + +#if defined(FONTCONFIG_ENABLED) +static void load_font_list_by_fontconfig() +{ + FcConfig* config = FcInitLoadConfigAndFonts(); + FcFontSet* fontset = FcConfigGetFonts(config, FcSetSystem); + + if (!fontset) + { + printf("load_font_list_by_fontconfig failed.\n"); + FcConfigDestroy(config); + return; + } + + for (int i = 0; i < fontset->nfont; i++) + { + FcValue fvalue, dvalue; + if (FcResultMatch == FcPatternGet(fontset->fonts[i], FC_FAMILY, 0, &fvalue)) + { + if (FcResultMatch == FcPatternGet(fontset->fonts[i], FC_FILE, 0, &dvalue)) + { + const char* familyName = (const char*) fvalue.u.s; + const char* filePath = (const char*) dvalue.u.s; + int existsFamily = GLFW_FALSE; + int existingIndex = 0; + + if (!strstr(filePath, ".ttf")) + { + continue; + } + + for (int j = 1; j < fontNum; ++j) + { + if (strcmp(fontFamilyNames[j], familyName) == 0) + { + existsFamily = GLFW_TRUE; + existingIndex = j; + break; + } + } + + if (existsFamily) + { + // Prefer "regular" to the others. + if (strstr(filePath, "regular") || strstr(filePath, "Regular")) + replace_font(existingIndex, familyName, filePath, GLFW_FALSE); + } + else + add_font(familyName, filePath, GLFW_FALSE); + + if (MAX_FONTS_LEN <= fontNum) + { + printf("MAX_FONTS_LEN reached. Could not load some fonts.\n"); + break; + } + } + } + } + + FcConfigDestroy(config); +} +#endif + +static void load_default_font_for_each_platform() +{ + int hasSucceeded = GLFW_FALSE; + if (MAX_FONTS_LEN <= fontNum) + return; + + if (glfwGetPlatform() == GLFW_PLATFORM_COCOA) + hasSucceeded = add_font("Arial Unicode MS", "/Library/Fonts/Arial Unicode.ttf", GLFW_TRUE); + else if(glfwGetPlatform() == GLFW_PLATFORM_WIN32) + { + // Use "Yu Mincho" since it is the only TTF for Japanese in the FOD packages on Windows10 and Windows11. + // https://learn.microsoft.com/en-us/typography/fonts/windows_10_font_list#japanese-supplemental-fonts + char filepath[MAX_FONT_FILEPATH_LEN]; + char* winDir = getenv("systemroot"); + if (winDir) + snprintf(filepath, MAX_FONT_FILEPATH_LEN, "%s\\Fonts\\Yumin.ttf", winDir); + else + strcpy(filepath, "C:\\Windows\\Fonts\\Yumin.ttf"); + hasSucceeded = add_font("Yu Mincho Regular", filepath, GLFW_TRUE); + } + + if (hasSucceeded) + currentFontIndex = fontNum - 1; +} + +static void init_font_list() +{ + int useCustomFont = GLFW_FALSE; + int customFontIndex = 0; + + fontFamilyNames = (char**) malloc(sizeof(char*) * MAX_FONTS_LEN); + assert(fontFamilyNames); + fontFilePaths = (char**) malloc(sizeof(char*) * MAX_FONTS_LEN); + assert(fontFilePaths); + + fontFamilyNames[0] = "GLFW default"; + fontFilePaths[0] = ""; + fontNum++; + +#if defined(TTF_FONT_FILEPATH) + useCustomFont = load_custom_font(); + if (useCustomFont) + customFontIndex = fontNum - 1; +#endif + + load_default_font_for_each_platform(); + +#if defined(FONTCONFIG_ENABLED) + load_font_list_by_fontconfig(); +#endif + + if (useCustomFont) + currentFontIndex = customFontIndex; +} + +static void deinit_font_list() +{ + for (int i = 1; i < fontNum; ++i) + { + free(fontFamilyNames[i]); + free(fontFilePaths[i]); + } + + free(fontFamilyNames); + free(fontFilePaths); +} + +// https://github.com/Immediate-Mode-UI/Nuklear/wiki/Complete-font-guide +static void update_font(struct nk_context* nk, float height) +{ + struct nk_font_atlas* atlas; + + nk_glfw3_font_stash_begin(&atlas); + + if (currentFontIndex == 0) + { + currentFont = nk_font_atlas_add_default(atlas, height, 0); + } + else + { + struct nk_font* new_font; + struct nk_font_config cfg; + cfg = nk_font_config(0); + cfg.range = rangesJapan; + cfg.oversample_h = 1; + cfg.oversample_v = 1; + cfg.pixel_snap = true; + + new_font = nk_font_atlas_add_from_file(atlas, fontFilePaths[currentFontIndex], height, &cfg); + if (new_font) + { + currentFont = new_font; + printf("Succeeded to load font file: %s\n", fontFilePaths[currentFontIndex]); + } + else + printf("Failed to load font file: %s\n", fontFilePaths[currentFontIndex]); + } + + nk_glfw3_font_stash_end(); + nk_style_set_font(nk, ¤tFont->handle); +} + +static void set_menu_buttons(GLFWwindow* window, struct nk_context* nk, int height) +{ + static int windowedX, windowedY, windowedWidth, windowedHeight; + + nk_layout_row_dynamic(nk, height, 2); + if (nk_button_label(nk, "Toggle Fullscreen")) + { + if (glfwGetWindowMonitor(window)) + { + glfwSetWindowMonitor(window, NULL, + windowedX, windowedY, + windowedWidth, windowedHeight, 0); + } + else + { + GLFWmonitor* monitor = glfwGetPrimaryMonitor(); + const GLFWvidmode* mode = glfwGetVideoMode(monitor); + glfwGetWindowPos(window, &windowedX, &windowedY); + glfwGetWindowSize(window, &windowedWidth, &windowedHeight); + glfwSetWindowMonitor(window, monitor, + 0, 0, mode->width, mode->height, + mode->refreshRate); + } + } + + { + int auto_iconify = glfwGetWindowAttrib(window, GLFW_AUTO_ICONIFY); + if (nk_checkbox_label(nk, "Auto Iconify", &auto_iconify)) + glfwSetWindowAttrib(window, GLFW_AUTO_ICONIFY, auto_iconify); + } +} + +static int set_font_selecter(GLFWwindow* window, struct nk_context* nk, int height, int fontHeight) +{ + int newSelectedIndex; + + nk_layout_row_begin(nk, NK_DYNAMIC, height, 2); + + nk_layout_row_push(nk, 1.f / 3.f); + nk_label(nk, "Font", NK_TEXT_LEFT); + + nk_layout_row_push(nk, 2.f / 3.f); + newSelectedIndex = nk_combo(nk, (const char**) fontFamilyNames, fontNum, currentFontIndex, fontHeight, nk_vec2(300, 400)); + + nk_layout_row_end(nk); + + if (newSelectedIndex == currentFontIndex) + return GLFW_FALSE; + + currentFontIndex = newSelectedIndex; + return GLFW_TRUE; +} + +static void set_ime_buttons(GLFWwindow* window, struct nk_context* nk, int height) +{ + nk_layout_row_dynamic(nk, height, 2); + + if (nk_button_label(nk, "Toggle IME status")) + { + glfwSetInputMode(window, GLFW_IME, !currentIMEStatus); + } + + if (nk_button_label(nk, "Reset preedit text")) + { + glfwResetPreeditText(window); + } +} + +static void set_preedit_cursor_edit(GLFWwindow* window, struct nk_context* nk, int height, int* isAutoUpdating) +{ + static int lastX = -1, lastY = -1, lastW = -1, lastH = -1; + static char xBuf[12] = "", yBuf[12] = "", wBuf[12] = "", hBuf[12] = ""; + + const nk_flags flags = NK_EDIT_FIELD | + NK_EDIT_SIG_ENTER | + NK_EDIT_GOTO_END_ON_ACTIVATE; + nk_flags events; + int x, y, w, h; + + glfwGetPreeditCursorRectangle(window, &x, &y, &w, &h); + + if (x != lastX) + sprintf(xBuf, "%i", x); + if (y != lastY) + sprintf(yBuf, "%i", y); + if (w != lastW) + sprintf(wBuf, "%i", w); + if (h != lastH) + sprintf(hBuf, "%i", h); + + nk_layout_row_begin(nk, NK_DYNAMIC, height, 5); + + nk_layout_row_push(nk, 4.f / 9.f); + nk_label(nk, "Preedit cursor (x,y,w,h)", NK_TEXT_LEFT); + + nk_layout_row_push(nk, 1.f / 9.f); + events = nk_edit_string_zero_terminated(nk, flags, xBuf, + sizeof(xBuf), + nk_filter_decimal); + if (events & NK_EDIT_COMMITED) + { + x = atoi(xBuf); + *isAutoUpdating = GLFW_FALSE; + glfwSetPreeditCursorRectangle(window, x, y, w, h); + } + else if (events & NK_EDIT_DEACTIVATED) + sprintf(xBuf, "%i", x); + + nk_layout_row_push(nk, 1.f / 9.f); + events = nk_edit_string_zero_terminated(nk, flags, yBuf, + sizeof(yBuf), + nk_filter_decimal); + if (events & NK_EDIT_COMMITED) + { + y = atoi(yBuf); + *isAutoUpdating = GLFW_FALSE; + glfwSetPreeditCursorRectangle(window, x, y, w, h); + } + else if (events & NK_EDIT_DEACTIVATED) + sprintf(yBuf, "%i", y); + + nk_layout_row_push(nk, 1.f / 9.f); + events = nk_edit_string_zero_terminated(nk, flags, wBuf, + sizeof(wBuf), + nk_filter_decimal); + if (events & NK_EDIT_COMMITED) + { + w = atoi(wBuf); + *isAutoUpdating = GLFW_FALSE; + glfwSetPreeditCursorRectangle(window, x, y, w, h); + } + else if (events & NK_EDIT_DEACTIVATED) + sprintf(wBuf, "%i", w); + + nk_layout_row_push(nk, 1.f / 9.f); + events = nk_edit_string_zero_terminated(nk, flags, hBuf, + sizeof(hBuf), + nk_filter_decimal); + if (events & NK_EDIT_COMMITED) + { + h = atoi(hBuf); + *isAutoUpdating = GLFW_FALSE; + glfwSetPreeditCursorRectangle(window, x, y, w, h); + } + else if (events & NK_EDIT_DEACTIVATED) + sprintf(hBuf, "%i", h); + + nk_layout_row_push(nk, 1.f / 9.f); + nk_checkbox_label(nk, "Auto", isAutoUpdating); + + nk_layout_row_end(nk); + + lastX = x; + lastY = y; + lastW = w; + lastH = h; +} + +static void set_ime_stauts_labels(GLFWwindow* window, struct nk_context* nk, int height) +{ + nk_layout_row_dynamic(nk, height, 1); + nk_value_bool(nk, "IME status", currentIMEStatus); +} + +static void set_preedit_labels(GLFWwindow* window, struct nk_context* nk, int height) +{ + nk_layout_row_begin(nk, NK_DYNAMIC, height, 5); + + nk_layout_row_push(nk, 1.f / 3.f); + nk_label(nk, "Preedit info:", NK_TEXT_LEFT); + + nk_layout_row_push(nk, 2.f / 3.f); + nk_label(nk, (const char*) preeditBuf, NK_TEXT_LEFT); + + nk_layout_row_end(nk); +} + +// If it is possible to take the text-cursor position calculated in `nk_do_edit` function in `deps/nuklear.h`, +// we can set preedit-cursor position more easily. +// However, there doesn't seem to be a way to do that, so this does a simplified calculation only for the end +// of the text. (Can not trace the cursor movement) +static void update_cursor_pos(GLFWwindow* window, struct nk_context* nk, struct nk_user_font* f, char* boxBuffer, int boxLen) +{ + float lineWidth = 0; + int totalLines = 1; + + const char* text; + int textPos = 0; + + struct nk_str nkString; + nk_str_init_fixed(&nkString, boxBuffer, (nk_size) MAX_BUFFER_LEN); + nkString.buffer.allocated = (nk_size) boxLen; + nkString.len = nk_utf_len(boxBuffer, boxLen); + + text = nk_str_get_const(&nkString); + + while (textPos < boxLen) + { + nk_rune unicode = 0; + int remainedBoxLen = boxLen - textPos; + int nextGlyphSize = nk_utf_decode(text + textPos, &unicode, remainedBoxLen); + if (!nextGlyphSize) + break; + + if (unicode == '\n') + { + textPos++; + totalLines++; + lineWidth = 0; + continue; + } + + textPos += nextGlyphSize; + lineWidth += f->width(f->userdata, f->height, text + textPos, nextGlyphSize); + } + + { + // I don't know how to get these info. + int widgetLayoutX = 10; + int widgetLayoutY = 220; + + int lineHeight = f->height + nk->style.edit.row_padding; + + int cursorPosX = widgetLayoutX + lineWidth; + int cursorPosY = widgetLayoutY + lineHeight * (totalLines - 1); + int cursorHeight = lineHeight; + int cursorWidth; + + // Keep the value of width since it doesn't need to be updated. + glfwGetPreeditCursorRectangle(window, NULL, NULL, &cursorWidth, NULL); + + glfwSetPreeditCursorRectangle(window, cursorPosX, cursorPosY, cursorWidth, cursorHeight); + } +} + +static void ime_callback(GLFWwindow* window) +{ + currentIMEStatus = glfwGetInputMode(window, GLFW_IME); + printf("IME switched: %s\n", currentIMEStatus ? "ON" : "OFF"); +} + +static void preedit_callback(GLFWwindow* window, int preeditCount, + unsigned int* preeditString, int blockCount, + int* blockSizes, int focusedBlock, int caret) +{ + int blockIndex = -1, remainingBlockSize = 0; + if (preeditCount == 0 || blockCount == 0) + { + strcpy(preeditBuf, "(empty)"); + return; + } + + strcpy(preeditBuf, ""); + + for (int i = 0; i < preeditCount; i++) + { + char encoded[5] = ""; + size_t encodedCount = 0; + + if (i == caret) + { + if (strlen(preeditBuf) + strlen("|") < MAX_PREEDIT_LEN) + strcat(preeditBuf, "|"); + } + if (remainingBlockSize == 0) + { + if (blockIndex == focusedBlock) + { + if (strlen(preeditBuf) + strlen("]") < MAX_PREEDIT_LEN) + strcat(preeditBuf, "]"); + } + blockIndex++; + remainingBlockSize = blockSizes[blockIndex]; + if (blockIndex == focusedBlock) + { + if (strlen(preeditBuf) + strlen("[") < MAX_PREEDIT_LEN) + strcat(preeditBuf, "["); + } + } + encodedCount = encode_utf8(encoded, preeditString[i]); + encoded[encodedCount] = '\0'; + if (strlen(preeditBuf) + strlen(encoded) < MAX_PREEDIT_LEN) + strcat(preeditBuf, encoded); + remainingBlockSize--; + } + if (blockIndex == focusedBlock) + { + if (strlen(preeditBuf) + strlen("]") < MAX_PREEDIT_LEN) + strcat(preeditBuf, "]"); + } + if (caret == preeditCount) + { + if (strlen(preeditBuf) + strlen("|") < MAX_PREEDIT_LEN) + strcat(preeditBuf, "|"); + } +} + +int main(int argc, char** argv) +{ + GLFWwindow* window; + struct nk_context* nk; + int width, height; + char boxBuffer[MAX_BUFFER_LEN] = "Input text here."; + int boxLen = strlen(boxBuffer); + int isAutoUpdatingCursorPosEnabled = GLFW_TRUE; + int ch; + + while ((ch = getopt(argc, argv, "hs")) != -1) + { + switch (ch) + { + case 'h': + usage(); + exit(EXIT_SUCCESS); + + case 's': + glfwInitHint(GLFW_X11_ONTHESPOT, GLFW_TRUE); + break; + } + } + + if (!glfwInit()) + exit(EXIT_FAILURE); + + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); + glfwWindowHint(GLFW_WIN32_KEYBOARD_MENU, GLFW_TRUE); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); + + window = glfwCreateWindow(600, 600, "Input Text", NULL, NULL); + if (!window) + { + glfwTerminate(); + exit(EXIT_FAILURE); + } + + currentIMEStatus = glfwGetInputMode(window, GLFW_IME); + glfwSetPreeditCursorRectangle(window, 0, 0, 1, 1); + glfwSetIMEStatusCallback(window, ime_callback); + glfwSetPreeditCallback(window, preedit_callback); + + glfwMakeContextCurrent(window); + gladLoadGL(glfwGetProcAddress); + glfwSwapInterval(0); + + nk = nk_glfw3_init(window, NK_GLFW3_INSTALL_CALLBACKS); + init_font_list(); + update_font(nk, 18); + + while (!glfwWindowShouldClose(window)) + { + struct nk_rect area; + + glfwGetWindowSize(window, &width, &height); + + area = nk_rect(0.f, 0.f, (float) width, (float) height); + nk_window_set_bounds(nk, "main", area); + + nk_glfw3_new_frame(); + if (nk_begin(nk, "main", area, 0)) + { + set_menu_buttons(window, nk, 30); + if (set_font_selecter(window, nk, 30, 18)) + update_font(nk, 18); + set_ime_buttons(window, nk, 30); + set_preedit_cursor_edit(window, nk, 30, &isAutoUpdatingCursorPosEnabled); + set_ime_stauts_labels(window, nk, 30); + set_preedit_labels(window, nk, 30); + + nk_layout_row_dynamic(nk, height - 250, 1); + nk_edit_string(nk, NK_EDIT_BOX, boxBuffer, &boxLen, MAX_BUFFER_LEN, nk_filter_default); + } + nk_end(nk); + + glClear(GL_COLOR_BUFFER_BIT); + nk_glfw3_render(NK_ANTI_ALIASING_ON); + glfwSwapBuffers(window); + + if (isAutoUpdatingCursorPosEnabled) + update_cursor_pos(window, nk, ¤tFont->handle, boxBuffer, boxLen); + + glfwWaitEvents(); + } + + deinit_font_list(); + + nk_glfw3_shutdown(); + glfwTerminate(); + exit(EXIT_SUCCESS); +}