diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8cba11e..414b2be 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/editor.c b/src/editor.c deleted file mode 100644 index 5a7c1b5..0000000 --- a/src/editor.c +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2020 - * - * 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 -#include - -#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; -} diff --git a/src/editor.h b/src/editor.h deleted file mode 100644 index ac8583d..0000000 --- a/src/editor.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2025 kichikuou - * - * 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 - -struct inputstring_param; - -bool edit_string(struct inputstring_param *); - -#endif // __EDITOR_H__ diff --git a/src/input_modal.c b/src/input_modal.c index 7e2faae..7c6f083 100644 --- a/src/input_modal.c +++ b/src/input_modal.c @@ -26,8 +26,9 @@ #include #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. diff --git a/src/modal.c b/src/modal.c index 78bdb3b..3e5d1a0 100644 --- a/src/modal.c +++ b/src/modal.c @@ -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); } diff --git a/src/modal.h b/src/modal.h index 8e0791d..9c3a80d 100644 --- a/src/modal.h +++ b/src/modal.h @@ -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