mirror of
https://github.com/kichikuou/xsystem35-sdl2.git
synced 2026-09-30 02:38:10 +03:00
menu_sdl: Implement the string-input dialog
The text field is hand-drawn and IME-aware: SDL_TEXTINPUT / SDL_TEXTEDITING / BACKSPACE are handled directly (mirroring editor.c) instead of using a microui textbox.
This commit is contained in:
+172
-1
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
@@ -32,6 +33,7 @@
|
||||
#include "gfx_private.h"
|
||||
#include "msgskip.h"
|
||||
#include "modal.h"
|
||||
#include "utfsjis.h"
|
||||
#ifdef _WIN32
|
||||
#include "win/dialog.h"
|
||||
#endif
|
||||
@@ -157,11 +159,180 @@ void menu_resetmenu_open(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// The string-input dialog.
|
||||
// The input field is not a microui textbox: to support IME (SDL_TEXTEDITING)
|
||||
// composition we keep our own buffer (str_buf) and feed it from the handler,
|
||||
// mirroring editor.c.
|
||||
struct string_state {
|
||||
modal base;
|
||||
INPUTSTRING_PARAM *param;
|
||||
char composing[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; // IME preedit text
|
||||
bool done;
|
||||
bool accepted;
|
||||
};
|
||||
|
||||
// Heap buffer holding the string dialog's edited text. It must outlive the
|
||||
// modal loop because the caller reads p->newstring after menu_inputstring()
|
||||
// returns; it is freed and reallocated on the next call.
|
||||
static char *str_buf;
|
||||
|
||||
// Append `add` to str_buf, truncating so the total stays within
|
||||
// st->param->max characters (counted as UTF-8 code points).
|
||||
static void str_append(struct string_state *st, const char *add) {
|
||||
int room = st->param->max;
|
||||
for (const char *p = str_buf; *p; p = advance_char(p, UTF8))
|
||||
room--;
|
||||
const char *end = add;
|
||||
while (room > 0 && *end) {
|
||||
end = advance_char(end, UTF8);
|
||||
room--;
|
||||
}
|
||||
strncat(str_buf, add, end - add);
|
||||
}
|
||||
|
||||
// Delete the last UTF-8 character of str_buf.
|
||||
static void str_backspace(void) {
|
||||
if (!*str_buf)
|
||||
return;
|
||||
char *p = str_buf + strlen(str_buf) - 1;
|
||||
while (p > str_buf && UTF8_TRAIL_BYTE(*p))
|
||||
p--;
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
static bool menu_string_handler(const SDL_Event *e, modal *modal) {
|
||||
struct string_state *st = (struct string_state *)modal;
|
||||
switch (e->type) {
|
||||
case SDL_KEYDOWN:
|
||||
// While an IME composition is active, let it consume the key.
|
||||
if (!*st->composing) {
|
||||
switch (e->key.keysym.sym) {
|
||||
case SDLK_RETURN:
|
||||
st->accepted = true;
|
||||
st->done = true;
|
||||
break;
|
||||
case SDLK_ESCAPE:
|
||||
st->base.cancelled = true;
|
||||
break;
|
||||
case SDLK_BACKSPACE:
|
||||
str_backspace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true; // never forward keys to microui (there is no textbox)
|
||||
case SDL_TEXTINPUT:
|
||||
str_append(st, e->text.text);
|
||||
st->composing[0] = '\0';
|
||||
return true;
|
||||
case SDL_TEXTEDITING:
|
||||
strncpy(st->composing, e->edit.text, sizeof(st->composing) - 1);
|
||||
st->composing[sizeof(st->composing) - 1] = '\0';
|
||||
return true;
|
||||
}
|
||||
return modal_default_handler(e, modal);
|
||||
}
|
||||
|
||||
// Tell SDL where the text input is, so the IME candidate window is positioned
|
||||
// near the field. `box` is in logical (view) coordinates; convert to window
|
||||
// pixels for SDL_SetTextInputRect.
|
||||
static void set_text_input_rect(mu_Rect box) {
|
||||
SDL_Rect r = { box.x, box.y, box.w, box.h };
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
||||
int x2, y2;
|
||||
SDL_RenderLogicalToWindow(gfx_renderer, box.x, box.y, &r.x, &r.y);
|
||||
SDL_RenderLogicalToWindow(gfx_renderer, box.x + box.w, box.y + box.h, &x2, &y2);
|
||||
r.w = x2 - r.x;
|
||||
r.h = y2 - r.y;
|
||||
#endif
|
||||
SDL_SetTextInputRect(&r);
|
||||
}
|
||||
|
||||
// Draw the IME-aware text field (committed text, preedit with underline, caret).
|
||||
static void draw_string_field(mu_Context *ctx, mu_Rect box, const struct string_state *st) {
|
||||
mu_draw_rect(ctx, box, ctx->style->colors[MU_COLOR_BASE]);
|
||||
mu_draw_box(ctx, box, ctx->style->colors[MU_COLOR_BORDER]);
|
||||
|
||||
mu_Font font = ctx->style->font;
|
||||
mu_Color col = ctx->style->colors[MU_COLOR_TEXT];
|
||||
int th = ctx->text_height(font);
|
||||
int x = box.x + ctx->style->padding;
|
||||
int y = box.y + (box.h - th) / 2;
|
||||
|
||||
mu_push_clip_rect(ctx, box);
|
||||
if (*str_buf) {
|
||||
mu_draw_text(ctx, font, str_buf, -1, mu_vec2(x, y), col);
|
||||
x += ctx->text_width(font, str_buf, -1);
|
||||
}
|
||||
if (*st->composing) {
|
||||
mu_draw_text(ctx, font, st->composing, -1, mu_vec2(x, y), col);
|
||||
int cw = ctx->text_width(font, st->composing, -1);
|
||||
mu_draw_rect(ctx, mu_rect(x, y + th, cw, 1), col); // preedit underline
|
||||
x += cw;
|
||||
}
|
||||
mu_draw_rect(ctx, mu_rect(x, y, 1, th), col); // caret
|
||||
mu_pop_clip_rect(ctx);
|
||||
|
||||
set_text_input_rect(box);
|
||||
}
|
||||
|
||||
static bool inputstring_build(mu_Context *ctx, modal *modal) {
|
||||
struct string_state *st = (struct string_state *)modal;
|
||||
const char *title = (st->param->title && *st->param->title)
|
||||
? st->param->title : _("Enter a string");
|
||||
int row_h = ctx->text_height(ctx->style->font) + ctx->style->padding * 2;
|
||||
int w = 320;
|
||||
int h = 3 * (row_h + ctx->style->spacing) + ctx->style->padding * 2
|
||||
+ ctx->style->title_height;
|
||||
mu_Rect r = mu_rect((view_w - w) / 2, (view_h - h) / 2, w, h);
|
||||
|
||||
if (mu_begin_window_ex(ctx, title, r,
|
||||
MU_OPT_NORESIZE | MU_OPT_NOCLOSE | MU_OPT_NOSCROLL)) {
|
||||
mu_layout_row(ctx, 1, (int[]){ -1 }, 0);
|
||||
|
||||
char info[64];
|
||||
snprintf(info, sizeof(info), _("Up to %d characters"), st->param->max);
|
||||
mu_label(ctx, info);
|
||||
|
||||
draw_string_field(ctx, mu_layout_next(ctx), st);
|
||||
|
||||
int content = w - ctx->style->padding * 2;
|
||||
int half = (content - ctx->style->spacing) / 2;
|
||||
mu_layout_row(ctx, 2, (int[]){ half, -1 }, 0);
|
||||
if (mu_button(ctx, _("OK"))) {
|
||||
st->accepted = true;
|
||||
st->done = true;
|
||||
}
|
||||
if (mu_button(ctx, _("Cancel"))) {
|
||||
st->accepted = false;
|
||||
st->done = true;
|
||||
}
|
||||
mu_end_window(ctx);
|
||||
}
|
||||
|
||||
if (st->base.cancelled) { // Esc
|
||||
st->accepted = false;
|
||||
st->done = true;
|
||||
}
|
||||
return !st->done;
|
||||
}
|
||||
|
||||
bool menu_inputstring(INPUTSTRING_PARAM *p) {
|
||||
#ifdef _WIN32
|
||||
return input_string(p);
|
||||
#else
|
||||
p->newstring = p->oldstring;
|
||||
struct string_state st = {
|
||||
.base = { .build = inputstring_build, .handler = menu_string_handler },
|
||||
.param = p,
|
||||
};
|
||||
free(str_buf);
|
||||
str_buf = malloc(p->max * MAX_UTF8_BYTES_PAR_CHAR + 1);
|
||||
strcpy(str_buf, p->oldstring ? p->oldstring : "");
|
||||
|
||||
SDL_StartTextInput();
|
||||
modal_run(&st.base);
|
||||
SDL_StopTextInput();
|
||||
|
||||
p->newstring = st.accepted ? str_buf : p->oldstring;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user