diff options
Diffstat (limited to 'contrib/SDL-3.2.8/test/testime.c')
| -rw-r--r-- | contrib/SDL-3.2.8/test/testime.c | 1155 |
1 files changed, 1155 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/test/testime.c b/contrib/SDL-3.2.8/test/testime.c new file mode 100644 index 0000000..ea56d3c --- /dev/null +++ b/contrib/SDL-3.2.8/test/testime.c | |||
| @@ -0,0 +1,1155 @@ | |||
| 1 | /* | ||
| 2 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 3 | |||
| 4 | This software is provided 'as-is', without any express or implied | ||
| 5 | warranty. In no event will the authors be held liable for any damages | ||
| 6 | arising from the use of this software. | ||
| 7 | |||
| 8 | Permission is granted to anyone to use this software for any purpose, | ||
| 9 | including commercial applications, and to alter it and redistribute it | ||
| 10 | freely. | ||
| 11 | */ | ||
| 12 | |||
| 13 | /* A simple program to test the Input Method support in SDL. | ||
| 14 | * | ||
| 15 | * This uses the GNU Unifont to display non-ASCII characters, available at: | ||
| 16 | * http://unifoundry.com/unifont.html | ||
| 17 | * | ||
| 18 | * An example of IME support with TrueType fonts is available in the SDL_ttf example code: | ||
| 19 | * https://github.com/libsdl-org/SDL_ttf/blob/main/examples/editbox.h | ||
| 20 | */ | ||
| 21 | #include <SDL3/SDL.h> | ||
| 22 | #include <SDL3/SDL_main.h> | ||
| 23 | #include <SDL3/SDL_test_font.h> | ||
| 24 | |||
| 25 | #include <SDL3/SDL_test_common.h> | ||
| 26 | #include "testutils.h" | ||
| 27 | |||
| 28 | #define DEFAULT_FONT "unifont-15.1.05.hex" | ||
| 29 | #define MAX_TEXT_LENGTH 256 | ||
| 30 | |||
| 31 | #define WINDOW_WIDTH 640 | ||
| 32 | #define WINDOW_HEIGHT 480 | ||
| 33 | |||
| 34 | #define MARGIN 32.0f | ||
| 35 | #define LINE_HEIGHT (FONT_CHARACTER_SIZE + 4.0f) | ||
| 36 | #define CURSOR_BLINK_INTERVAL_MS 500 | ||
| 37 | |||
| 38 | typedef struct | ||
| 39 | { | ||
| 40 | SDL_Window *window; | ||
| 41 | SDL_Renderer *renderer; | ||
| 42 | int rendererID; | ||
| 43 | bool settings_visible; | ||
| 44 | SDL_Texture *settings_icon; | ||
| 45 | SDL_FRect settings_rect; | ||
| 46 | SDL_PropertiesID text_settings; | ||
| 47 | SDL_FRect textRect; | ||
| 48 | SDL_FRect markedRect; | ||
| 49 | char text[MAX_TEXT_LENGTH]; | ||
| 50 | char markedText[MAX_TEXT_LENGTH]; | ||
| 51 | int cursor; | ||
| 52 | int cursor_length; | ||
| 53 | bool cursor_visible; | ||
| 54 | Uint64 last_cursor_change; | ||
| 55 | char **candidates; | ||
| 56 | int num_candidates; | ||
| 57 | int selected_candidate; | ||
| 58 | bool horizontal_candidates; | ||
| 59 | } WindowState; | ||
| 60 | |||
| 61 | static SDLTest_CommonState *state; | ||
| 62 | static WindowState *windowstate; | ||
| 63 | static const SDL_Color lineColor = { 0, 0, 0, 255 }; | ||
| 64 | static const SDL_Color backColor = { 255, 255, 255, 255 }; | ||
| 65 | static const SDL_Color textColor = { 0, 0, 0, 255 }; | ||
| 66 | static SDL_BlendMode highlight_mode; | ||
| 67 | |||
| 68 | static const struct | ||
| 69 | { | ||
| 70 | const char *label; | ||
| 71 | const char *setting; | ||
| 72 | int value; | ||
| 73 | } settings[] = { | ||
| 74 | { "Text", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT }, | ||
| 75 | { "Name", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_NAME }, | ||
| 76 | { "E-mail", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_EMAIL }, | ||
| 77 | { "Username", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_USERNAME }, | ||
| 78 | { "Password (hidden)", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN }, | ||
| 79 | { "Password (visible)", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_VISIBLE }, | ||
| 80 | { "Number", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_NUMBER }, | ||
| 81 | { "Numeric PIN (hidden)", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN }, | ||
| 82 | { "Numeric PIN (visible)", SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_VISIBLE }, | ||
| 83 | { "", NULL }, | ||
| 84 | { "No capitalization", SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_NONE }, | ||
| 85 | { "Capitalize sentences", SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_SENTENCES }, | ||
| 86 | { "Capitalize words", SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_WORDS }, | ||
| 87 | { "All caps", SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_LETTERS }, | ||
| 88 | { "", NULL }, | ||
| 89 | { "Auto-correct OFF", SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN, false }, | ||
| 90 | { "Auto-correct ON", SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN, true }, | ||
| 91 | { "Multiline OFF", SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN, false }, | ||
| 92 | { "Multiline ON", SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN, true } | ||
| 93 | }; | ||
| 94 | |||
| 95 | #define UNIFONT_MAX_CODEPOINT 0x1ffff | ||
| 96 | #define UNIFONT_NUM_GLYPHS 0x20000 | ||
| 97 | #define UNIFONT_REPLACEMENT 0xFFFD | ||
| 98 | /* Using 512x512 textures that are supported everywhere. */ | ||
| 99 | #define UNIFONT_TEXTURE_WIDTH 512 | ||
| 100 | #define UNIFONT_GLYPH_SIZE 16 | ||
| 101 | #define UNIFONT_GLYPH_BORDER 1 | ||
| 102 | #define UNIFONT_GLYPH_AREA (UNIFONT_GLYPH_BORDER + UNIFONT_GLYPH_SIZE + UNIFONT_GLYPH_BORDER) | ||
| 103 | #define UNIFONT_GLYPHS_IN_ROW (UNIFONT_TEXTURE_WIDTH / UNIFONT_GLYPH_AREA) | ||
| 104 | #define UNIFONT_GLYPHS_IN_TEXTURE (UNIFONT_GLYPHS_IN_ROW * UNIFONT_GLYPHS_IN_ROW) | ||
| 105 | #define UNIFONT_NUM_TEXTURES ((UNIFONT_NUM_GLYPHS + UNIFONT_GLYPHS_IN_TEXTURE - 1) / UNIFONT_GLYPHS_IN_TEXTURE) | ||
| 106 | #define UNIFONT_TEXTURE_SIZE (UNIFONT_TEXTURE_WIDTH * UNIFONT_TEXTURE_WIDTH * 4) | ||
| 107 | #define UNIFONT_TEXTURE_PITCH (UNIFONT_TEXTURE_WIDTH * 4) | ||
| 108 | #define UNIFONT_DRAW_SCALE 2.0f | ||
| 109 | static struct UnifontGlyph | ||
| 110 | { | ||
| 111 | Uint8 width; | ||
| 112 | Uint8 data[32]; | ||
| 113 | } * unifontGlyph; | ||
| 114 | static SDL_Texture **unifontTexture; | ||
| 115 | static Uint8 unifontTextureLoaded[UNIFONT_NUM_TEXTURES] = { 0 }; | ||
| 116 | |||
| 117 | /* Unifont loading code start */ | ||
| 118 | |||
| 119 | static Uint8 dehex(char c) | ||
| 120 | { | ||
| 121 | if (c >= '0' && c <= '9') { | ||
| 122 | return c - '0'; | ||
| 123 | } else if (c >= 'a' && c <= 'f') { | ||
| 124 | return c - 'a' + 10; | ||
| 125 | } else if (c >= 'A' && c <= 'F') { | ||
| 126 | return c - 'A' + 10; | ||
| 127 | } | ||
| 128 | return 255; | ||
| 129 | } | ||
| 130 | |||
| 131 | static Uint8 dehex2(char c1, char c2) | ||
| 132 | { | ||
| 133 | return (dehex(c1) << 4) | dehex(c2); | ||
| 134 | } | ||
| 135 | |||
| 136 | static Uint8 validate_hex(const char *cp, size_t len, Uint32 *np) | ||
| 137 | { | ||
| 138 | Uint32 n = 0; | ||
| 139 | for (; len > 0; cp++, len--) { | ||
| 140 | Uint8 c = dehex(*cp); | ||
| 141 | if (c == 255) { | ||
| 142 | return 0; | ||
| 143 | } | ||
| 144 | n = (n << 4) | c; | ||
| 145 | } | ||
| 146 | if (np) { | ||
| 147 | *np = n; | ||
| 148 | } | ||
| 149 | return 1; | ||
| 150 | } | ||
| 151 | |||
| 152 | static int unifont_init(const char *fontname) | ||
| 153 | { | ||
| 154 | Uint8 hexBuffer[65]; | ||
| 155 | Uint32 numGlyphs = 0; | ||
| 156 | int lineNumber = 1; | ||
| 157 | size_t bytesRead; | ||
| 158 | SDL_IOStream *hexFile; | ||
| 159 | const size_t unifontGlyphSize = UNIFONT_NUM_GLYPHS * sizeof(struct UnifontGlyph); | ||
| 160 | const size_t unifontTextureSize = UNIFONT_NUM_TEXTURES * state->num_windows * sizeof(void *); | ||
| 161 | char *filename; | ||
| 162 | |||
| 163 | /* Allocate memory for the glyph data so the file can be closed after initialization. */ | ||
| 164 | unifontGlyph = (struct UnifontGlyph *)SDL_malloc(unifontGlyphSize); | ||
| 165 | if (!unifontGlyph) { | ||
| 166 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Failed to allocate %d KiB for glyph data.", (int)(unifontGlyphSize + 1023) / 1024); | ||
| 167 | return -1; | ||
| 168 | } | ||
| 169 | SDL_memset(unifontGlyph, 0, unifontGlyphSize); | ||
| 170 | |||
| 171 | /* Allocate memory for texture pointers for all renderers. */ | ||
| 172 | unifontTexture = (SDL_Texture **)SDL_malloc(unifontTextureSize); | ||
| 173 | if (!unifontTexture) { | ||
| 174 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Failed to allocate %d KiB for texture pointer data.", (int)(unifontTextureSize + 1023) / 1024); | ||
| 175 | return -1; | ||
| 176 | } | ||
| 177 | SDL_memset(unifontTexture, 0, unifontTextureSize); | ||
| 178 | |||
| 179 | filename = GetResourceFilename(NULL, fontname); | ||
| 180 | if (!filename) { | ||
| 181 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory"); | ||
| 182 | return -1; | ||
| 183 | } | ||
| 184 | hexFile = SDL_IOFromFile(filename, "rb"); | ||
| 185 | SDL_free(filename); | ||
| 186 | if (!hexFile) { | ||
| 187 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Failed to open font file: %s", fontname); | ||
| 188 | return -1; | ||
| 189 | } | ||
| 190 | |||
| 191 | /* Read all the glyph data into memory to make it accessible later when textures are created. */ | ||
| 192 | do { | ||
| 193 | int i, codepointHexSize; | ||
| 194 | size_t bytesOverread; | ||
| 195 | Uint8 glyphWidth; | ||
| 196 | Uint32 codepoint; | ||
| 197 | |||
| 198 | bytesRead = SDL_ReadIO(hexFile, hexBuffer, 9); | ||
| 199 | if (numGlyphs > 0 && bytesRead == 0) { | ||
| 200 | break; /* EOF */ | ||
| 201 | } | ||
| 202 | if ((numGlyphs == 0 && bytesRead == 0) || (numGlyphs > 0 && bytesRead < 9)) { | ||
| 203 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Unexpected end of hex file."); | ||
| 204 | return -1; | ||
| 205 | } | ||
| 206 | |||
| 207 | /* Looking for the colon that separates the codepoint and glyph data at position 2, 4, 6 and 8. */ | ||
| 208 | if (hexBuffer[2] == ':') { | ||
| 209 | codepointHexSize = 2; | ||
| 210 | } else if (hexBuffer[4] == ':') { | ||
| 211 | codepointHexSize = 4; | ||
| 212 | } else if (hexBuffer[6] == ':') { | ||
| 213 | codepointHexSize = 6; | ||
| 214 | } else if (hexBuffer[8] == ':') { | ||
| 215 | codepointHexSize = 8; | ||
| 216 | } else { | ||
| 217 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Could not find codepoint and glyph data separator symbol in hex file on line %d.", lineNumber); | ||
| 218 | return -1; | ||
| 219 | } | ||
| 220 | |||
| 221 | if (!validate_hex((const char *)hexBuffer, codepointHexSize, &codepoint)) { | ||
| 222 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Malformed hexadecimal number in hex file on line %d.", lineNumber); | ||
| 223 | return -1; | ||
| 224 | } | ||
| 225 | if (codepoint > UNIFONT_MAX_CODEPOINT) { | ||
| 226 | SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "unifont: Codepoint on line %d exceeded limit of 0x%x.", lineNumber, UNIFONT_MAX_CODEPOINT); | ||
| 227 | } | ||
| 228 | |||
| 229 | /* If there was glyph data read in the last file read, move it to the front of the buffer. */ | ||
| 230 | bytesOverread = 8 - codepointHexSize; | ||
| 231 | if (codepointHexSize < 8) { | ||
| 232 | SDL_memmove(hexBuffer, hexBuffer + codepointHexSize + 1, bytesOverread); | ||
| 233 | } | ||
| 234 | bytesRead = SDL_ReadIO(hexFile, hexBuffer + bytesOverread, 33 - bytesOverread); | ||
| 235 | |||
| 236 | if (bytesRead < (33 - bytesOverread)) { | ||
| 237 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Unexpected end of hex file."); | ||
| 238 | return -1; | ||
| 239 | } | ||
| 240 | if (hexBuffer[32] == '\n') { | ||
| 241 | glyphWidth = 8; | ||
| 242 | } else { | ||
| 243 | glyphWidth = 16; | ||
| 244 | bytesRead = SDL_ReadIO(hexFile, hexBuffer + 33, 32); | ||
| 245 | if (bytesRead < 32) { | ||
| 246 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Unexpected end of hex file."); | ||
| 247 | return -1; | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | if (!validate_hex((const char *)hexBuffer, glyphWidth * 4, NULL)) { | ||
| 252 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Malformed hexadecimal glyph data in hex file on line %d.", lineNumber); | ||
| 253 | return -1; | ||
| 254 | } | ||
| 255 | |||
| 256 | if (codepoint <= UNIFONT_MAX_CODEPOINT) { | ||
| 257 | if (unifontGlyph[codepoint].width > 0) { | ||
| 258 | SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "unifont: Ignoring duplicate codepoint 0x%08" SDL_PRIx32 " in hex file on line %d.", codepoint, lineNumber); | ||
| 259 | } else { | ||
| 260 | unifontGlyph[codepoint].width = glyphWidth; | ||
| 261 | /* Pack the hex data into a more compact form. */ | ||
| 262 | for (i = 0; i < glyphWidth * 2; i++) { | ||
| 263 | unifontGlyph[codepoint].data[i] = dehex2(hexBuffer[i * 2], hexBuffer[i * 2 + 1]); | ||
| 264 | } | ||
| 265 | numGlyphs++; | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | lineNumber++; | ||
| 270 | } while (bytesRead > 0); | ||
| 271 | |||
| 272 | SDL_CloseIO(hexFile); | ||
| 273 | SDL_Log("unifont: Loaded %" SDL_PRIu32 " glyphs.", numGlyphs); | ||
| 274 | return 0; | ||
| 275 | } | ||
| 276 | |||
| 277 | static void unifont_make_rgba(const Uint8 *src, Uint8 *dst, Uint8 width) | ||
| 278 | { | ||
| 279 | int i, j; | ||
| 280 | Uint8 *row = dst; | ||
| 281 | |||
| 282 | for (i = 0; i < width * 2; i++) { | ||
| 283 | Uint8 data = src[i]; | ||
| 284 | for (j = 0; j < 8; j++) { | ||
| 285 | if (data & 0x80) { | ||
| 286 | row[0] = textColor.r; | ||
| 287 | row[1] = textColor.g; | ||
| 288 | row[2] = textColor.b; | ||
| 289 | row[3] = textColor.a; | ||
| 290 | } else { | ||
| 291 | row[0] = 0; | ||
| 292 | row[1] = 0; | ||
| 293 | row[2] = 0; | ||
| 294 | row[3] = 0; | ||
| 295 | } | ||
| 296 | data <<= 1; | ||
| 297 | row += 4; | ||
| 298 | } | ||
| 299 | |||
| 300 | if (width == 8 || (width == 16 && i % 2 == 1)) { | ||
| 301 | dst += UNIFONT_TEXTURE_PITCH; | ||
| 302 | row = dst; | ||
| 303 | } | ||
| 304 | } | ||
| 305 | } | ||
| 306 | |||
| 307 | static int unifont_load_texture(Uint32 textureID) | ||
| 308 | { | ||
| 309 | int i; | ||
| 310 | Uint8 *textureRGBA; | ||
| 311 | |||
| 312 | if (textureID >= UNIFONT_NUM_TEXTURES) { | ||
| 313 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Tried to load out of range texture %" SDL_PRIu32, textureID); | ||
| 314 | return -1; | ||
| 315 | } | ||
| 316 | |||
| 317 | textureRGBA = (Uint8 *)SDL_malloc(UNIFONT_TEXTURE_SIZE); | ||
| 318 | if (!textureRGBA) { | ||
| 319 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Failed to allocate %d MiB for a texture.", UNIFONT_TEXTURE_SIZE / 1024 / 1024); | ||
| 320 | return -1; | ||
| 321 | } | ||
| 322 | SDL_memset(textureRGBA, 0, UNIFONT_TEXTURE_SIZE); | ||
| 323 | |||
| 324 | /* Copy the glyphs into memory in RGBA format. */ | ||
| 325 | for (i = 0; i < UNIFONT_GLYPHS_IN_TEXTURE; i++) { | ||
| 326 | Uint32 codepoint = UNIFONT_GLYPHS_IN_TEXTURE * textureID + i; | ||
| 327 | if (unifontGlyph[codepoint].width > 0) { | ||
| 328 | const Uint32 cInTex = codepoint % UNIFONT_GLYPHS_IN_TEXTURE; | ||
| 329 | const size_t offset = ((size_t)cInTex / UNIFONT_GLYPHS_IN_ROW) * UNIFONT_TEXTURE_PITCH * UNIFONT_GLYPH_AREA + (cInTex % UNIFONT_GLYPHS_IN_ROW) * UNIFONT_GLYPH_AREA * 4; | ||
| 330 | unifont_make_rgba(unifontGlyph[codepoint].data, textureRGBA + offset, unifontGlyph[codepoint].width); | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | /* Create textures and upload the RGBA data from above. */ | ||
| 335 | for (i = 0; i < state->num_windows; ++i) { | ||
| 336 | SDL_Renderer *renderer = state->renderers[i]; | ||
| 337 | SDL_Texture *tex = unifontTexture[UNIFONT_NUM_TEXTURES * i + textureID]; | ||
| 338 | if (state->windows[i] == NULL || renderer == NULL || tex != NULL) { | ||
| 339 | continue; | ||
| 340 | } | ||
| 341 | tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC, UNIFONT_TEXTURE_WIDTH, UNIFONT_TEXTURE_WIDTH); | ||
| 342 | if (tex == NULL) { | ||
| 343 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "unifont: Failed to create texture %" SDL_PRIu32 " for renderer %d.", textureID, i); | ||
| 344 | return -1; | ||
| 345 | } | ||
| 346 | unifontTexture[UNIFONT_NUM_TEXTURES * i + textureID] = tex; | ||
| 347 | SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND); | ||
| 348 | if (!SDL_UpdateTexture(tex, NULL, textureRGBA, UNIFONT_TEXTURE_PITCH)) { | ||
| 349 | SDL_Log("unifont error: Failed to update texture %" SDL_PRIu32 " data for renderer %d.", textureID, i); | ||
| 350 | } | ||
| 351 | } | ||
| 352 | |||
| 353 | SDL_free(textureRGBA); | ||
| 354 | unifontTextureLoaded[textureID] = 1; | ||
| 355 | return -1; | ||
| 356 | } | ||
| 357 | |||
| 358 | static int unifont_glyph_width(Uint32 codepoint) | ||
| 359 | { | ||
| 360 | if (codepoint > UNIFONT_MAX_CODEPOINT || | ||
| 361 | unifontGlyph[codepoint].width == 0) { | ||
| 362 | codepoint = UNIFONT_REPLACEMENT; | ||
| 363 | } | ||
| 364 | return unifontGlyph[codepoint].width; | ||
| 365 | } | ||
| 366 | |||
| 367 | static int unifont_draw_glyph(Uint32 codepoint, int rendererID, SDL_FRect *dst) | ||
| 368 | { | ||
| 369 | SDL_Texture *texture; | ||
| 370 | Uint32 textureID; | ||
| 371 | SDL_FRect srcrect; | ||
| 372 | srcrect.w = srcrect.h = (float)UNIFONT_GLYPH_SIZE; | ||
| 373 | |||
| 374 | if (codepoint > UNIFONT_MAX_CODEPOINT || | ||
| 375 | unifontGlyph[codepoint].width == 0) { | ||
| 376 | codepoint = UNIFONT_REPLACEMENT; | ||
| 377 | } | ||
| 378 | |||
| 379 | textureID = codepoint / UNIFONT_GLYPHS_IN_TEXTURE; | ||
| 380 | if (!unifontTextureLoaded[textureID]) { | ||
| 381 | if (unifont_load_texture(textureID) < 0) { | ||
| 382 | return 0; | ||
| 383 | } | ||
| 384 | } | ||
| 385 | texture = unifontTexture[UNIFONT_NUM_TEXTURES * rendererID + textureID]; | ||
| 386 | if (texture) { | ||
| 387 | const Uint32 cInTex = codepoint % UNIFONT_GLYPHS_IN_TEXTURE; | ||
| 388 | srcrect.x = (float)(cInTex % UNIFONT_GLYPHS_IN_ROW * UNIFONT_GLYPH_AREA); | ||
| 389 | srcrect.y = (float)(cInTex / UNIFONT_GLYPHS_IN_ROW * UNIFONT_GLYPH_AREA); | ||
| 390 | SDL_RenderTexture(state->renderers[rendererID], texture, &srcrect, dst); | ||
| 391 | } | ||
| 392 | return unifontGlyph[codepoint].width; | ||
| 393 | } | ||
| 394 | |||
| 395 | static void unifont_cleanup(void) | ||
| 396 | { | ||
| 397 | int i, j; | ||
| 398 | for (i = 0; i < state->num_windows; ++i) { | ||
| 399 | SDL_Renderer *renderer = state->renderers[i]; | ||
| 400 | if (state->windows[i] == NULL || !renderer) { | ||
| 401 | continue; | ||
| 402 | } | ||
| 403 | for (j = 0; j < UNIFONT_NUM_TEXTURES; j++) { | ||
| 404 | SDL_Texture *tex = unifontTexture[UNIFONT_NUM_TEXTURES * i + j]; | ||
| 405 | if (tex) { | ||
| 406 | SDL_DestroyTexture(tex); | ||
| 407 | } | ||
| 408 | } | ||
| 409 | } | ||
| 410 | |||
| 411 | for (j = 0; j < UNIFONT_NUM_TEXTURES; j++) { | ||
| 412 | unifontTextureLoaded[j] = 0; | ||
| 413 | } | ||
| 414 | |||
| 415 | SDL_free(unifontTexture); | ||
| 416 | SDL_free(unifontGlyph); | ||
| 417 | } | ||
| 418 | |||
| 419 | /* Unifont code end */ | ||
| 420 | |||
| 421 | static size_t utf8_length(unsigned char c) | ||
| 422 | { | ||
| 423 | c = (unsigned char)(0xff & c); | ||
| 424 | if (c < 0x80) { | ||
| 425 | return 1; | ||
| 426 | } else if ((c >> 5) == 0x6) { | ||
| 427 | return 2; | ||
| 428 | } else if ((c >> 4) == 0xe) { | ||
| 429 | return 3; | ||
| 430 | } else if ((c >> 3) == 0x1e) { | ||
| 431 | return 4; | ||
| 432 | } | ||
| 433 | return 0; | ||
| 434 | } | ||
| 435 | |||
| 436 | static Uint32 utf8_decode(const char *p, size_t len) | ||
| 437 | { | ||
| 438 | Uint32 codepoint = 0; | ||
| 439 | size_t i = 0; | ||
| 440 | if (!len) { | ||
| 441 | return 0; | ||
| 442 | } | ||
| 443 | |||
| 444 | for (; i < len; ++i) { | ||
| 445 | if (i == 0) { | ||
| 446 | codepoint = (0xff >> len) & *p; | ||
| 447 | } else { | ||
| 448 | codepoint <<= 6; | ||
| 449 | codepoint |= 0x3f & *p; | ||
| 450 | } | ||
| 451 | if (!*p) { | ||
| 452 | return 0; | ||
| 453 | } | ||
| 454 | p++; | ||
| 455 | } | ||
| 456 | |||
| 457 | return codepoint; | ||
| 458 | } | ||
| 459 | |||
| 460 | static WindowState *GetWindowStateForWindowID(SDL_WindowID windowID) | ||
| 461 | { | ||
| 462 | int i; | ||
| 463 | SDL_Window *window = SDL_GetWindowFromID(windowID); | ||
| 464 | |||
| 465 | for (i = 0; i < state->num_windows; ++i) { | ||
| 466 | if (windowstate[i].window == window) { | ||
| 467 | return &windowstate[i]; | ||
| 468 | } | ||
| 469 | } | ||
| 470 | return NULL; | ||
| 471 | } | ||
| 472 | |||
| 473 | static void InitInput(WindowState *ctx) | ||
| 474 | { | ||
| 475 | /* Prepare a rect for text input */ | ||
| 476 | ctx->textRect.x = ctx->textRect.y = 100.0f; | ||
| 477 | ctx->textRect.w = DEFAULT_WINDOW_WIDTH - 2 * ctx->textRect.x; | ||
| 478 | ctx->textRect.h = 50.0f; | ||
| 479 | ctx->markedRect = ctx->textRect; | ||
| 480 | |||
| 481 | ctx->text_settings = SDL_CreateProperties(); | ||
| 482 | |||
| 483 | SDL_StartTextInputWithProperties(ctx->window, ctx->text_settings); | ||
| 484 | } | ||
| 485 | |||
| 486 | |||
| 487 | static void ClearCandidates(WindowState *ctx) | ||
| 488 | { | ||
| 489 | int i; | ||
| 490 | |||
| 491 | for (i = 0; i < ctx->num_candidates; ++i) { | ||
| 492 | SDL_free(ctx->candidates[i]); | ||
| 493 | } | ||
| 494 | SDL_free(ctx->candidates); | ||
| 495 | ctx->candidates = NULL; | ||
| 496 | ctx->num_candidates = 0; | ||
| 497 | } | ||
| 498 | |||
| 499 | static void SaveCandidates(WindowState *ctx, SDL_Event *event) | ||
| 500 | { | ||
| 501 | int i; | ||
| 502 | |||
| 503 | ClearCandidates(ctx); | ||
| 504 | |||
| 505 | ctx->num_candidates = event->edit_candidates.num_candidates; | ||
| 506 | if (ctx->num_candidates > 0) { | ||
| 507 | ctx->candidates = (char **)SDL_malloc(ctx->num_candidates * sizeof(*ctx->candidates)); | ||
| 508 | if (!ctx->candidates) { | ||
| 509 | ctx->num_candidates = 0; | ||
| 510 | return; | ||
| 511 | } | ||
| 512 | for (i = 0; i < ctx->num_candidates; ++i) { | ||
| 513 | ctx->candidates[i] = SDL_strdup(event->edit_candidates.candidates[i]); | ||
| 514 | } | ||
| 515 | ctx->selected_candidate = event->edit_candidates.selected_candidate; | ||
| 516 | ctx->horizontal_candidates = event->edit_candidates.horizontal; | ||
| 517 | } | ||
| 518 | } | ||
| 519 | |||
| 520 | static void DrawCandidates(WindowState *ctx, SDL_FRect *cursorRect) | ||
| 521 | { | ||
| 522 | SDL_Renderer *renderer = ctx->renderer; | ||
| 523 | int rendererID = ctx->rendererID; | ||
| 524 | int i; | ||
| 525 | int output_w = 0, output_h = 0; | ||
| 526 | float w = 0.0f, h = 0.0f; | ||
| 527 | SDL_FRect candidatesRect, dstRect, underlineRect; | ||
| 528 | |||
| 529 | if (ctx->num_candidates == 0) { | ||
| 530 | return; | ||
| 531 | } | ||
| 532 | |||
| 533 | /* Calculate the size of the candidate list */ | ||
| 534 | for (i = 0; i < ctx->num_candidates; ++i) { | ||
| 535 | if (!ctx->candidates[i]) { | ||
| 536 | continue; | ||
| 537 | } | ||
| 538 | |||
| 539 | if (ctx->horizontal_candidates) { | ||
| 540 | const char *utext = ctx->candidates[i]; | ||
| 541 | Uint32 codepoint; | ||
| 542 | size_t len; | ||
| 543 | float advance = 0.0f; | ||
| 544 | |||
| 545 | if (i > 0) { | ||
| 546 | advance += unifont_glyph_width(' ') * UNIFONT_DRAW_SCALE; | ||
| 547 | } | ||
| 548 | while ((codepoint = utf8_decode(utext, len = utf8_length(*utext))) != 0) { | ||
| 549 | advance += unifont_glyph_width(codepoint) * UNIFONT_DRAW_SCALE; | ||
| 550 | utext += len; | ||
| 551 | } | ||
| 552 | w += advance; | ||
| 553 | h = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE; | ||
| 554 | } else { | ||
| 555 | const char *utext = ctx->candidates[i]; | ||
| 556 | Uint32 codepoint; | ||
| 557 | size_t len; | ||
| 558 | float advance = 0.0f; | ||
| 559 | |||
| 560 | while ((codepoint = utf8_decode(utext, len = utf8_length(*utext))) != 0) { | ||
| 561 | advance += unifont_glyph_width(codepoint) * UNIFONT_DRAW_SCALE; | ||
| 562 | utext += len; | ||
| 563 | } | ||
| 564 | w = SDL_max(w, advance); | ||
| 565 | if (i > 0) { | ||
| 566 | h += 2.0f; | ||
| 567 | } | ||
| 568 | h += UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE; | ||
| 569 | } | ||
| 570 | } | ||
| 571 | |||
| 572 | /* Position the candidate window */ | ||
| 573 | SDL_GetCurrentRenderOutputSize(renderer, &output_w, &output_h); | ||
| 574 | candidatesRect.x = cursorRect->x; | ||
| 575 | candidatesRect.y = cursorRect->y + cursorRect->h + 2.0f; | ||
| 576 | candidatesRect.w = 1.0f + 2.0f + w + 2.0f + 1.0f; | ||
| 577 | candidatesRect.h = 1.0f + 2.0f + h + 2.0f + 1.0f; | ||
| 578 | if ((candidatesRect.x + candidatesRect.w) > output_w) { | ||
| 579 | candidatesRect.x = (output_w - candidatesRect.w); | ||
| 580 | if (candidatesRect.x < 0.0f) { | ||
| 581 | candidatesRect.x = 0.0f; | ||
| 582 | } | ||
| 583 | } | ||
| 584 | |||
| 585 | /* Draw the candidate background */ | ||
| 586 | SDL_SetRenderDrawColor(renderer, 0xAA, 0xAA, 0xAA, 0xFF); | ||
| 587 | SDL_RenderFillRect(renderer, &candidatesRect); | ||
| 588 | SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF); | ||
| 589 | SDL_RenderRect(renderer, &candidatesRect); | ||
| 590 | |||
| 591 | /* Draw the candidates */ | ||
| 592 | dstRect.x = candidatesRect.x + 3.0f; | ||
| 593 | dstRect.y = candidatesRect.y + 3.0f; | ||
| 594 | for (i = 0; i < ctx->num_candidates; ++i) { | ||
| 595 | if (!ctx->candidates[i]) { | ||
| 596 | continue; | ||
| 597 | } | ||
| 598 | |||
| 599 | dstRect.w = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE; | ||
| 600 | dstRect.h = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE; | ||
| 601 | |||
| 602 | if (ctx->horizontal_candidates) { | ||
| 603 | const char *utext = ctx->candidates[i]; | ||
| 604 | Uint32 codepoint; | ||
| 605 | size_t len; | ||
| 606 | float start; | ||
| 607 | |||
| 608 | if (i > 0) { | ||
| 609 | dstRect.x += unifont_draw_glyph(' ', rendererID, &dstRect) * UNIFONT_DRAW_SCALE; | ||
| 610 | } | ||
| 611 | |||
| 612 | start = dstRect.x + 2 * unifont_glyph_width(' ') * UNIFONT_DRAW_SCALE; | ||
| 613 | while ((codepoint = utf8_decode(utext, len = utf8_length(*utext))) != 0) { | ||
| 614 | dstRect.x += unifont_draw_glyph(codepoint, rendererID, &dstRect) * UNIFONT_DRAW_SCALE; | ||
| 615 | utext += len; | ||
| 616 | } | ||
| 617 | |||
| 618 | if (i == ctx->selected_candidate) { | ||
| 619 | underlineRect.x = start; | ||
| 620 | underlineRect.y = dstRect.y + dstRect.h - 2; | ||
| 621 | underlineRect.h = 2; | ||
| 622 | underlineRect.w = dstRect.x - start; | ||
| 623 | |||
| 624 | SDL_SetRenderDrawColor(renderer, lineColor.r, lineColor.g, lineColor.b, lineColor.a); | ||
| 625 | SDL_RenderFillRect(renderer, &underlineRect); | ||
| 626 | } | ||
| 627 | } else { | ||
| 628 | const char *utext = ctx->candidates[i]; | ||
| 629 | Uint32 codepoint; | ||
| 630 | size_t len; | ||
| 631 | float start; | ||
| 632 | |||
| 633 | dstRect.x = candidatesRect.x + 3.0f; | ||
| 634 | |||
| 635 | start = dstRect.x + 2 * unifont_glyph_width(' ') * UNIFONT_DRAW_SCALE; | ||
| 636 | while ((codepoint = utf8_decode(utext, len = utf8_length(*utext))) != 0) { | ||
| 637 | dstRect.x += unifont_draw_glyph(codepoint, rendererID, &dstRect) * UNIFONT_DRAW_SCALE; | ||
| 638 | utext += len; | ||
| 639 | } | ||
| 640 | |||
| 641 | if (i == ctx->selected_candidate) { | ||
| 642 | underlineRect.x = start; | ||
| 643 | underlineRect.y = dstRect.y + dstRect.h - 2; | ||
| 644 | underlineRect.h = 2; | ||
| 645 | underlineRect.w = dstRect.x - start; | ||
| 646 | |||
| 647 | SDL_SetRenderDrawColor(renderer, lineColor.r, lineColor.g, lineColor.b, lineColor.a); | ||
| 648 | SDL_RenderFillRect(renderer, &underlineRect); | ||
| 649 | } | ||
| 650 | |||
| 651 | if (i > 0) { | ||
| 652 | dstRect.y += 2.0f; | ||
| 653 | } | ||
| 654 | dstRect.y += dstRect.h; | ||
| 655 | } | ||
| 656 | } | ||
| 657 | } | ||
| 658 | |||
| 659 | static void UpdateTextInputArea(WindowState *ctx, const SDL_FRect *cursorRect) | ||
| 660 | { | ||
| 661 | SDL_Rect rect; | ||
| 662 | int cursor_offset = (int)(cursorRect->x - ctx->textRect.x); | ||
| 663 | |||
| 664 | rect.x = (int)ctx->textRect.x; | ||
| 665 | rect.y = (int)ctx->textRect.y; | ||
| 666 | rect.w = (int)ctx->textRect.w; | ||
| 667 | rect.h = (int)ctx->textRect.h; | ||
| 668 | SDL_SetTextInputArea(ctx->window, &rect, cursor_offset); | ||
| 669 | } | ||
| 670 | |||
| 671 | static void CleanupVideo(void) | ||
| 672 | { | ||
| 673 | int i; | ||
| 674 | |||
| 675 | for (i = 0; i < state->num_windows; ++i) { | ||
| 676 | WindowState *ctx = &windowstate[i]; | ||
| 677 | |||
| 678 | SDL_StopTextInput(ctx->window); | ||
| 679 | ClearCandidates(ctx); | ||
| 680 | SDL_DestroyProperties(ctx->text_settings); | ||
| 681 | } | ||
| 682 | unifont_cleanup(); | ||
| 683 | } | ||
| 684 | |||
| 685 | static void DrawSettingsButton(WindowState *ctx) | ||
| 686 | { | ||
| 687 | SDL_Renderer *renderer = ctx->renderer; | ||
| 688 | |||
| 689 | SDL_RenderTexture(renderer, ctx->settings_icon, NULL, &ctx->settings_rect); | ||
| 690 | } | ||
| 691 | |||
| 692 | static void ToggleSettings(WindowState *ctx) | ||
| 693 | { | ||
| 694 | if (ctx->settings_visible) { | ||
| 695 | ctx->settings_visible = false; | ||
| 696 | SDL_StartTextInputWithProperties(ctx->window, ctx->text_settings); | ||
| 697 | } else { | ||
| 698 | SDL_StopTextInput(ctx->window); | ||
| 699 | ctx->settings_visible = true; | ||
| 700 | } | ||
| 701 | } | ||
| 702 | |||
| 703 | static int GetDefaultSetting(SDL_PropertiesID props, const char *setting) | ||
| 704 | { | ||
| 705 | if (SDL_strcmp(setting, SDL_PROP_TEXTINPUT_TYPE_NUMBER) == 0) { | ||
| 706 | return SDL_TEXTINPUT_TYPE_TEXT; | ||
| 707 | } | ||
| 708 | |||
| 709 | if (SDL_strcmp(setting, SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER) == 0) { | ||
| 710 | switch (SDL_GetNumberProperty(props, SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT)) { | ||
| 711 | case SDL_TEXTINPUT_TYPE_TEXT: | ||
| 712 | return SDL_CAPITALIZE_SENTENCES; | ||
| 713 | case SDL_TEXTINPUT_TYPE_TEXT_NAME: | ||
| 714 | return SDL_CAPITALIZE_WORDS; | ||
| 715 | default: | ||
| 716 | return SDL_CAPITALIZE_NONE; | ||
| 717 | } | ||
| 718 | } | ||
| 719 | |||
| 720 | if (SDL_strcmp(setting, SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN) == 0) { | ||
| 721 | return true; | ||
| 722 | } | ||
| 723 | |||
| 724 | if (SDL_strcmp(setting, SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN) == 0) { | ||
| 725 | return true; | ||
| 726 | } | ||
| 727 | |||
| 728 | SDL_assert(!"Unknown setting"); | ||
| 729 | return 0; | ||
| 730 | } | ||
| 731 | |||
| 732 | static void DrawSettings(WindowState *ctx) | ||
| 733 | { | ||
| 734 | SDL_Renderer *renderer = ctx->renderer; | ||
| 735 | SDL_FRect checkbox; | ||
| 736 | int i; | ||
| 737 | |||
| 738 | checkbox.x = MARGIN; | ||
| 739 | checkbox.y = MARGIN; | ||
| 740 | checkbox.w = (float)FONT_CHARACTER_SIZE; | ||
| 741 | checkbox.h = (float)FONT_CHARACTER_SIZE; | ||
| 742 | |||
| 743 | for (i = 0; i < SDL_arraysize(settings); ++i) { | ||
| 744 | if (settings[i].setting) { | ||
| 745 | int value = (int)SDL_GetNumberProperty(ctx->text_settings, settings[i].setting, GetDefaultSetting(ctx->text_settings, settings[i].setting)); | ||
| 746 | if (value == settings[i].value) { | ||
| 747 | SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255); | ||
| 748 | SDL_RenderFillRect(renderer, &checkbox); | ||
| 749 | } | ||
| 750 | SDL_SetRenderDrawColor(renderer, backColor.r, backColor.g, backColor.b, backColor.a); | ||
| 751 | SDL_RenderRect(renderer, &checkbox); | ||
| 752 | SDLTest_DrawString(renderer, checkbox.x + checkbox.w + 8.0f, checkbox.y, settings[i].label); | ||
| 753 | } | ||
| 754 | checkbox.y += LINE_HEIGHT; | ||
| 755 | } | ||
| 756 | } | ||
| 757 | |||
| 758 | static void ClickSettings(WindowState *ctx, float x, float y) | ||
| 759 | { | ||
| 760 | int setting = (int)SDL_floorf((y - MARGIN) / LINE_HEIGHT); | ||
| 761 | if (setting >= 0 && setting < SDL_arraysize(settings)) { | ||
| 762 | SDL_SetNumberProperty(ctx->text_settings, settings[setting].setting, settings[setting].value); | ||
| 763 | } | ||
| 764 | } | ||
| 765 | |||
| 766 | static void RedrawWindow(WindowState *ctx) | ||
| 767 | { | ||
| 768 | SDL_Renderer *renderer = ctx->renderer; | ||
| 769 | int rendererID = ctx->rendererID; | ||
| 770 | SDL_FRect drawnTextRect, cursorRect, underlineRect; | ||
| 771 | char text[MAX_TEXT_LENGTH]; | ||
| 772 | |||
| 773 | DrawSettingsButton(ctx); | ||
| 774 | |||
| 775 | if (ctx->settings_visible) { | ||
| 776 | DrawSettings(ctx); | ||
| 777 | return; | ||
| 778 | } | ||
| 779 | |||
| 780 | /* Hide the text if it's a password */ | ||
| 781 | switch ((SDL_TextInputType)SDL_GetNumberProperty(ctx->text_settings, SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT)) { | ||
| 782 | case SDL_TEXTINPUT_TYPE_TEXT_PASSWORD_HIDDEN: | ||
| 783 | case SDL_TEXTINPUT_TYPE_NUMBER_PASSWORD_HIDDEN: { | ||
| 784 | size_t len = SDL_utf8strlen(ctx->text); | ||
| 785 | SDL_memset(text, '*', len); | ||
| 786 | text[len] = '\0'; | ||
| 787 | break; | ||
| 788 | } | ||
| 789 | default: | ||
| 790 | SDL_strlcpy(text, ctx->text, sizeof(text)); | ||
| 791 | break; | ||
| 792 | } | ||
| 793 | |||
| 794 | SDL_SetRenderDrawColor(renderer, backColor.r, backColor.g, backColor.b, backColor.a); | ||
| 795 | SDL_RenderFillRect(renderer, &ctx->textRect); | ||
| 796 | |||
| 797 | /* Initialize the drawn text rectangle for the cursor */ | ||
| 798 | drawnTextRect.x = ctx->textRect.x; | ||
| 799 | drawnTextRect.y = ctx->textRect.y + (ctx->textRect.h - UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE) / 2; | ||
| 800 | drawnTextRect.w = 0.0f; | ||
| 801 | drawnTextRect.h = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE; | ||
| 802 | |||
| 803 | if (text[0]) { | ||
| 804 | char *utext = text; | ||
| 805 | Uint32 codepoint; | ||
| 806 | size_t len; | ||
| 807 | SDL_FRect dstrect; | ||
| 808 | |||
| 809 | dstrect.x = ctx->textRect.x; | ||
| 810 | dstrect.y = ctx->textRect.y + (ctx->textRect.h - UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE) / 2; | ||
| 811 | dstrect.w = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE; | ||
| 812 | dstrect.h = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE; | ||
| 813 | drawnTextRect.y = dstrect.y; | ||
| 814 | drawnTextRect.h = dstrect.h; | ||
| 815 | |||
| 816 | while ((codepoint = utf8_decode(utext, len = utf8_length(*utext))) != 0) { | ||
| 817 | float advance = unifont_draw_glyph(codepoint, rendererID, &dstrect) * UNIFONT_DRAW_SCALE; | ||
| 818 | dstrect.x += advance; | ||
| 819 | drawnTextRect.w += advance; | ||
| 820 | utext += len; | ||
| 821 | } | ||
| 822 | } | ||
| 823 | |||
| 824 | /* The marked text rectangle is the text area that hasn't been filled by committed text */ | ||
| 825 | ctx->markedRect.x = ctx->textRect.x + drawnTextRect.w; | ||
| 826 | ctx->markedRect.w = ctx->textRect.w - drawnTextRect.w; | ||
| 827 | |||
| 828 | /* Update the drawn text rectangle for composition text, after the committed text */ | ||
| 829 | drawnTextRect.x += drawnTextRect.w; | ||
| 830 | drawnTextRect.w = 0; | ||
| 831 | |||
| 832 | /* Set the cursor to the new location, we'll update it as we go, below */ | ||
| 833 | cursorRect = drawnTextRect; | ||
| 834 | cursorRect.w = 2; | ||
| 835 | cursorRect.h = drawnTextRect.h; | ||
| 836 | |||
| 837 | if (ctx->markedText[0]) { | ||
| 838 | int i = 0; | ||
| 839 | char *utext = ctx->markedText; | ||
| 840 | Uint32 codepoint; | ||
| 841 | size_t len; | ||
| 842 | SDL_FRect dstrect; | ||
| 843 | |||
| 844 | dstrect.x = drawnTextRect.x; | ||
| 845 | dstrect.y = ctx->textRect.y + (ctx->textRect.h - UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE) / 2; | ||
| 846 | dstrect.w = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE; | ||
| 847 | dstrect.h = UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE; | ||
| 848 | drawnTextRect.y = dstrect.y; | ||
| 849 | drawnTextRect.h = dstrect.h; | ||
| 850 | |||
| 851 | while ((codepoint = utf8_decode(utext, len = utf8_length(*utext))) != 0) { | ||
| 852 | float advance = unifont_draw_glyph(codepoint, rendererID, &dstrect) * UNIFONT_DRAW_SCALE; | ||
| 853 | dstrect.x += advance; | ||
| 854 | drawnTextRect.w += advance; | ||
| 855 | if (i < ctx->cursor) { | ||
| 856 | cursorRect.x += advance; | ||
| 857 | } | ||
| 858 | i++; | ||
| 859 | utext += len; | ||
| 860 | } | ||
| 861 | |||
| 862 | if (ctx->cursor_length > 0) { | ||
| 863 | cursorRect.w = ctx->cursor_length * UNIFONT_GLYPH_SIZE * UNIFONT_DRAW_SCALE; | ||
| 864 | } | ||
| 865 | |||
| 866 | cursorRect.y = drawnTextRect.y; | ||
| 867 | cursorRect.h = drawnTextRect.h; | ||
| 868 | |||
| 869 | underlineRect = ctx->markedRect; | ||
| 870 | underlineRect.y = drawnTextRect.y + drawnTextRect.h - 2; | ||
| 871 | underlineRect.h = 2; | ||
| 872 | underlineRect.w = drawnTextRect.w; | ||
| 873 | |||
| 874 | SDL_SetRenderDrawColor(renderer, lineColor.r, lineColor.g, lineColor.b, lineColor.a); | ||
| 875 | SDL_RenderFillRect(renderer, &underlineRect); | ||
| 876 | } | ||
| 877 | |||
| 878 | /* Draw the cursor */ | ||
| 879 | Uint64 now = SDL_GetTicks(); | ||
| 880 | if ((now - ctx->last_cursor_change) >= CURSOR_BLINK_INTERVAL_MS) { | ||
| 881 | ctx->cursor_visible = !ctx->cursor_visible; | ||
| 882 | ctx->last_cursor_change = now; | ||
| 883 | } | ||
| 884 | if (ctx->cursor_length > 0) { | ||
| 885 | /* We'll show a highlight */ | ||
| 886 | SDL_SetRenderDrawBlendMode(renderer, highlight_mode); | ||
| 887 | SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); | ||
| 888 | SDL_RenderFillRect(renderer, &cursorRect); | ||
| 889 | SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); | ||
| 890 | } else if (ctx->cursor_visible) { | ||
| 891 | SDL_SetRenderDrawColor(renderer, lineColor.r, lineColor.g, lineColor.b, lineColor.a); | ||
| 892 | SDL_RenderFillRect(renderer, &cursorRect); | ||
| 893 | } | ||
| 894 | |||
| 895 | /* Draw the candidates */ | ||
| 896 | DrawCandidates(ctx, &cursorRect); | ||
| 897 | |||
| 898 | /* Update the area used to draw composition UI */ | ||
| 899 | UpdateTextInputArea(ctx, &cursorRect); | ||
| 900 | } | ||
| 901 | |||
| 902 | static void Redraw(void) | ||
| 903 | { | ||
| 904 | int i; | ||
| 905 | for (i = 0; i < state->num_windows; ++i) { | ||
| 906 | SDL_Renderer *renderer = state->renderers[i]; | ||
| 907 | if (state->windows[i] == NULL) { | ||
| 908 | continue; | ||
| 909 | } | ||
| 910 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); | ||
| 911 | SDL_RenderClear(renderer); | ||
| 912 | |||
| 913 | RedrawWindow(&windowstate[i]); | ||
| 914 | |||
| 915 | SDL_RenderPresent(renderer); | ||
| 916 | } | ||
| 917 | } | ||
| 918 | |||
| 919 | int main(int argc, char *argv[]) | ||
| 920 | { | ||
| 921 | bool render_composition = false; | ||
| 922 | bool render_candidates = false; | ||
| 923 | int i, done; | ||
| 924 | SDL_Event event; | ||
| 925 | char *fontname = NULL; | ||
| 926 | |||
| 927 | /* Initialize test framework */ | ||
| 928 | state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); | ||
| 929 | if (!state) { | ||
| 930 | return 1; | ||
| 931 | } | ||
| 932 | |||
| 933 | /* Parse commandline */ | ||
| 934 | for (i = 1; i < argc;) { | ||
| 935 | int consumed; | ||
| 936 | |||
| 937 | consumed = SDLTest_CommonArg(state, i); | ||
| 938 | if (SDL_strcmp(argv[i], "--font") == 0) { | ||
| 939 | if (*argv[i + 1]) { | ||
| 940 | fontname = argv[i + 1]; | ||
| 941 | consumed = 2; | ||
| 942 | } | ||
| 943 | } else if (SDL_strcmp(argv[i], "--render-composition") == 0) { | ||
| 944 | render_composition = true; | ||
| 945 | consumed = 1; | ||
| 946 | } else if (SDL_strcmp(argv[i], "--render-candidates") == 0) { | ||
| 947 | render_candidates = true; | ||
| 948 | consumed = 1; | ||
| 949 | } | ||
| 950 | if (consumed <= 0) { | ||
| 951 | static const char *options[] = { "[--font fontfile] [--render-composition] [--render-candidates]", NULL }; | ||
| 952 | SDLTest_CommonLogUsage(state, argv[0], options); | ||
| 953 | return 1; | ||
| 954 | } | ||
| 955 | |||
| 956 | i += consumed; | ||
| 957 | } | ||
| 958 | |||
| 959 | if (render_composition && render_candidates) { | ||
| 960 | SDL_SetHint(SDL_HINT_IME_IMPLEMENTED_UI, "composition,candidates"); | ||
| 961 | } else if (render_composition) { | ||
| 962 | SDL_SetHint(SDL_HINT_IME_IMPLEMENTED_UI, "composition"); | ||
| 963 | } else if (render_candidates) { | ||
| 964 | SDL_SetHint(SDL_HINT_IME_IMPLEMENTED_UI, "candidates"); | ||
| 965 | } | ||
| 966 | |||
| 967 | if (!SDLTest_CommonInit(state)) { | ||
| 968 | return 2; | ||
| 969 | } | ||
| 970 | |||
| 971 | windowstate = (WindowState *)SDL_calloc(state->num_windows, sizeof(*windowstate)); | ||
| 972 | if (!windowstate) { | ||
| 973 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't allocate window state: %s", SDL_GetError()); | ||
| 974 | return -1; | ||
| 975 | } | ||
| 976 | |||
| 977 | fontname = GetResourceFilename(fontname, DEFAULT_FONT); | ||
| 978 | |||
| 979 | if (unifont_init(fontname) < 0) { | ||
| 980 | return -1; | ||
| 981 | } | ||
| 982 | |||
| 983 | SDL_Log("Using font: %s", fontname); | ||
| 984 | |||
| 985 | /* Initialize window state */ | ||
| 986 | for (i = 0; i < state->num_windows; ++i) { | ||
| 987 | WindowState *ctx = &windowstate[i]; | ||
| 988 | SDL_Window *window = state->windows[i]; | ||
| 989 | SDL_Renderer *renderer = state->renderers[i]; | ||
| 990 | int icon_w = 0, icon_h = 0; | ||
| 991 | |||
| 992 | SDL_SetRenderLogicalPresentation(renderer, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); | ||
| 993 | |||
| 994 | ctx->window = window; | ||
| 995 | ctx->renderer = renderer; | ||
| 996 | ctx->rendererID = i; | ||
| 997 | ctx->settings_icon = LoadTexture(renderer, "icon.bmp", true, &icon_w, &icon_h); | ||
| 998 | ctx->settings_rect.x = (float)WINDOW_WIDTH - icon_w - MARGIN; | ||
| 999 | ctx->settings_rect.y = MARGIN; | ||
| 1000 | ctx->settings_rect.w = (float)icon_w; | ||
| 1001 | ctx->settings_rect.h = (float)icon_h; | ||
| 1002 | |||
| 1003 | InitInput(ctx); | ||
| 1004 | |||
| 1005 | SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); | ||
| 1006 | SDL_SetRenderDrawColor(renderer, 0xA0, 0xA0, 0xA0, 0xFF); | ||
| 1007 | SDL_RenderClear(renderer); | ||
| 1008 | } | ||
| 1009 | highlight_mode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR, | ||
| 1010 | SDL_BLENDFACTOR_ZERO, | ||
| 1011 | SDL_BLENDOPERATION_ADD, | ||
| 1012 | SDL_BLENDFACTOR_ZERO, | ||
| 1013 | SDL_BLENDFACTOR_ONE, | ||
| 1014 | SDL_BLENDOPERATION_ADD); | ||
| 1015 | |||
| 1016 | /* Main render loop */ | ||
| 1017 | done = 0; | ||
| 1018 | while (!done) { | ||
| 1019 | /* Check for events */ | ||
| 1020 | while (SDL_PollEvent(&event)) { | ||
| 1021 | SDLTest_CommonEvent(state, &event, &done); | ||
| 1022 | switch (event.type) { | ||
| 1023 | case SDL_EVENT_MOUSE_BUTTON_UP: { | ||
| 1024 | SDL_FPoint point; | ||
| 1025 | WindowState *ctx = GetWindowStateForWindowID(event.button.windowID); | ||
| 1026 | if (!ctx) { | ||
| 1027 | break; | ||
| 1028 | } | ||
| 1029 | |||
| 1030 | SDL_ConvertEventToRenderCoordinates(ctx->renderer, &event); | ||
| 1031 | point.x = event.button.x; | ||
| 1032 | point.y = event.button.y; | ||
| 1033 | if (SDL_PointInRectFloat(&point, &ctx->settings_rect)) { | ||
| 1034 | ToggleSettings(ctx); | ||
| 1035 | } else if (ctx->settings_visible) { | ||
| 1036 | ClickSettings(ctx, point.x, point.y); | ||
| 1037 | } | ||
| 1038 | break; | ||
| 1039 | } | ||
| 1040 | case SDL_EVENT_KEY_DOWN: { | ||
| 1041 | WindowState *ctx = GetWindowStateForWindowID(event.key.windowID); | ||
| 1042 | if (!ctx) { | ||
| 1043 | break; | ||
| 1044 | } | ||
| 1045 | |||
| 1046 | switch (event.key.key) { | ||
| 1047 | case SDLK_RETURN: | ||
| 1048 | ctx->text[0] = 0x00; | ||
| 1049 | break; | ||
| 1050 | case SDLK_BACKSPACE: | ||
| 1051 | /* Only delete text if not in editing mode. */ | ||
| 1052 | if (!ctx->markedText[0]) { | ||
| 1053 | size_t textlen = SDL_strlen(ctx->text); | ||
| 1054 | |||
| 1055 | do { | ||
| 1056 | if (textlen == 0) { | ||
| 1057 | break; | ||
| 1058 | } | ||
| 1059 | if (!(ctx->text[textlen - 1] & 0x80)) { | ||
| 1060 | /* One byte */ | ||
| 1061 | ctx->text[textlen - 1] = 0x00; | ||
| 1062 | break; | ||
| 1063 | } | ||
| 1064 | if ((ctx->text[textlen - 1] & 0xC0) == 0x80) { | ||
| 1065 | /* Byte from the multibyte sequence */ | ||
| 1066 | ctx->text[textlen - 1] = 0x00; | ||
| 1067 | textlen--; | ||
| 1068 | } | ||
| 1069 | if ((ctx->text[textlen - 1] & 0xC0) == 0xC0) { | ||
| 1070 | /* First byte of multibyte sequence */ | ||
| 1071 | ctx->text[textlen - 1] = 0x00; | ||
| 1072 | break; | ||
| 1073 | } | ||
| 1074 | } while (1); | ||
| 1075 | } | ||
| 1076 | break; | ||
| 1077 | default: | ||
| 1078 | break; | ||
| 1079 | } | ||
| 1080 | |||
| 1081 | if (done) { | ||
| 1082 | break; | ||
| 1083 | } | ||
| 1084 | |||
| 1085 | SDL_Log("Keyboard: scancode 0x%08X = %s, keycode 0x%08" SDL_PRIX32 " = %s", | ||
| 1086 | event.key.scancode, | ||
| 1087 | SDL_GetScancodeName(event.key.scancode), | ||
| 1088 | SDL_static_cast(Uint32, event.key.key), | ||
| 1089 | SDL_GetKeyName(event.key.key)); | ||
| 1090 | break; | ||
| 1091 | } | ||
| 1092 | case SDL_EVENT_TEXT_INPUT: { | ||
| 1093 | WindowState *ctx = GetWindowStateForWindowID(event.text.windowID); | ||
| 1094 | if (!ctx) { | ||
| 1095 | break; | ||
| 1096 | } | ||
| 1097 | |||
| 1098 | if (event.text.text[0] == '\0' || event.text.text[0] == '\n' || ctx->markedRect.w < 0) { | ||
| 1099 | break; | ||
| 1100 | } | ||
| 1101 | |||
| 1102 | SDL_Log("Keyboard: text input \"%s\"", event.text.text); | ||
| 1103 | |||
| 1104 | if (SDL_strlen(ctx->text) + SDL_strlen(event.text.text) < sizeof(ctx->text)) { | ||
| 1105 | SDL_strlcat(ctx->text, event.text.text, sizeof(ctx->text)); | ||
| 1106 | } | ||
| 1107 | |||
| 1108 | SDL_Log("text inputted: %s", ctx->text); | ||
| 1109 | |||
| 1110 | /* After text inputted, we can clear up markedText because it */ | ||
| 1111 | /* is committed */ | ||
| 1112 | ctx->markedText[0] = 0; | ||
| 1113 | break; | ||
| 1114 | } | ||
| 1115 | case SDL_EVENT_TEXT_EDITING: { | ||
| 1116 | WindowState *ctx = GetWindowStateForWindowID(event.edit.windowID); | ||
| 1117 | if (!ctx) { | ||
| 1118 | break; | ||
| 1119 | } | ||
| 1120 | |||
| 1121 | SDL_Log("text editing \"%s\", selected range (%" SDL_PRIs32 ", %" SDL_PRIs32 ")", | ||
| 1122 | event.edit.text, event.edit.start, event.edit.length); | ||
| 1123 | |||
| 1124 | SDL_strlcpy(ctx->markedText, event.edit.text, sizeof(ctx->markedText)); | ||
| 1125 | ctx->cursor = event.edit.start; | ||
| 1126 | ctx->cursor_length = event.edit.length; | ||
| 1127 | break; | ||
| 1128 | } | ||
| 1129 | case SDL_EVENT_TEXT_EDITING_CANDIDATES: { | ||
| 1130 | WindowState *ctx = GetWindowStateForWindowID(event.edit.windowID); | ||
| 1131 | if (!ctx) { | ||
| 1132 | break; | ||
| 1133 | } | ||
| 1134 | |||
| 1135 | SDL_Log("text candidates:"); | ||
| 1136 | for (i = 0; i < event.edit_candidates.num_candidates; ++i) { | ||
| 1137 | SDL_Log("%c%s", i == event.edit_candidates.selected_candidate ? '>' : ' ', event.edit_candidates.candidates[i]); | ||
| 1138 | } | ||
| 1139 | |||
| 1140 | ClearCandidates(ctx); | ||
| 1141 | SaveCandidates(ctx, &event); | ||
| 1142 | break; | ||
| 1143 | } | ||
| 1144 | default: | ||
| 1145 | break; | ||
| 1146 | } | ||
| 1147 | } | ||
| 1148 | |||
| 1149 | Redraw(); | ||
| 1150 | } | ||
| 1151 | SDL_free(fontname); | ||
| 1152 | CleanupVideo(); | ||
| 1153 | SDLTest_CommonQuit(state); | ||
| 1154 | return 0; | ||
| 1155 | } | ||
