diff options
author | 3gg <3gg@shellblade.net> | 2025-08-30 16:53:58 -0700 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2025-08-30 16:53:58 -0700 |
commit | 6aaedb813fa11ba0679c3051bc2eb28646b9506c (patch) | |
tree | 34acbfc9840e02cb4753e6306ea7ce978bf8b58e /src/contrib/SDL-3.2.20/test/testime.c | |
parent | 8f228ade99dd3d4c8da9b78ade1815c9adf85c8f (diff) |
Update to SDL3
Diffstat (limited to 'src/contrib/SDL-3.2.20/test/testime.c')
-rw-r--r-- | src/contrib/SDL-3.2.20/test/testime.c | 1155 |
1 files changed, 1155 insertions, 0 deletions
diff --git a/src/contrib/SDL-3.2.20/test/testime.c b/src/contrib/SDL-3.2.20/test/testime.c new file mode 100644 index 0000000..ea56d3c --- /dev/null +++ b/src/contrib/SDL-3.2.20/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 | } | ||