SDL: checkkeys: draw the IME composition text

From 382494eeda1b6960925498916afdc0258a0a1ebe Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 24 Jun 2024 18:07:49 -0700
Subject: [PATCH] checkkeys: draw the IME composition text

---
 test/checkkeys.c | 105 +++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 92 insertions(+), 13 deletions(-)

diff --git a/test/checkkeys.c b/test/checkkeys.c
index 2270ab6595348..adaa1cb0ff61d 100644
--- a/test/checkkeys.c
+++ b/test/checkkeys.c
@@ -28,35 +28,53 @@
 
 #define CURSOR_BLINK_INTERVAL_MS    500
 
+typedef struct
+{
+    SDLTest_TextWindow *textwindow;
+    char *edit_text;
+    int edit_cursor;
+    int edit_length;
+} TextWindowState;
+
 static SDLTest_CommonState *state;
-static SDLTest_TextWindow **textwindows;
+static TextWindowState *windowstates;
 static SDL_bool escape_pressed;
 static SDL_bool cursor_visible;
 static Uint64 last_cursor_change;
 static int done;
 
-static SDLTest_TextWindow *GetTextWindowForWindowID(SDL_WindowID id)
+static TextWindowState *GetTextWindowStateForWindowID(SDL_WindowID id)
 {
     int i;
 
     for (i = 0; i < state->num_windows; ++i) {
         if (id == SDL_GetWindowID(state->windows[i])) {
-            return textwindows[i];
+            return &windowstates[i];
         }
     }
     return NULL;
 }
 
