mirror of
https://github.com/kichikuou/xsystem35-sdl2.git
synced 2026-09-22 22:48:08 +03:00
Reimplement the string input dialog with a microui textbox
The dialog edited and drew its field by hand because microui's textbox cannot display IME composition text. Teach microui about compositions instead, and split mu_textbox_raw() into input and drawing halves so the MJ inline editor can share the input path while keeping the game's own appearance.
This commit is contained in:
+46
-124
@@ -148,118 +148,31 @@ bool input_modal_number(INPUTNUM_PARAM *p) {
|
||||
#include "utfsjis.h"
|
||||
|
||||
// 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;
|
||||
FontSpec font; // inline edit (input_modal_string_inline) only
|
||||
bool focus_init; // focus the text field on the first frame
|
||||
FontSpec font; // inline edit (input_modal_string_inline) only
|
||||
};
|
||||
|
||||
// Heap buffer holding the string dialog's edited text. It must outlive the
|
||||
// modal loop because the caller reads p->newstring after input_modal_string()
|
||||
// returns; it is freed and reallocated on the next call.
|
||||
// Heap buffer holding the edited text. It must outlive the modal loop because
|
||||
// the caller reads p->newstring after input_modal_string() returns; it is freed
|
||||
// and reallocated on the next call.
|
||||
static char *str_buf;
|
||||
static int str_bufsz;
|
||||
|
||||
// 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 = gfx_viewToWindowRect(
|
||||
(SDL_Rect){box.x, box.y, box.w, box.h});
|
||||
SDL_SetTextInputRect(&r);
|
||||
static void str_buf_init(const char *initial, int max_chars) {
|
||||
free(str_buf);
|
||||
str_bufsz = max_chars * MAX_UTF8_BYTES_PAR_CHAR + 1;
|
||||
str_buf = malloc(str_bufsz);
|
||||
strncpy(str_buf, initial ? initial : "", str_bufsz - 1);
|
||||
str_buf[str_bufsz - 1] = '\0';
|
||||
}
|
||||
|
||||
#ifndef _WIN32 // input_modal_string is provided by win/dialog.c on Windows.
|
||||
|
||||
// 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)
|
||||
@@ -278,7 +191,17 @@ static bool inputstring_build(mu_Context *ctx, modal *modal) {
|
||||
snprintf(info, sizeof(info), _("Up to %d characters"), st->param->max);
|
||||
mu_label(ctx, info);
|
||||
|
||||
draw_string_field(ctx, mu_layout_next(ctx), st);
|
||||
mu_Id id = mu_get_id(ctx, "value", 5);
|
||||
mu_Rect box = mu_layout_next(ctx);
|
||||
if (st->focus_init) {
|
||||
mu_set_kb_focus(ctx, id);
|
||||
st->focus_init = false;
|
||||
}
|
||||
if (mu_textbox_raw(ctx, str_buf, str_bufsz, st->param->max, id, box, 0)
|
||||
& MU_RES_SUBMIT) {
|
||||
st->accepted = true;
|
||||
st->done = true;
|
||||
}
|
||||
|
||||
int content = w - ctx->style->padding * 2;
|
||||
int half = (content - ctx->style->spacing) / 2;
|
||||
@@ -303,16 +226,12 @@ static bool inputstring_build(mu_Context *ctx, modal *modal) {
|
||||
|
||||
bool input_modal_string(INPUTSTRING_PARAM *p) {
|
||||
struct string_state st = {
|
||||
.base = { .build = inputstring_build, .handler = menu_string_handler },
|
||||
.base = { .build = inputstring_build, .handler = modal_default_handler },
|
||||
.param = p,
|
||||
.focus_init = true,
|
||||
};
|
||||
free(str_buf);
|
||||
str_buf = malloc(p->max * MAX_UTF8_BYTES_PAR_CHAR + 1);
|
||||
strcpy(str_buf, p->oldstring ? p->oldstring : "");
|
||||
|
||||
SDL_StartTextInput();
|
||||
str_buf_init(p->oldstring, p->max);
|
||||
modal_run(&st.base);
|
||||
SDL_StopTextInput();
|
||||
|
||||
p->newstring = st.accepted ? str_buf : p->oldstring;
|
||||
return true;
|
||||
@@ -320,8 +239,8 @@ bool input_modal_string(INPUTSTRING_PARAM *p) {
|
||||
|
||||
#endif // !_WIN32
|
||||
|
||||
// The inline text input (MJ command). It reuses the IME buffer handling
|
||||
// and the event handler of input_modal_string.
|
||||
// The inline text input (MJ command). It shares the textbox input handling but
|
||||
// draws itself with the game's font and palette.
|
||||
|
||||
// Look up an AGS color index in the active palette and return it as a mu_Color.
|
||||
static mu_Color palette_color(int index) {
|
||||
@@ -342,6 +261,17 @@ static bool inline_edit_build(mu_Context *ctx, modal *modal) {
|
||||
// all drawing below is done manually with the game's colors.
|
||||
if (mu_begin_window_ex(ctx, "editstr", rect,
|
||||
MU_OPT_NOFRAME | MU_OPT_NOTITLE | MU_OPT_NORESIZE | MU_OPT_NOSCROLL)) {
|
||||
mu_Id id = mu_get_id(ctx, "editstr", 7);
|
||||
if (st->focus_init) {
|
||||
mu_set_kb_focus(ctx, id);
|
||||
st->focus_init = false;
|
||||
}
|
||||
if (mu_textbox_input(ctx, str_buf, str_bufsz, st->param->max, id, rect, 0)
|
||||
& MU_RES_SUBMIT) {
|
||||
st->accepted = true;
|
||||
st->done = true;
|
||||
}
|
||||
|
||||
mu_Color bg = palette_color(nact->msg.WinBackgroundColor);
|
||||
mu_Color fg = palette_color(nact->msg.MsgFontColor);
|
||||
mu_Font font = (mu_Font)&st->font;
|
||||
@@ -354,14 +284,13 @@ static bool inline_edit_build(mu_Context *ctx, modal *modal) {
|
||||
mu_draw_text(ctx, font, str_buf, -1, mu_vec2(x, y), fg);
|
||||
x += ctx->text_width(font, str_buf, -1);
|
||||
}
|
||||
if (*st->composing) {
|
||||
mu_draw_text(ctx, font, st->composing, -1, mu_vec2(x, y), fg);
|
||||
int cw = ctx->text_width(font, st->composing, -1);
|
||||
if (*ctx->preedit) {
|
||||
mu_draw_text(ctx, font, ctx->preedit, -1, mu_vec2(x, y), fg);
|
||||
int cw = ctx->text_width(font, ctx->preedit, -1);
|
||||
mu_draw_rect(ctx, mu_rect(x, y + fh, cw, 2), fg); // preedit underline
|
||||
x += cw;
|
||||
}
|
||||
mu_draw_rect(ctx, mu_rect(x, y, 2, fh), fg); // caret
|
||||
set_text_input_rect(rect);
|
||||
mu_end_window(ctx);
|
||||
}
|
||||
|
||||
@@ -372,18 +301,14 @@ static bool inline_edit_build(mu_Context *ctx, modal *modal) {
|
||||
|
||||
bool input_modal_string_inline(INPUTSTRING_PARAM *p) {
|
||||
struct string_state st = {
|
||||
.base = { .build = inline_edit_build, .handler = menu_string_handler,
|
||||
.base = { .build = inline_edit_build, .handler = modal_default_handler,
|
||||
.no_dim = true },
|
||||
.param = p,
|
||||
.focus_init = true,
|
||||
.font = { FONT_GOTHIC, FONT_WEIGHT_NORMAL, p->h },
|
||||
};
|
||||
free(str_buf);
|
||||
str_buf = malloc(p->max * MAX_UTF8_BYTES_PAR_CHAR + 1);
|
||||
strcpy(str_buf, p->oldstring ? p->oldstring : "");
|
||||
|
||||
SDL_StartTextInput();
|
||||
str_buf_init(p->oldstring, p->max);
|
||||
modal_run(&st.base);
|
||||
SDL_StopTextInput();
|
||||
|
||||
p->newstring = st.accepted ? str_buf : NULL;
|
||||
return true;
|
||||
@@ -456,7 +381,7 @@ static bool inputnumber_build(mu_Context *ctx, modal *modal) {
|
||||
mu_set_kb_focus(ctx, id);
|
||||
st->focus_init = false;
|
||||
}
|
||||
int res = mu_textbox_raw(ctx, st->buf, sizeof(st->buf), id, box, 0);
|
||||
int res = mu_textbox_raw(ctx, st->buf, sizeof(st->buf), 0, id, box, 0);
|
||||
if (mu_button(ctx, "-"))
|
||||
num_adjust(st, -1);
|
||||
if (mu_button(ctx, "+"))
|
||||
@@ -501,10 +426,7 @@ bool input_modal_number(INPUTNUM_PARAM *p) {
|
||||
.focus_init = true,
|
||||
};
|
||||
snprintf(st.buf, sizeof(st.buf), "%d", p->def);
|
||||
|
||||
SDL_StartTextInput();
|
||||
modal_run(&st.base);
|
||||
SDL_StopTextInput();
|
||||
|
||||
if (!st.accepted)
|
||||
return false;
|
||||
|
||||
+100
-50
@@ -149,6 +149,7 @@ void mu_begin(mu_Context *ctx) {
|
||||
ctx->nav_first = ctx->nav_last = 0;
|
||||
ctx->nav_prev = ctx->nav_next = ctx->nav_cursor = 0;
|
||||
ctx->kb_focus_seen = 0;
|
||||
ctx->text_input = 0;
|
||||
ctx->frame++;
|
||||
}
|
||||
|
||||
@@ -190,6 +191,9 @@ void mu_end(mu_Context *ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
/* drop a composition left over from a textbox that lost the focus */
|
||||
if (!ctx->text_input) { ctx->preedit[0] = '\0'; }
|
||||
|
||||
/* bring hover root to front if mouse was pressed */
|
||||
if (ctx->mouse_pressed && ctx->next_hover_root &&
|
||||
ctx->next_hover_root->zindex < ctx->last_zindex &&
|
||||
@@ -427,6 +431,8 @@ void mu_input_scroll(mu_Context *ctx, int x, int y) {
|
||||
|
||||
|
||||
void mu_input_keydown(mu_Context *ctx, int key) {
|
||||
/* while an IME composition is active the keys belong to the IME */
|
||||
if (*ctx->preedit) { return; }
|
||||
ctx->key_pressed |= key;
|
||||
ctx->key_down |= key;
|
||||
}
|
||||
@@ -438,10 +444,22 @@ void mu_input_keyup(mu_Context *ctx, int key) {
|
||||
|
||||
|
||||
void mu_input_text(mu_Context *ctx, const char *text) {
|
||||
/* SDL splits a long commit into several events, which all arrive within one
|
||||
** frame and can overflow the buffer. Drop the excess instead of failing. */
|
||||
int len = strlen(ctx->input_text);
|
||||
int size = strlen(text) + 1;
|
||||
expect(len + size <= (int) sizeof(ctx->input_text));
|
||||
memcpy(ctx->input_text + len, text, size);
|
||||
int n = mu_min((int) strlen(text), (int) sizeof(ctx->input_text) - 1 - len);
|
||||
while (n > 0 && (text[n] & 0xc0) == 0x80) { n--; } /* keep utf-8 whole */
|
||||
memcpy(ctx->input_text + len, text, n);
|
||||
ctx->input_text[len + n] = '\0';
|
||||
ctx->preedit[0] = '\0'; /* committing ends the composition */
|
||||
}
|
||||
|
||||
|
||||
void mu_input_preedit(mu_Context *ctx, const char *text) {
|
||||
int n = mu_min((int) strlen(text), (int) sizeof(ctx->preedit) - 1);
|
||||
while (n > 0 && (text[n] & 0xc0) == 0x80) { n--; } /* keep utf-8 whole */
|
||||
memcpy(ctx->preedit, text, n);
|
||||
ctx->preedit[n] = '\0';
|
||||
}
|
||||
|
||||
|
||||
@@ -829,59 +847,91 @@ int mu_checkbox(mu_Context *ctx, const char *label, int *state) {
|
||||
}
|
||||
|
||||
|
||||
int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, mu_Id id, mu_Rect r,
|
||||
int opt)
|
||||
/* number of characters in a utf-8 string */
|
||||
static int utf8_strlen(const char *s) {
|
||||
int n = 0;
|
||||
for (; *s; s++) { if ((*s & 0xc0) != 0x80) { n++; } }
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
int mu_textbox_input(mu_Context *ctx, char *buf, int bufsz, int max_chars,
|
||||
mu_Id id, mu_Rect r, int opt)
|
||||
{
|
||||
int res = 0;
|
||||
/* the keyboard focus decides which textbox is editable, so that Tab moves the
|
||||
** caret out of a MU_OPT_HOLDFOCUS textbox. A click reaches this through
|
||||
** mu_update_control(), which also sets kb_focus. */
|
||||
if (ctx->kb_focus == id) { mu_set_focus(ctx, id); }
|
||||
else if (ctx->focus == id) { mu_set_focus(ctx, 0); }
|
||||
mu_update_control(ctx, id, r, opt | MU_OPT_HOLDFOCUS);
|
||||
/* the keyboard focus decides which textbox is editable, so that Tab moves
|
||||
** the caret out of it. A click reaches this through mu_update_control(). */
|
||||
mu_update_control(ctx, id, r, opt);
|
||||
if (ctx->kb_focus != id) { return 0; }
|
||||
|
||||
if (ctx->focus == id) {
|
||||
/* handle text input */
|
||||
int len = strlen(buf);
|
||||
int n = mu_min(bufsz - len - 1, (int) strlen(ctx->input_text));
|
||||
if (n > 0) {
|
||||
memcpy(buf + len, ctx->input_text, n);
|
||||
len += n;
|
||||
buf[len] = '\0';
|
||||
res |= MU_RES_CHANGE;
|
||||
}
|
||||
/* handle backspace */
|
||||
if (ctx->key_pressed & MU_KEY_BACKSPACE && len > 0) {
|
||||
/* skip utf-8 continuation bytes */
|
||||
while ((buf[--len] & 0xc0) == 0x80 && len > 0);
|
||||
buf[len] = '\0';
|
||||
res |= MU_RES_CHANGE;
|
||||
}
|
||||
/* handle return */
|
||||
if (ctx->key_pressed & MU_KEY_RETURN) {
|
||||
mu_set_focus(ctx, 0);
|
||||
res |= MU_RES_SUBMIT;
|
||||
}
|
||||
/* let the host turn the IME on and place its candidate window */
|
||||
ctx->text_input = 1;
|
||||
ctx->text_input_rect = r;
|
||||
|
||||
/* handle text input, honoring both the byte and the character limit */
|
||||
int len = strlen(buf);
|
||||
int room = bufsz - len - 1;
|
||||
int chars = max_chars ? max_chars - utf8_strlen(buf) : bufsz;
|
||||
int n = 0;
|
||||
while (ctx->input_text[n] && chars > 0) {
|
||||
int e = n + 1;
|
||||
while ((ctx->input_text[e] & 0xc0) == 0x80) { e++; }
|
||||
if (e > room) { break; }
|
||||
n = e;
|
||||
chars--;
|
||||
}
|
||||
if (n > 0) {
|
||||
memcpy(buf + len, ctx->input_text, n);
|
||||
len += n;
|
||||
buf[len] = '\0';
|
||||
res |= MU_RES_CHANGE;
|
||||
}
|
||||
/* handle backspace */
|
||||
if (ctx->key_pressed & MU_KEY_BACKSPACE && len > 0) {
|
||||
/* skip utf-8 continuation bytes */
|
||||
while ((buf[--len] & 0xc0) == 0x80 && len > 0);
|
||||
buf[len] = '\0';
|
||||
res |= MU_RES_CHANGE;
|
||||
}
|
||||
/* handle return */
|
||||
if (ctx->key_pressed & MU_KEY_RETURN) { res |= MU_RES_SUBMIT; }
|
||||
|
||||
/* draw */
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
void mu_draw_textbox(mu_Context *ctx, const char *buf, mu_Id id, mu_Rect r,
|
||||
int opt)
|
||||
{
|
||||
mu_draw_control_frame(ctx, id, r, MU_COLOR_BASE, opt);
|
||||
if (ctx->focus == id) {
|
||||
mu_Color color = ctx->style->colors[MU_COLOR_TEXT];
|
||||
mu_Font font = ctx->style->font;
|
||||
int textw = ctx->text_width(font, buf, -1);
|
||||
int texth = ctx->text_height(font);
|
||||
int ofx = r.w - ctx->style->padding - textw - 1;
|
||||
int textx = r.x + mu_min(ofx, ctx->style->padding);
|
||||
int texty = r.y + (r.h - texth) / 2;
|
||||
mu_push_clip_rect(ctx, r);
|
||||
mu_draw_text(ctx, font, buf, -1, mu_vec2(textx, texty), color);
|
||||
mu_draw_rect(ctx, mu_rect(textx + textw, texty, 1, texth), color);
|
||||
mu_pop_clip_rect(ctx);
|
||||
} else {
|
||||
if (ctx->kb_focus != id) {
|
||||
mu_draw_control_text(ctx, buf, r, MU_COLOR_TEXT, opt);
|
||||
return;
|
||||
}
|
||||
mu_Color color = ctx->style->colors[MU_COLOR_TEXT];
|
||||
mu_Font font = ctx->style->font;
|
||||
int textw = ctx->text_width(font, buf, -1);
|
||||
int prew = *ctx->preedit ? ctx->text_width(font, ctx->preedit, -1) : 0;
|
||||
int texth = ctx->text_height(font);
|
||||
int ofx = r.w - ctx->style->padding - textw - prew - 1;
|
||||
int textx = r.x + mu_min(ofx, ctx->style->padding);
|
||||
int texty = r.y + (r.h - texth) / 2;
|
||||
mu_push_clip_rect(ctx, r);
|
||||
mu_draw_text(ctx, font, buf, -1, mu_vec2(textx, texty), color);
|
||||
if (prew) {
|
||||
mu_draw_text(ctx, font, ctx->preedit, -1, mu_vec2(textx + textw, texty), color);
|
||||
mu_draw_rect(ctx, mu_rect(textx + textw, texty + texth, prew, 1), color);
|
||||
}
|
||||
mu_draw_rect(ctx, mu_rect(textx + textw + prew, texty, 1, texth), color);
|
||||
mu_pop_clip_rect(ctx);
|
||||
}
|
||||
|
||||
|
||||
int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, int max_chars,
|
||||
mu_Id id, mu_Rect r, int opt)
|
||||
{
|
||||
int res = mu_textbox_input(ctx, buf, bufsz, max_chars, id, r, opt);
|
||||
mu_draw_textbox(ctx, buf, id, r, opt);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -895,8 +945,8 @@ static int number_textbox(mu_Context *ctx, mu_Real *value, mu_Rect r, mu_Id id)
|
||||
}
|
||||
if (ctx->number_edit == id) {
|
||||
int res = mu_textbox_raw(
|
||||
ctx, ctx->number_edit_buf, sizeof(ctx->number_edit_buf), id, r, 0);
|
||||
if (res & MU_RES_SUBMIT || ctx->focus != id) {
|
||||
ctx, ctx->number_edit_buf, sizeof(ctx->number_edit_buf), 0, id, r, 0);
|
||||
if (res & MU_RES_SUBMIT || ctx->kb_focus != id) {
|
||||
*value = strtod(ctx->number_edit_buf, NULL);
|
||||
ctx->number_edit = 0;
|
||||
} else {
|
||||
@@ -910,7 +960,7 @@ static int number_textbox(mu_Context *ctx, mu_Real *value, mu_Rect r, mu_Id id)
|
||||
int mu_textbox_ex(mu_Context *ctx, char *buf, int bufsz, int opt) {
|
||||
mu_Id id = mu_get_id(ctx, &buf, sizeof(buf));
|
||||
mu_Rect r = mu_layout_next(ctx);
|
||||
return mu_textbox_raw(ctx, buf, bufsz, id, r, opt);
|
||||
return mu_textbox_raw(ctx, buf, bufsz, 0, id, r, opt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+12
-1
@@ -229,6 +229,11 @@ struct mu_Context {
|
||||
int key_down;
|
||||
int key_pressed;
|
||||
char input_text[32];
|
||||
char preedit[32]; /* IME composition text, not yet committed */
|
||||
/* Set by mu_textbox_input() while a textbox has the keyboard focus, so the
|
||||
** host can enable the IME and place its candidate window. */
|
||||
int text_input;
|
||||
mu_Rect text_input_rect;
|
||||
};
|
||||
|
||||
|
||||
@@ -263,6 +268,7 @@ void mu_input_scroll(mu_Context *ctx, int x, int y);
|
||||
void mu_input_keydown(mu_Context *ctx, int key);
|
||||
void mu_input_keyup(mu_Context *ctx, int key);
|
||||
void mu_input_text(mu_Context *ctx, const char *text);
|
||||
void mu_input_preedit(mu_Context *ctx, const char *text);
|
||||
|
||||
mu_Command* mu_push_command(mu_Context *ctx, int type, int size);
|
||||
int mu_next_command(mu_Context *ctx, mu_Command **cmd);
|
||||
@@ -298,7 +304,12 @@ void mu_text(mu_Context *ctx, const char *text);
|
||||
void mu_label(mu_Context *ctx, const char *text);
|
||||
int mu_button_ex(mu_Context *ctx, const char *label, int icon, int opt);
|
||||
int mu_checkbox(mu_Context *ctx, const char *label, int *state);
|
||||
int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, mu_Id id, mu_Rect r, int opt);
|
||||
/* Edit `buf` in place, without drawing. `max_chars` limits the length in UTF-8
|
||||
** characters (0 for no limit beyond `bufsz`). Use this with mu_draw_textbox(),
|
||||
** or with custom drawing when the default appearance does not fit. */
|
||||
int mu_textbox_input(mu_Context *ctx, char *buf, int bufsz, int max_chars, mu_Id id, mu_Rect r, int opt);
|
||||
void mu_draw_textbox(mu_Context *ctx, const char *buf, mu_Id id, mu_Rect r, int opt);
|
||||
int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, int max_chars, mu_Id id, mu_Rect r, int opt);
|
||||
int mu_textbox_ex(mu_Context *ctx, char *buf, int bufsz, int opt);
|
||||
int mu_slider_ex(mu_Context *ctx, mu_Real *value, mu_Real low, mu_Real high, mu_Real step, const char *fmt, int opt);
|
||||
int mu_number_ex(mu_Context *ctx, mu_Real *value, mu_Real step, const char *fmt, int opt);
|
||||
|
||||
+30
-1
@@ -120,6 +120,9 @@ bool modal_default_handler(const SDL_Event *e, modal *modal) {
|
||||
case SDL_TEXTINPUT:
|
||||
mu_input_text(ctx, e->text.text);
|
||||
break;
|
||||
case SDL_TEXTEDITING:
|
||||
mu_input_preedit(ctx, e->edit.text);
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP: {
|
||||
int b = 0;
|
||||
@@ -161,7 +164,9 @@ bool modal_default_handler(const SDL_Event *e, modal *modal) {
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP: {
|
||||
if (e->type == SDL_KEYDOWN && e->key.keysym.sym == SDLK_ESCAPE)
|
||||
// Esc cancels an IME composition first, not the modal.
|
||||
if (e->type == SDL_KEYDOWN && e->key.keysym.sym == SDLK_ESCAPE &&
|
||||
!*ctx->preedit)
|
||||
modal->cancelled = true;
|
||||
int c = 0;
|
||||
switch (e->key.keysym.sym) {
|
||||
@@ -293,6 +298,27 @@ static void modal_render(void) {
|
||||
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
|
||||
}
|
||||
|
||||
// Turn SDL's text input on while a textbox has the keyboard focus, and keep the
|
||||
// IME candidate window near it. The rect is in logical (view) coordinates.
|
||||
static void update_text_input(void) {
|
||||
static SDL_Rect last_rect;
|
||||
if (!ctx->text_input) {
|
||||
if (SDL_IsTextInputActive())
|
||||
SDL_StopTextInput();
|
||||
return;
|
||||
}
|
||||
if (!SDL_IsTextInputActive()) {
|
||||
SDL_StartTextInput();
|
||||
last_rect = (SDL_Rect){0, 0, 0, 0};
|
||||
}
|
||||
mu_Rect r = ctx->text_input_rect;
|
||||
SDL_Rect wr = gfx_viewToWindowRect((SDL_Rect){r.x, r.y, r.w, r.h});
|
||||
if (memcmp(&wr, &last_rect, sizeof(wr))) {
|
||||
last_rect = wr;
|
||||
SDL_SetTextInputRect(&wr);
|
||||
}
|
||||
}
|
||||
|
||||
bool modal_handle_event(const SDL_Event *e) {
|
||||
if (!current_modal)
|
||||
return false;
|
||||
@@ -341,6 +367,7 @@ void modal_run(modal *m) {
|
||||
mu_begin(ctx);
|
||||
open = m->build(ctx, m);
|
||||
mu_end(ctx);
|
||||
update_text_input();
|
||||
|
||||
// Present only when the overlay changed.
|
||||
mu_Id hash = mu_get_id(ctx, ctx->command_list.items, ctx->command_list.idx);
|
||||
@@ -355,6 +382,8 @@ void modal_run(modal *m) {
|
||||
current_modal = NULL;
|
||||
free(ctx);
|
||||
ctx = NULL;
|
||||
if (SDL_IsTextInputActive())
|
||||
SDL_StopTextInput();
|
||||
gfx_requestRedraw(); // repaint once more to clear the overlay
|
||||
|
||||
// The gesture that opened/dismissed the dialog must not leave the engine
|
||||
|
||||
Reference in New Issue
Block a user