mirror of
https://github.com/kichikuou/xsystem35-sdl2.git
synced 2026-09-27 01:00:24 +03:00
Split string/number input dialogs out of menu_sdl.c into input_modal.c
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
./src/menu_sdl.c
|
||||
./src/input_modal.c
|
||||
./src/volume.c
|
||||
|
||||
+1
-1
@@ -124,7 +124,7 @@ elseif (ANDROID)
|
||||
target_sources(xsystem35 PRIVATE menu_android.c)
|
||||
else ()
|
||||
target_sources(xsystem35 PRIVATE
|
||||
menu_sdl.c editor.c modal.c microui/microui.c volume.c)
|
||||
menu_sdl.c input_modal.c editor.c modal.c microui/microui.c volume.c)
|
||||
endif()
|
||||
|
||||
if (HAVE_WEBP)
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* input_modal.c string- and number-input dialogs
|
||||
*
|
||||
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "portab.h"
|
||||
#include "menu.h"
|
||||
#include "editor.h"
|
||||
#include "gfx_private.h"
|
||||
#include "modal.h"
|
||||
#include "utfsjis.h"
|
||||
#ifdef _WIN32
|
||||
#include "win/dialog.h"
|
||||
#endif
|
||||
|
||||
// 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
|
||||
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
|
||||
}
|
||||
|
||||
bool menu_inputstring2(INPUTSTRING_PARAM *p) {
|
||||
return edit_string(p);
|
||||
}
|
||||
|
||||
// The number-input dialog.
|
||||
|
||||
struct number_state {
|
||||
modal base;
|
||||
INPUTNUM_PARAM *param;
|
||||
char buf[32]; // edited value as text
|
||||
bool done; // set when OK/Cancel/Esc ends the dialog
|
||||
bool accepted; // true on OK, false on Cancel
|
||||
bool focus_init; // focus the input field on the first frame
|
||||
};
|
||||
|
||||
// Parse st->buf; returns true (and stores the value) if it is a valid integer
|
||||
// within [min, max].
|
||||
static bool num_value_valid(const struct number_state *st, int *out) {
|
||||
if (!st->buf[0])
|
||||
return false;
|
||||
char *end;
|
||||
long v = strtol(st->buf, &end, 10);
|
||||
if (*end != '\0' || v < st->param->min || v > st->param->max)
|
||||
return false;
|
||||
if (out)
|
||||
*out = (int)v;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add `delta` to the current value, clamp to [min, max], and write it back.
|
||||
static void num_adjust(struct number_state *st, int delta) {
|
||||
long v = strtol(st->buf, NULL, 10) + delta;
|
||||
if (v < st->param->min)
|
||||
v = st->param->min;
|
||||
if (v > st->param->max)
|
||||
v = st->param->max;
|
||||
snprintf(st->buf, sizeof(st->buf), "%ld", v);
|
||||
}
|
||||
|
||||
static bool inputnumber_build(mu_Context *ctx, modal *modal) {
|
||||
struct number_state *st = (struct number_state *)modal;
|
||||
const char *title = (st->param->title && *st->param->title)
|
||||
? st->param->title : _("Enter a number");
|
||||
int row_h = ctx->text_height(ctx->style->font) + ctx->style->padding * 2;
|
||||
int w = 240;
|
||||
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), "%d - %d", st->param->min, st->param->max);
|
||||
mu_label(ctx, info);
|
||||
|
||||
// Input field flanked by - / + spin buttons.
|
||||
int content = w - ctx->style->padding * 2;
|
||||
int bw = row_h; // square spin buttons
|
||||
int tbw = content - (bw + ctx->style->spacing) * 2;
|
||||
mu_layout_row(ctx, 3, (int[]){ tbw, bw, bw }, 0);
|
||||
|
||||
mu_Id id = mu_get_id(ctx, "value", 5);
|
||||
mu_Rect box = mu_layout_next(ctx);
|
||||
if (st->focus_init) {
|
||||
mu_set_focus(ctx, id);
|
||||
st->focus_init = false;
|
||||
}
|
||||
int res = mu_textbox_raw(ctx, st->buf, sizeof(st->buf), id, box, 0);
|
||||
if (mu_button(ctx, "-"))
|
||||
num_adjust(st, -1);
|
||||
if (mu_button(ctx, "+"))
|
||||
num_adjust(st, 1);
|
||||
|
||||
// OK is only enabled when the field holds an in-range number; Enter
|
||||
// (the textbox SUBMIT) is gated the same way.
|
||||
bool valid = num_value_valid(st, NULL);
|
||||
if ((res & MU_RES_SUBMIT) && valid) {
|
||||
st->accepted = true;
|
||||
st->done = true;
|
||||
}
|
||||
|
||||
int half = (content - ctx->style->spacing) / 2;
|
||||
mu_layout_row(ctx, 2, (int[]){ half, -1 }, 0);
|
||||
if (valid) {
|
||||
if (mu_button(ctx, _("OK"))) {
|
||||
st->accepted = true;
|
||||
st->done = true;
|
||||
}
|
||||
} else {
|
||||
modal_disabled_button(ctx, _("OK"));
|
||||
}
|
||||
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_inputnumber(INPUTNUM_PARAM *p) {
|
||||
#ifdef _WIN32
|
||||
return input_number(p);
|
||||
#else
|
||||
struct number_state st = {
|
||||
.base = { .build = inputnumber_build, .handler = modal_default_handler },
|
||||
.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;
|
||||
long v = strtol(st.buf, NULL, 10);
|
||||
if (v < p->min)
|
||||
v = p->min;
|
||||
if (v > p->max)
|
||||
v = p->max;
|
||||
p->value = (int)v;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
-314
@@ -29,12 +29,10 @@
|
||||
#include "system.h"
|
||||
#include "nact.h"
|
||||
#include "menu.h"
|
||||
#include "editor.h"
|
||||
#include "gfx_private.h"
|
||||
#include "msgskip.h"
|
||||
#include "modal.h"
|
||||
#include "volume.h"
|
||||
#include "utfsjis.h"
|
||||
#ifdef _WIN32
|
||||
#include "win/dialog.h"
|
||||
#endif
|
||||
@@ -193,318 +191,6 @@ 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
|
||||
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
|
||||
}
|
||||
|
||||
bool menu_inputstring2(INPUTSTRING_PARAM *p) {
|
||||
return edit_string(p);
|
||||
}
|
||||
|
||||
// The number-input dialog.
|
||||
|
||||
struct number_state {
|
||||
modal base;
|
||||
INPUTNUM_PARAM *param;
|
||||
char buf[32]; // edited value as text
|
||||
bool done; // set when OK/Cancel/Esc ends the dialog
|
||||
bool accepted; // true on OK, false on Cancel
|
||||
bool focus_init; // focus the input field on the first frame
|
||||
};
|
||||
|
||||
// Parse st->buf; returns true (and stores the value) if it is a valid integer
|
||||
// within [min, max].
|
||||
static bool num_value_valid(const struct number_state *st, int *out) {
|
||||
if (!st->buf[0])
|
||||
return false;
|
||||
char *end;
|
||||
long v = strtol(st->buf, &end, 10);
|
||||
if (*end != '\0' || v < st->param->min || v > st->param->max)
|
||||
return false;
|
||||
if (out)
|
||||
*out = (int)v;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add `delta` to the current value, clamp to [min, max], and write it back.
|
||||
static void num_adjust(struct number_state *st, int delta) {
|
||||
long v = strtol(st->buf, NULL, 10) + delta;
|
||||
if (v < st->param->min)
|
||||
v = st->param->min;
|
||||
if (v > st->param->max)
|
||||
v = st->param->max;
|
||||
snprintf(st->buf, sizeof(st->buf), "%ld", v);
|
||||
}
|
||||
|
||||
static bool inputnumber_build(mu_Context *ctx, modal *modal) {
|
||||
struct number_state *st = (struct number_state *)modal;
|
||||
const char *title = (st->param->title && *st->param->title)
|
||||
? st->param->title : _("Enter a number");
|
||||
int row_h = ctx->text_height(ctx->style->font) + ctx->style->padding * 2;
|
||||
int w = 240;
|
||||
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), "%d - %d", st->param->min, st->param->max);
|
||||
mu_label(ctx, info);
|
||||
|
||||
// Input field flanked by - / + spin buttons.
|
||||
int content = w - ctx->style->padding * 2;
|
||||
int bw = row_h; // square spin buttons
|
||||
int tbw = content - (bw + ctx->style->spacing) * 2;
|
||||
mu_layout_row(ctx, 3, (int[]){ tbw, bw, bw }, 0);
|
||||
|
||||
mu_Id id = mu_get_id(ctx, "value", 5);
|
||||
mu_Rect box = mu_layout_next(ctx);
|
||||
if (st->focus_init) {
|
||||
mu_set_focus(ctx, id);
|
||||
st->focus_init = false;
|
||||
}
|
||||
int res = mu_textbox_raw(ctx, st->buf, sizeof(st->buf), id, box, 0);
|
||||
if (mu_button(ctx, "-"))
|
||||
num_adjust(st, -1);
|
||||
if (mu_button(ctx, "+"))
|
||||
num_adjust(st, 1);
|
||||
|
||||
// OK is only enabled when the field holds an in-range number; Enter
|
||||
// (the textbox SUBMIT) is gated the same way.
|
||||
bool valid = num_value_valid(st, NULL);
|
||||
if ((res & MU_RES_SUBMIT) && valid) {
|
||||
st->accepted = true;
|
||||
st->done = true;
|
||||
}
|
||||
|
||||
int half = (content - ctx->style->spacing) / 2;
|
||||
mu_layout_row(ctx, 2, (int[]){ half, -1 }, 0);
|
||||
if (valid) {
|
||||
if (mu_button(ctx, _("OK"))) {
|
||||
st->accepted = true;
|
||||
st->done = true;
|
||||
}
|
||||
} else {
|
||||
modal_disabled_button(ctx, _("OK"));
|
||||
}
|
||||
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_inputnumber(INPUTNUM_PARAM *p) {
|
||||
#ifdef _WIN32
|
||||
return input_number(p);
|
||||
#else
|
||||
struct number_state st = {
|
||||
.base = { .build = inputnumber_build, .handler = modal_default_handler },
|
||||
.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;
|
||||
long v = strtol(st.buf, NULL, 10);
|
||||
if (v < p->min)
|
||||
v = p->min;
|
||||
if (v > p->max)
|
||||
v = p->max;
|
||||
p->value = (int)v;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void menu_init(void) {
|
||||
#ifdef _WIN32
|
||||
win_menu_init();
|
||||
|
||||
Reference in New Issue
Block a user