+static SDLTest_TextWindow *GetTextWindowForWindowID(SDL_WindowID id)
+{
+    TextWindowState *windowstate = GetTextWindowStateForWindowID(id);
+    if (windowstate) {
+        return windowstate->textwindow;
+    }
+    return NULL;
+}
+
 static void UpdateTextWindowInputRect(SDL_WindowID id)
 {
     int i;
 
     for (i = 0; i < state->num_windows; ++i) {
         if (id == SDL_GetWindowID(state->windows[i])) {
+            SDLTest_TextWindow *textwindow = windowstates[i].textwindow;
             int w, h;
             SDL_Rect rect;
-            int current = textwindows[i]->current;
-            const char *current_line = textwindows[i]->lines[current];
+            int current = textwindow->current;
+            const char *current_line = textwindow->lines[current];
 
             SDL_GetWindowSize(state->windows[i], &w, &h);
 
@@ -271,13 +289,18 @@ static void CountKeysDown(void)
 static void DrawCursor(int i)
 {
     SDL_FRect rect;
-    int current = textwindows[i]->current;
-    const char *current_line = textwindows[i]->lines[current];
+    TextWindowState *windowstate = &windowstates[i];
+    SDLTest_TextWindow *textwindow = windowstate->textwindow;
+    int current = textwindow->current;
+    const char *current_line = textwindow->lines[current];
 
     rect.x = TEXT_WINDOW_OFFSET_X;
     if (current_line) {
         rect.x += SDL_utf8strlen(current_line) * FONT_CHARACTER_SIZE;
     }
+    if (windowstate->edit_cursor > 0) {
+        rect.x += (float)windowstate->edit_cursor * FONT_CHARACTER_SIZE;
+    }
     rect.y = TEXT_WINDOW_OFFSET_Y + current * FONT_LINE_HEIGHT;
     rect.w = FONT_CHARACTER_SIZE * 0.75f;
     rect.h = (float)FONT_CHARACTER_SIZE;
@@ -286,6 +309,45 @@ static void DrawCursor(int i)
     SDL_RenderFillRect(state->renderers[i], &rect);
 }
 
+static void DrawEditText(int i)
+{
+    SDL_FRect rect;
+    TextWindowState *windowstate = &windowstates[i];
+    SDLTest_TextWindow *textwindow = windowstate->textwindow;
+    int current = textwindow->current;
+    const char *current_line = textwindow->lines[current];
+
+    if (windowstate->edit_text == NULL) {
+        return;
+    }
+
+    /* Draw the highlight under the selected text */
+    if (windowstate->edit_length > 0) {
+        rect.x = TEXT_WINDOW_OFFSET_X;
+        if (current_line) {
+            rect.x += SDL_utf8strlen(current_line) * FONT_CHARACTER_SIZE;
+        }
+        if (windowstate->edit_cursor > 0) {
+            rect.x += (float)windowstate->edit_cursor * FONT_CHARACTER_SIZE;
+        }
+        rect.y = TEXT_WINDOW_OFFSET_Y + current * FONT_LINE_HEIGHT;
+        rect.w = (float)windowstate->edit_length * FONT_CHARACTER_SIZE;
+        rect.h = (float)FONT_CHARACTER_SIZE;
+
+        SDL_SetRenderDrawColor(state->renderers[i], 0xAA, 0xAA, 0xAA, 255);
+        SDL_RenderFillRect(state->renderers[i], &rect);
+    }
+
+    /* Draw the edit text */
+    rect.x = TEXT_WINDOW_OFFSET_X;
+    if (current_line) {
+        rect.x += SDL_utf8strlen(current_line) * FONT_CHARACTER_SIZE;
+    }
+    rect.y = TEXT_WINDOW_OFFSET_Y + current * FONT_LINE_HEIGHT;
+    SDL_SetRenderDrawColor(state->renderers[i], 255, 255, 0, 255);
+    SDLTest_DrawString(state->renderers[i], rect.x, rect.y, windowstate->edit_text);
+}
+
 static void loop(void)
 {
     SDL_Event event;
@@ -325,9 +387,22 @@ static void loop(void)
             CountKeysDown();
             break;
         case SDL_EVENT_TEXT_EDITING:
+        {
+            TextWindowState *windowstate = GetTextWindowStateForWindowID(event.edit.windowID);
+            if (windowstate->edit_text) {
+                SDL_free(windowstate->edit_text);
+                windowstate->edit_text = NULL;
+            }
+            if (event.edit.text && *event.edit.text) {
+                windowstate->edit_text = SDL_strdup(event.edit.text);
+            }
+            windowstate->edit_cursor = event.edit.start;
+            windowstate->edit_length = event.edit.length;
+
             SDL_snprintf(line, sizeof(line), "EDIT %" SDL_PRIs32 ":%" SDL_PRIs32, event.edit.start, event.edit.length);
             PrintText(line, event.edit.text);
             break;
+        }
         case SDL_EVENT_TEXT_INPUT:
             PrintText("INPUT", event.text.text);
             SDLTest_TextWindowAddText(GetTextWindowForWindowID(event.text.windowID), "%s", event.text.text);
@@ -381,7 +456,7 @@ static void loop(void)
         SDL_SetRenderDrawColor(state->renderers[i], 255, 255, 255, 255);
         SDL_snprintf(caption, sizeof(caption), "Text input %s (click right mouse button to toggle)\n", SDL_TextInputActive(state->windows[i]) ? "enabled" : "disabled");
         SDLTest_DrawString(state->renderers[i], TEXT_WINDOW_OFFSET_X, TEXT_WINDOW_OFFSET_X, caption);
-        SDLTest_TextWindowDisplay(textwindows[i], state->renderers[i]);
+        SDLTest_TextWindowDisplay(windowstates[i].textwindow, state->renderers[i]);
 
         /* Draw the cursor */
         if ((now - last_cursor_change) >= CURSOR_BLINK_INTERVAL_MS) {
@@ -391,6 +466,10 @@ static void loop(void)
         if (cursor_visible) {
             DrawCursor(i);
         }
+
+        /* Draw the composition text */
+        DrawEditText(i);
+
         SDL_RenderPresent(state->renderers[i]);
     }
 
@@ -434,8 +513,8 @@ int main(int argc, char *argv[])
         return 1;
     }
 
-    textwindows = (SDLTest_TextWindow **)SDL_malloc(state->num_windows * sizeof(*textwindows));
-    if (!textwindows) {
+    windowstates = (TextWindowState *)SDL_calloc(state->num_windows, sizeof(*windowstates));
+    if (!windowstates) {
         SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't allocate text windows: %s\n", SDL_GetError());
         goto done;
     }
@@ -449,7 +528,7 @@ int main(int argc, char *argv[])
         rect.y = TEXT_WINDOW_OFFSET_Y;
         rect.w = w - (2 * TEXT_WINDOW_OFFSET_X);
         rect.h = h - TEXT_WINDOW_OFFSET_Y;
-        textwindows[i] = SDLTest_TextWindowCreate(rect.x, rect.y, rect.w, rect.h);
+        windowstates[i].textwindow = SDLTest_TextWindowCreate(rect.x, rect.y, rect.w, rect.h);
     }
 
 #ifdef SDL_PLATFORM_IOS
@@ -488,9 +567,9 @@ int main(int argc, char *argv[])
 
 done:
     for (i = 0; i < state->num_windows; ++i) {
-        SDLTest_TextWindowDestroy(textwindows[i]);
+        SDLTest_TextWindowDestroy(windowstates[i].textwindow);
     }
-    SDL_free(textwindows);
+    SDL_free(windowstates);
     SDLTest_CleanupTextDrawing();
     SDLTest_CommonQuit(state);
     return 0;