mirror of
https://github.com/kichikuou/xsystem35-sdl2.git
synced 2026-09-30 18:58:07 +03:00
Reimplement the windowless string input on the modal framework
Move edit_string into input_modal.c as an inline modal dialog, sharing the IME buffer handling and event handler with menu_inputstring.
This commit is contained in:
+1
-1
@@ -124,7 +124,7 @@ elseif (ANDROID)
|
||||
target_sources(xsystem35 PRIVATE menu_android.c)
|
||||
else ()
|
||||
target_sources(xsystem35 PRIVATE
|
||||
menu_sdl.c input_modal.c editor.c modal.c microui/microui.c volume.c)
|
||||
menu_sdl.c input_modal.c modal.c microui/microui.c volume.c)
|
||||
endif()
|
||||
|
||||
if (HAVE_WEBP)
|
||||
|
||||
-174
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 <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 <string.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#include "editor.h"
|
||||
#include "event.h"
|
||||
#include "gfx.h"
|
||||
#include "menu.h"
|
||||
#include "nact.h"
|
||||
#include "system.h"
|
||||
|
||||
#define XMARGIN 3
|
||||
#define YMARGIN 2
|
||||
|
||||
struct EditorState {
|
||||
INPUTSTRING_PARAM *params;
|
||||
SDL_Rect rect;
|
||||
char *text;
|
||||
char composingText[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
|
||||
int cursor;
|
||||
bool done;
|
||||
};
|
||||
static struct EditorState *input;
|
||||
|
||||
static void redraw() {
|
||||
const int bgcolor = nact->msg.WinBackgroundColor;
|
||||
const int fgcolor = nact->msg.MsgFontColor;
|
||||
|
||||
ags_fillRectangle(input->rect.x, input->rect.y, input->rect.w, input->rect.h, bgcolor);
|
||||
|
||||
SDL_Rect r = {
|
||||
.x = input->rect.x + XMARGIN,
|
||||
.y = input->rect.y + YMARGIN,
|
||||
.w = input->rect.w - XMARGIN * 2,
|
||||
.h = input->rect.h - YMARGIN * 2,
|
||||
};
|
||||
|
||||
const FontSpec font = { .size = input->params->h };
|
||||
|
||||
if (*input->text) {
|
||||
int w = gfx_drawString(r.x, r.y, input->text, fgcolor, font).w;
|
||||
r.x += w;
|
||||
r.w -= w;
|
||||
}
|
||||
|
||||
if (*input->composingText) {
|
||||
int w = gfx_drawString(r.x, r.y, input->composingText, fgcolor, font).w;
|
||||
ags_fillRectangle(r.x, r.y + r.h, w, 2, fgcolor); // underline
|
||||
}
|
||||
|
||||
// FIXME: This doesn't work with propotional fonts!
|
||||
int cursor_x = r.x + r.h * input->cursor;
|
||||
ags_fillRectangle(cursor_x, r.y, 2, r.h, fgcolor); // cursor
|
||||
|
||||
// Dib geometry -> window geometry
|
||||
r.x -= nact->ags.view_area.x;
|
||||
r.y -= nact->ags.view_area.y;
|
||||
SDL_SetTextInputRect(&r);
|
||||
|
||||
ags_updateArea(input->rect.x, input->rect.y, input->rect.w, input->rect.h);
|
||||
}
|
||||
|
||||
static bool handle_event(const SDL_Event *e) {
|
||||
switch (e->type) {
|
||||
case SDL_KEYDOWN:
|
||||
if (*input->composingText)
|
||||
break; // Let the IME handle this event.
|
||||
switch (e->key.keysym.sym) {
|
||||
case SDLK_RETURN:
|
||||
input->params->newstring = input->text;
|
||||
input->done = true;
|
||||
return true;
|
||||
case SDLK_ESCAPE:
|
||||
input->params->newstring = NULL;
|
||||
input->done = true;
|
||||
return true;
|
||||
case SDLK_BACKSPACE:
|
||||
{
|
||||
char *p = input->text + strlen(input->text) - 1;
|
||||
while (p >= input->text && UTF8_TRAIL_BYTE(*p))
|
||||
p--;
|
||||
if (p >= input->text)
|
||||
*p = '\0';
|
||||
redraw();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_TEXTINPUT:
|
||||
{
|
||||
int chars = input->params->max;
|
||||
for (const char *p = input->text; *p; p = advance_char(p, UTF8))
|
||||
chars--;
|
||||
const char *end = e->text.text;
|
||||
while (chars > 0 && *end) {
|
||||
end = advance_char(end, UTF8);
|
||||
chars--;
|
||||
}
|
||||
strncat(input->text, e->text.text, end - e->text.text);
|
||||
|
||||
input->composingText[0] = '\0';
|
||||
input->cursor = 0;
|
||||
redraw();
|
||||
}
|
||||
return true;
|
||||
|
||||
case SDL_TEXTEDITING:
|
||||
strncpy(input->composingText, e->edit.text, SDL_TEXTEDITINGEVENT_TEXT_SIZE);
|
||||
input->cursor = e->edit.start;
|
||||
redraw();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool edit_string(INPUTSTRING_PARAM *p) {
|
||||
static char *buf;
|
||||
free(buf);
|
||||
buf = malloc(p->max * MAX_UTF8_BYTES_PAR_CHAR + 1);
|
||||
|
||||
struct EditorState s = {
|
||||
.params = p,
|
||||
.rect = {
|
||||
.x = p->x + nact->ags.view_area.x, // window geometry -> dib geometry
|
||||
.y = p->y + nact->ags.view_area.y,
|
||||
.w = p->h * p->max + XMARGIN * 2,
|
||||
.h = p->h + YMARGIN * 2
|
||||
},
|
||||
.text = buf,
|
||||
};
|
||||
strcpy(s.text, p->oldstring);
|
||||
input = &s;
|
||||
|
||||
SDL_StartTextInput();
|
||||
|
||||
void *saved_region = ags_saveRegion(s.rect.x, s.rect.y, s.rect.w, s.rect.h);
|
||||
redraw();
|
||||
|
||||
event_custom_handler = handle_event;
|
||||
while (!input->done) {
|
||||
event_get_key(); // message pump
|
||||
nact->callback();
|
||||
sys_wait_vsync();
|
||||
}
|
||||
SDL_StopTextInput();
|
||||
event_custom_handler = NULL;
|
||||
|
||||
ags_restoreRegion(saved_region, s.rect.x, s.rect.y);
|
||||
ags_updateArea(s.rect.x, s.rect.y, s.rect.w, s.rect.h);
|
||||
|
||||
input = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 kichikuou <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
|
||||
*
|
||||
*/
|
||||
#ifndef __EDITOR_H__
|
||||
#define __EDITOR_H__
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct inputstring_param;
|
||||
|
||||
bool edit_string(struct inputstring_param *);
|
||||
|
||||
#endif // __EDITOR_H__
|
||||
+69
-2
@@ -26,8 +26,9 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "portab.h"
|
||||
#include "nact.h"
|
||||
#include "menu.h"
|
||||
#include "editor.h"
|
||||
#include "font.h"
|
||||
#include "gfx_private.h"
|
||||
#include "modal.h"
|
||||
#include "utfsjis.h"
|
||||
@@ -45,6 +46,7 @@ struct string_state {
|
||||
char composing[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; // IME preedit text
|
||||
bool done;
|
||||
bool accepted;
|
||||
FontSpec font; // inline edit (menu_inputstring2) only
|
||||
};
|
||||
|
||||
// Heap buffer holding the string dialog's edited text. It must outlive the
|
||||
@@ -213,8 +215,73 @@ bool menu_inputstring(INPUTSTRING_PARAM *p) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// The inline text input (MJ command). It reuses the IME buffer handling
|
||||
// and the event handler of menu_inputstring.
|
||||
|
||||
// Look up an AGS color index in the active palette and return it as a mu_Color.
|
||||
static mu_Color palette_color(int index) {
|
||||
SDL_Color c = gfx_palette->colors[index];
|
||||
return (mu_Color){ c.r, c.g, c.b, 255 };
|
||||
}
|
||||
|
||||
static bool inline_edit_build(mu_Context *ctx, modal *modal) {
|
||||
const int EDIT_XMARGIN = 3;
|
||||
const int EDIT_YMARGIN = 2;
|
||||
|
||||
struct string_state *st = (struct string_state *)modal;
|
||||
int fh = st->param->h;
|
||||
mu_Rect rect = mu_rect(st->param->x, st->param->y,
|
||||
fh * st->param->max + EDIT_XMARGIN * 2, fh + EDIT_YMARGIN * 2);
|
||||
|
||||
// A frameless, title-less window just to set up microui's clip/draw context;
|
||||
// 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_Color bg = palette_color(nact->msg.WinBackgroundColor);
|
||||
mu_Color fg = palette_color(nact->msg.MsgFontColor);
|
||||
mu_Font font = (mu_Font)&st->font;
|
||||
|
||||
mu_draw_rect(ctx, rect, bg);
|
||||
|
||||
int x = rect.x + EDIT_XMARGIN;
|
||||
int y = rect.y + EDIT_YMARGIN;
|
||||
if (*str_buf) {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
if (st->base.cancelled) // Esc; accepted stays false
|
||||
st->done = true;
|
||||
return !st->done;
|
||||
}
|
||||
|
||||
bool menu_inputstring2(INPUTSTRING_PARAM *p) {
|
||||
return edit_string(p);
|
||||
struct string_state st = {
|
||||
.base = { .build = inline_edit_build, .handler = menu_string_handler,
|
||||
.no_dim = true },
|
||||
.param = p,
|
||||
.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();
|
||||
modal_run(&st.base);
|
||||
SDL_StopTextInput();
|
||||
|
||||
p->newstring = st.accepted ? str_buf : NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
// The number-input dialog.
|
||||
|
||||
+5
-4
@@ -36,6 +36,7 @@
|
||||
#define MODAL_FONT_SIZE 16
|
||||
|
||||
static mu_Context *ctx;
|
||||
static modal *current_modal;
|
||||
|
||||
static const FontSpec menu_font = { FONT_GOTHIC, FONT_WEIGHT_NORMAL, MODAL_FONT_SIZE };
|
||||
|
||||
@@ -206,8 +207,10 @@ static void modal_render(void) {
|
||||
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_BLEND);
|
||||
|
||||
// Dim the game behind the menu.
|
||||
SDL_SetRenderDrawColor(gfx_renderer, 0, 0, 0, 110);
|
||||
SDL_RenderFillRect(gfx_renderer, NULL);
|
||||
if (!current_modal->no_dim) {
|
||||
SDL_SetRenderDrawColor(gfx_renderer, 0, 0, 0, 110);
|
||||
SDL_RenderFillRect(gfx_renderer, NULL);
|
||||
}
|
||||
|
||||
mu_Command *cmd = NULL;
|
||||
while (mu_next_command(ctx, &cmd)) {
|
||||
@@ -237,8 +240,6 @@ static void modal_render(void) {
|
||||
SDL_RenderSetClipRect(gfx_renderer, NULL);
|
||||
}
|
||||
|
||||
static modal *current_modal;
|
||||
|
||||
static bool modal_event_trampoline(const SDL_Event *e) {
|
||||
return current_modal->handler(e, current_modal);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ typedef struct modal {
|
||||
bool (*handler)(const union SDL_Event *e, struct modal *modal);
|
||||
|
||||
bool cancelled; // set by the handler on Esc; build returns false
|
||||
bool no_dim; // don't dim the game behind the modal (for inline overlays)
|
||||
} modal;
|
||||
|
||||
// Default modal event handler. Use it as a modal's `handler` for simple dialogs, or
|
||||
|
||||
Reference in New Issue
Block a user