From e9d5060c4c2df66e8d4ef4f748f80e8ef32fc233 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Fri, 1 Jul 2022 12:56:47 -0700 Subject: [PATCH] checkkeys will now render text that is input Also added test functions for multi-line debug text display Currently this only supports ASCII, as the font doesn't have the correct Latin-1 characters --- VisualC/tests/checkkeys/checkkeys.vcxproj | 8 +- include/SDL_test_font.h | 93 ++++++++- src/test/SDL_test_font.c | 223 +++++++++++++++++++++- test/checkkeys.c | 40 +++- 4 files changed, 346 insertions(+), 18 deletions(-) diff --git a/VisualC/tests/checkkeys/checkkeys.vcxproj b/VisualC/tests/checkkeys/checkkeys.vcxproj index b9d4f9b21..3f068377b 100644 --- a/VisualC/tests/checkkeys/checkkeys.vcxproj +++ b/VisualC/tests/checkkeys/checkkeys.vcxproj @@ -200,6 +200,12 @@ false true + + {da956fd3-e143-46f2-9fe5-c77bebc56b1a} + false + false + true + @@ -216,4 +222,4 @@ - \ No newline at end of file + diff --git a/include/SDL_test_font.h b/include/SDL_test_font.h index c5cbbbbd3..ea50e88ac 100644 --- a/include/SDL_test_font.h +++ b/include/SDL_test_font.h @@ -38,7 +38,8 @@ extern "C" { /* Function prototypes */ -#define FONT_CHARACTER_SIZE 8 +#define FONT_CHARACTER_SIZE 8 +#define FONT_LINE_HEIGHT (FONT_CHARACTER_SIZE + 2) /** * \brief Draw a string in the currently set font. @@ -50,10 +51,12 @@ extern "C" { * * \returns 0 on success, -1 on failure. */ -int SDLTest_DrawCharacter(SDL_Renderer *renderer, int x, int y, char c); +int SDLTest_DrawCharacter(SDL_Renderer *renderer, int x, int y, Uint32 c); /** - * \brief Draw a string in the currently set font. + * \brief Draw a UTF-8 string in the currently set font. + * + * The font currently only supports characters in the Basic Latin and Latin-1 Supplement sets. * * \param renderer The renderer to draw on. * \param x The X coordinate of the upper left corner of the string. @@ -64,6 +67,90 @@ int SDLTest_DrawCharacter(SDL_Renderer *renderer, int x, int y, char c); */ int SDLTest_DrawString(SDL_Renderer *renderer, int x, int y, const char *s); +/** + * \brief Data used for multi-line text output + */ +typedef struct SDLTest_TextWindow +{ + SDL_Rect rect; + int current; + int numlines; + char **lines; +} SDLTest_TextWindow; + +/** + * \brief Create a multi-line text output window + * + * \param x The X coordinate of the upper left corner of the window. + * \param y The Y coordinate of the upper left corner of the window. + * \param w The width of the window (currently ignored) + * \param h The height of the window (currently ignored) + * + * \returns the new window, or NULL on failure. + * + * \since This function is available since SDL 2.24.0 + */ +SDLTest_TextWindow *SDLTest_TextWindowCreate(int x, int y, int w, int h); + +/** + * \brief Display a multi-line text output window + * + * This function should be called every frame to display the text + * + * \param textwin The text output window + * \param renderer The renderer to use for display + * + * \since This function is available since SDL 2.24.0 + */ +void SDLTest_TextWindowDisplay(SDLTest_TextWindow *textwin, SDL_Renderer *renderer); + +/** + * \brief Add text to a multi-line text output window + * + * Adds UTF-8 text to the end of the current text. The '\n' newline character starts a + * new line of text. The '\b' backspace character deletes the last character or, if the + * line is empty, deletes the line and goes to the end of the previous line. + * + * \param textwin The text output window + * \param fmt A printf() style format string + * \param ... additional parameters matching % tokens in the `fmt` string, if any + * + * \since This function is available since SDL 2.24.0 + */ +void SDLTest_TextWindowAddText(SDLTest_TextWindow *textwin, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) SDL_PRINTF_VARARG_FUNC(2); + +/** + * \brief Add text to a multi-line text output window + * + * Adds UTF-8 text to the end of the current text. The '\n' newline character starts a + * new line of text. The '\b' backspace character deletes the last character or, if the + * line is empty, deletes the line and goes to the end of the previous line. + * + * \param textwin The text output window + * \param text The text to add to the window + * \param len The length, in bytes, of the text to add to the window + * + * \since This function is available since SDL 2.24.0 + */ +void SDLTest_TextWindowAddTextWithLength(SDLTest_TextWindow *textwin, const char *text, size_t len); + +/** + * \brief Clear the text in a multi-line text output window + * + * \param textwin The text output window + * + * \since This function is available since SDL 2.24.0 + */ +void SDLTest_TextWindowClear(SDLTest_TextWindow *textwin); + +/** + * \brief Free the storage associated with a multi-line text output window + * + * \param textwin The text output window + * + * \since This function is available since SDL 2.24.0 + */ +void SDLTest_TextWindowDestroy(SDLTest_TextWindow *textwin); /** * \brief Cleanup textures used by font drawing functions. diff --git a/src/test/SDL_test_font.c b/src/test/SDL_test_font.c index 4129f3da2..d7ed98dea 100644 --- a/src/test/SDL_test_font.c +++ b/src/test/SDL_test_font.c @@ -3120,7 +3120,7 @@ struct SDLTest_CharTextureCache { */ static struct SDLTest_CharTextureCache *SDLTest_CharTextureCacheList; -int SDLTest_DrawCharacter(SDL_Renderer *renderer, int x, int y, char c) +int SDLTest_DrawCharacter(SDL_Renderer *renderer, int x, int y, Uint32 c) { const Uint32 charWidth = FONT_CHARACTER_SIZE; const Uint32 charHeight = FONT_CHARACTER_SIZE; @@ -3156,7 +3156,7 @@ int SDLTest_DrawCharacter(SDL_Renderer *renderer, int x, int y, char c) drect.h = charHeight; /* Character index in cache */ - ci = (unsigned char)c; + ci = c; /* Search for this renderer's cache */ for (cache = SDLTest_CharTextureCacheList; cache != NULL; cache = cache->next) { @@ -3242,23 +3242,234 @@ int SDLTest_DrawCharacter(SDL_Renderer *renderer, int x, int y, char c) return (result); } +/* Gets a unicode value from a UTF-8 encoded string + * Outputs increment to advance the string */ +#define UNKNOWN_UNICODE 0xFFFD +static Uint32 UTF8_getch(const char *src, size_t srclen, int *inc) +{ + const Uint8 *p = (const Uint8 *)src; + size_t left = 0; + size_t save_srclen = srclen; + SDL_bool overlong = SDL_FALSE; + SDL_bool underflow = SDL_FALSE; + Uint32 ch = UNKNOWN_UNICODE; + + if (srclen == 0) { + return UNKNOWN_UNICODE; + } + if (p[0] >= 0xFC) { + if ((p[0] & 0xFE) == 0xFC) { + if (p[0] == 0xFC && (p[1] & 0xFC) == 0x80) { + overlong = SDL_TRUE; + } + ch = (Uint32) (p[0] & 0x01); + left = 5; + } + } else if (p[0] >= 0xF8) { + if ((p[0] & 0xFC) == 0xF8) { + if (p[0] == 0xF8 && (p[1] & 0xF8) == 0x80) { + overlong = SDL_TRUE; + } + ch = (Uint32) (p[0] & 0x03); + left = 4; + } + } else if (p[0] >= 0xF0) { + if ((p[0] & 0xF8) == 0xF0) { + if (p[0] == 0xF0 && (p[1] & 0xF0) == 0x80) { + overlong = SDL_TRUE; + } + ch = (Uint32) (p[0] & 0x07); + left = 3; + } + } else if (p[0] >= 0xE0) { + if ((p[0] & 0xF0) == 0xE0) { + if (p[0] == 0xE0 && (p[1] & 0xE0) == 0x80) { + overlong = SDL_TRUE; + } + ch = (Uint32) (p[0] & 0x0F); + left = 2; + } + } else if (p[0] >= 0xC0) { + if ((p[0] & 0xE0) == 0xC0) { + if ((p[0] & 0xDE) == 0xC0) { + overlong = SDL_TRUE; + } + ch = (Uint32) (p[0] & 0x1F); + left = 1; + } + } else { + if ((p[0] & 0x80) == 0x00) { + ch = (Uint32) p[0]; + } + } + --srclen; + while (left > 0 && srclen > 0) { + ++p; + if ((p[0] & 0xC0) != 0x80) { + ch = UNKNOWN_UNICODE; + break; + } + ch <<= 6; + ch |= (p[0] & 0x3F); + --srclen; + --left; + } + if (left > 0) { + underflow = SDL_TRUE; + } + + if (overlong || underflow || + (ch >= 0xD800 && ch <= 0xDFFF) || + (ch == 0xFFFE || ch == 0xFFFF) || ch > 0x10FFFF) { + ch = UNKNOWN_UNICODE; + } + + *inc = (int)(save_srclen - srclen); + + return ch; +} + +#define UTF8_IsTrailingByte(c) ((c) >= 0x80 && (c) <= 0xBF) + int SDLTest_DrawString(SDL_Renderer * renderer, int x, int y, const char *s) { const Uint32 charWidth = FONT_CHARACTER_SIZE; int result = 0; int curx = x; int cury = y; - const char *curchar = s; + size_t len = SDL_strlen(s); - while (*curchar && !result) { - result |= SDLTest_DrawCharacter(renderer, curx, cury, *curchar); + while (len > 0 && !result) { + int advance = 0; + Uint32 ch = UTF8_getch(s, len, &advance); + if (ch < 256) { + result |= SDLTest_DrawCharacter(renderer, curx, cury, ch); + } curx += charWidth; - curchar++; + s += advance; + len -= advance; } return (result); } +SDLTest_TextWindow *SDLTest_TextWindowCreate(int x, int y, int w, int h) +{ + SDLTest_TextWindow *textwin = (SDLTest_TextWindow *)SDL_malloc(sizeof(*textwin)); + + if ( !textwin ) { + return NULL; + } + + textwin->rect.x = x; + textwin->rect.y = y; + textwin->rect.w = w; + textwin->rect.h = h; + textwin->current = 0; + textwin->numlines = (h / FONT_LINE_HEIGHT); + textwin->lines = (char **)SDL_calloc(textwin->numlines, sizeof(*textwin->lines)); + if ( !textwin->lines ) { + SDL_free(textwin); + return NULL; + } + return textwin; +} + +void SDLTest_TextWindowDisplay(SDLTest_TextWindow *textwin, SDL_Renderer *renderer) +{ + int i, y; + + for ( y = textwin->rect.y, i = 0; i < textwin->numlines; ++i, y += FONT_LINE_HEIGHT ) { + if ( textwin->lines[i] ) { + SDLTest_DrawString(renderer, textwin->rect.x, y, textwin->lines[i]); + } + } +} + +void SDLTest_TextWindowAddText(SDLTest_TextWindow *textwin, const char *fmt, ...) +{ + char text[1024]; + va_list ap; + + va_start(ap, fmt); + SDL_vsnprintf(text, sizeof(text), fmt, ap); + va_end(ap); + + SDLTest_TextWindowAddTextWithLength(textwin, text, SDL_strlen(text)); +} + +void SDLTest_TextWindowAddTextWithLength(SDLTest_TextWindow *textwin, const char *text, size_t len) +{ + size_t existing; + SDL_bool newline = SDL_FALSE; + char *line; + + if ( len > 0 && text[len - 1] == '\n' ) { + --len; + newline = SDL_TRUE; + } + + if ( textwin->lines[textwin->current] ) { + existing = SDL_strlen(textwin->lines[textwin->current]); + } else { + existing = 0; + } + + if ( *text == '\b' ) { + if ( existing ) { + while (existing > 1 && UTF8_IsTrailingByte((Uint8)textwin->lines[textwin->current][existing - 1])) { + --existing; + } + --existing; + textwin->lines[textwin->current][existing] = '\0'; + } else if (textwin->current > 0) { + SDL_free(textwin->lines[textwin->current]); + textwin->lines[textwin->current] = NULL; + --textwin->current; + } + return; + } + + line = (char *)SDL_realloc(textwin->lines[textwin->current], existing + len + 1); + if ( line ) { + SDL_memcpy(&line[existing], text, len); + line[existing + len] = '\0'; + textwin->lines[textwin->current] = line; + if ( newline ) { + if (textwin->current == textwin->numlines - 1) { + SDL_free(textwin->lines[0]); + SDL_memcpy(&textwin->lines[0], &textwin->lines[1], (textwin->numlines-1) * sizeof(textwin->lines[1])); + textwin->lines[textwin->current] = NULL; + } else { + ++textwin->current; + } + } + } +} + +void SDLTest_TextWindowClear(SDLTest_TextWindow *textwin) +{ + int i; + + for ( i = 0; i < textwin->numlines; ++i ) + { + if ( textwin->lines[i] ) { + SDL_free(textwin->lines[i]); + textwin->lines[i] = NULL; + } + } + textwin->current = 0; +} + +void SDLTest_TextWindowDestroy(SDLTest_TextWindow *textwin) +{ + if ( textwin ) { + SDLTest_TextWindowClear(textwin); + SDL_free(textwin->lines); + SDL_free(textwin); + } +} + void SDLTest_CleanupTextDrawing(void) { unsigned int i; diff --git a/test/checkkeys.c b/test/checkkeys.c index 010360278..385b80629 100644 --- a/test/checkkeys.c +++ b/test/checkkeys.c @@ -24,8 +24,12 @@ #endif #include "SDL.h" +#include "SDL_test_font.h" -int done; +static SDL_Window *window; +static SDL_Renderer *renderer; +static SDLTest_TextWindow *textwin; +static int done; /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ static void @@ -163,6 +167,18 @@ loop() case SDL_KEYDOWN: case SDL_KEYUP: PrintKey(&event.key.keysym, (event.key.state == SDL_PRESSED) ? SDL_TRUE : SDL_FALSE, (event.key.repeat) ? SDL_TRUE : SDL_FALSE); + if (event.type == SDL_KEYDOWN) { + switch (event.key.keysym.sym) { + case SDLK_BACKSPACE: + SDLTest_TextWindowAddText(textwin, "\b"); + break; + case SDLK_RETURN: + SDLTest_TextWindowAddText(textwin, "\n"); + break; + default: + break; + } + } break; case SDL_TEXTEDITING: PrintText("EDIT", event.edit.text); @@ -173,6 +189,7 @@ loop() break; case SDL_TEXTINPUT: PrintText("INPUT", event.text.text); + SDLTest_TextWindowAddText(textwin, "%s", event.text.text); break; case SDL_FINGERDOWN: if (SDL_IsTextInputActive()) { @@ -204,6 +221,13 @@ loop() break; } } + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDLTest_TextWindowDisplay(textwin, renderer); + SDL_RenderPresent(renderer); + #ifdef __EMSCRIPTEN__ if (done) { emscripten_cancel_main_loop(); @@ -214,9 +238,6 @@ loop() int main(int argc, char *argv[]) { - SDL_Window *window; - SDL_Renderer *renderer; - /* Enable standard application logging */ SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); @@ -242,11 +263,14 @@ main(int argc, char *argv[]) quit(2); } - /* On wayland, no window will actually show until something has - actually been displayed. - */ renderer = SDL_CreateRenderer(window, -1, 0); - SDL_RenderPresent(renderer); + if (!renderer) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s\n", + SDL_GetError()); + quit(2); + } + + textwin = SDLTest_TextWindowCreate(0, 0, 640, 480); #if __IPHONEOS__ /* Creating the context creates the view, which we need to show keyboard */