diff --git a/meow_ui.h b/meow_ui.h index 5813388..4c3da44 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -141,7 +141,8 @@ typedef struct meow_ime_ctx { void *ctx; } meow_ime_ctx; -/* 阻塞:弹出键盘直到确认/取消。返回 1=确认(buf 已回填),0=取消。 */ +/* 阻塞:弹出键盘直到关闭(ESC / 点背景取消);回车=只提交不关闭(内容经 on_change 实时同步)。 + * 关闭路径返回 0(取消);提交内容经 on_change 回调逐键同步,不依赖返回值回填 buf。 */ int meow_ui_text_input(meow_ime_ctx *ctx); /* 非阻塞:返回后 app 在 on_tick 里轮询 meow_ime_active()/meow_ime_text() */ @@ -443,7 +444,8 @@ int meow_btn_set_variant(meow_widget *w, int variant); /* 越界 clam meow_widget *meow_textfield_create(int x, int y, int w, int h, const char *placeholder, char *buf, int maxlen, meow_ime_mode mode); void meow_textfield_set_placeholder(meow_widget *w, const char *placeholder); -/* 聚焦并打开**阻塞** IME(同 meow_ui_text_input 内层轮询);返回 1=确认 0=取消;buf 已回填 */ +/* 聚焦并打开**阻塞** IME(同 meow_ui_text_input 内层轮询);回车不关闭键盘,ESC/点背景关闭。 + * 内容经 on_change 实时同步到 w->buf(返回值关闭路径为 0,提交不依赖返回值回填)。 */ int meow_textfield_activate(meow_widget *w); const char *meow_textfield_text(meow_widget *w); diff --git a/meow_ui_dialog.c b/meow_ui_dialog.c index bca80c6..5d06425 100644 --- a/meow_ui_dialog.c +++ b/meow_ui_dialog.c @@ -64,6 +64,7 @@ struct meow_dialog { int done; meow_ime_ctx ime; /* INPUT 型(拷贝) */ int has_ime; + meow_ime_ctx *ime_orig; /* INPUT:App 传入的原始 ctx(回写最终文本,App 关闭后可读回) */ }; /* 卡片几何:返回标题之后内容起点(行/按钮相对坐标)。 */ @@ -162,6 +163,30 @@ static void dlg_repaint(meow_dialog *d, const meow_theme *th) meow_ui_flush_region(g.dx, g.dy, g.dw, g.dh); } +/* M2.71-IME-fix:dialog INPUT 的 on_change——实时把 IME 文本回写进 d->ime.buf(dialog 内部拷贝), + * 并重绘卡片预览区,使用户边打字边看到当前文本。ESC/点背景(result=0)关闭后 d->ime.buf 仍是 + * 最新文本(否则 meow_ime_close(0) 不回写 buf → 文本丢失)。d->ime.buf 是 dialog 内部缓冲 + * (非 App 持有);App 的最终文本经 ime_orig 在关闭时回写(见 OK 按钮处理)。 */ +static void dlg_ime_change_cb(const char *text, void *ctx) +{ + meow_dialog *d = (meow_dialog *)ctx; + if (!d) return; + if (text) { + strncpy(d->ime.buf, text, sizeof d->ime.buf - 1); + d->ime.buf[sizeof d->ime.buf - 1] = 0; + } + const meow_theme *th = meow_theme_get(); + dlg_repaint(d, th); +} + +/* 准备好 dialog 的 IME 上下文:无论 App 传入(set_ime)或内部兜底(show 的默认 ctx), + * 都由 dialog 接管 on_change/ctx,以支持 dlg_ime_change_cb 的实时回写。 */ +static void dlg_prep_ime(meow_dialog *d) +{ + d->ime.on_change = dlg_ime_change_cb; + d->ime.ctx = d; +} + meow_dialog *meow_dialog_create(meow_dlg_t t, const char *title) { meow_dialog *d = (meow_dialog *)calloc(1, sizeof *d); @@ -183,7 +208,9 @@ void meow_dialog_set_ime(meow_dialog *d, meow_ime_ctx *ime) { if (!d || !ime) return; d->ime = *ime; + d->ime_orig = ime; /* M2.71-IME-fix:记住 App 原始 ctx,关闭时回写最终文本 */ d->has_ime = 1; + dlg_prep_ime(d); /* dialog 接管 on_change:实时回写 d->ime.buf + 重绘卡片 */ } void meow_dialog_close(meow_dialog *d) @@ -199,6 +226,8 @@ void meow_dialog_show(meow_dialog *d) if (d->type == MEOW_DLG_INPUT && !d->has_ime) { memset(&d->ime, 0, sizeof d->ime); d->ime.maxlen = 255; + d->ime_orig = NULL; /* 无 App 原始 ctx,关闭时不回写 */ + dlg_prep_ime(d); } meow_input_start(); meow_input_drain(); /* 排空残留触摸(goodix 长尾) */ @@ -236,6 +265,13 @@ void meow_dialog_show(meow_dialog *d) } else if (x >= cx && x < cx + g.btn_w) { /* OK */ if (d->type == MEOW_DLG_INPUT) { d->result = meow_ui_text_input(&d->ime); /* 阻塞编辑 */ + /* M2.71-IME-fix:回车只提交不关(键盘仍开);文本经 on_change 已实时 + * 写进 d->ime.buf。这里无论 result=0/1 都把最终文本回写 App 原始 ctx, + * 保证 ESC/点背景(result=0)关闭后 App 仍能读到编辑结果(不再丢失)。 */ + if (d->ime_orig) { + strncpy(d->ime_orig->buf, d->ime.buf, sizeof d->ime_orig->buf - 1); + d->ime_orig->buf[sizeof d->ime_orig->buf - 1] = 0; + } } else { d->result = (d->type == MEOW_DLG_CONFIRM) ? 1 : 1; } diff --git a/meow_ui_ime.c b/meow_ui_ime.c index 87a5a8e..ce15065 100644 --- a/meow_ui_ime.c +++ b/meow_ui_ime.c @@ -1,11 +1,15 @@ /* * meow_ui_ime.c — 内置触屏软键盘(B1.6)。仅英文 QWERTY / 数字,ASCII。 * - * 阻塞:meow_ui_text_input(ctx) 弹出键盘,自绘自分发直到确认(enter)/取消(esc|背景)。 + * 阻塞:meow_ui_text_input(ctx) 弹出键盘,自绘自分发直到关闭(ESC / 点背景取消)。 + * 回车(K_ENTER)只提交(mark g_ime_result=1)不关闭,用户可继续输入。 + * 阻塞期间周期性喂 App on_tick(meow_ui_feed_tick),动态内容(进度条/时钟)不卡死。 * 非阻塞:meow_ui_text_input_async(ctx) 打开后返回;meow_ui_run 在 on_tick 间隙 * 随帧绘制并拦截触摸,app 用 meow_ime_active()/meow_ime_text() 轮询。 * * 键盘覆盖屏幕底部约 300px;点按输入;键位 44×36(touch target 达标)。 + * M2.71-IME-fix:字母键面存小写("qwertyuiop"…),shift 开→大写、关→小写; + * 数字/符号键不随 shift 转换。旧版键面存大写 → 无论 shift 开关恒输出大写(bug)。 */ #include "meow_ui.h" #include "meow_ui_internal.h" @@ -15,6 +19,11 @@ #include #include +/* 阻塞 IME 循环周期性喂 App on_tick(实现在 meow_ui_input.c,读该 TU 的 g_events)。 + * 让 IME 打开期间 App 的动态内容(进度条/时钟)继续刷新;调用在 meow_ime_draw 之前, + * 保证 App 重绘先进 back_buf、IME 面板随后盖上去(IME 始终最上,不被 App 重绘覆盖)。 */ +extern void meow_ui_feed_tick(void); + #define IME_MAXKEYS 96 #define KB_TOP 420 /* 面板顶(预览行起点) */ #define GW 44 /* 键宽 */ @@ -94,10 +103,10 @@ static void build_kb(meow_ime_mode mode) int y = KB_TOP + 50; if (mode == MEOW_IME_ASCII) { add_ch_row(y, "1234567890"); - add_ch_row(y + GCOL, "QWERTYUIOP"); + add_ch_row(y + GCOL, "qwertyuiop"); /* 第 3 行:9 字母 + SHIFT */ { - const char *r = "ASDFGHJKL"; + const char *r = "asdfghjkl"; int n = (int)strlen(r); int total = 10 * GW + 9 * GAP; int x = (480 - total) / 2; @@ -106,7 +115,7 @@ static void build_kb(meow_ime_mode mode) } /* 第 4 行:7 字母 + BACKSPACE */ { - const char *r = "ZXCVBNM"; + const char *r = "zxcvbnm"; int n = (int)strlen(r); int total = 10 * GW + 9 * GAP; int x = (480 - total) / 2; @@ -121,8 +130,9 @@ static void build_kb(meow_ime_mode mode) add_key(x + GCOL, y + GCOL * 4, (GW + GAP) * 6, GH, K_SPACE, ' '); add_key(x + GCOL * 8, y + GCOL * 4, 2 * GW + GAP, GH, K_ENTER, '>'); } - } else { /* MEOW_IME_NUM:ESC(取消/关闭) + 空格 + 退格;单独一行回车(OK=提交并关闭)。 - * M2.71-bug7:修掉旧版「同格重叠的 BACK+ENTER」与重复回车。 */ + } else { /* MEOW_IME_NUM:ESC(取消/关闭) + 空格 + 退格;单独一行回车(OK=只提交不关闭)。 + * M2.71-bug7:修掉旧版「同格重叠的 BACK+ENTER」与重复回车。 + * M2.71-IME-fix:回车不再关闭(与 ASCII 一致),关闭走 ESC/背景。 */ add_ch_row(y, "1234567890"); add_ch_row(y + GCOL, "-/.:*#+="); { @@ -132,7 +142,7 @@ static void build_kb(meow_ime_mode mode) add_key(x + GCOL, y + GCOL * 2, (GW + GAP) * 7, GH, K_SPACE, ' '); /* 空格 */ add_key(x + GCOL * 8, y + GCOL * 2, (GW + GAP) * 2 - GAP, GH, K_BACK, '<'); /* 退格 */ } - /* 回车(OK=提交并关闭) */ + /* 回车(OK=只提交不关闭) */ { int total = 10 * GW + 9 * GAP; int x = (480 - total) / 2; @@ -148,7 +158,10 @@ static void apply_key(const struct kbkey *k) { switch (k->kind) { case K_CH: { - char c = g_shift && is_letter(k->ch) ? (char)toupper((unsigned char)k->ch) : k->ch; + /* M2.71-IME-fix:键面存小写(build_kb "qwertyuiop"…),shift 开→大写、关→小写; + * 数字/符号(非字母)不转换。旧版存大写 + else 分支恒返回大写 → 大小写不区分。 */ + char c = k->ch; + if (g_shift && is_letter(c)) c = (char)toupper((unsigned char)c); int len = (int)strlen(g_ime_buf); if (len < g_maxlen - 1) { g_ime_buf[len] = c; g_ime_buf[len + 1] = 0; @@ -169,7 +182,7 @@ static void apply_key(const struct kbkey *k) if (g_ime_ctx.on_change) g_ime_ctx.on_change(g_ime_buf, g_ime_ctx.ctx); } break; } - case K_ENTER: g_ime_result = 1; g_ime_done = 1; break; + case K_ENTER: g_ime_result = 1; break; /* M2.71-IME-fix:回车只提交(mark),不关键盘;关闭走 ESC/背景 */ case K_ESC: g_ime_result = 0; g_ime_done = 1; break; } } @@ -260,6 +273,10 @@ int meow_ui_text_input(meow_ime_ctx *ctx) meow_ui_activity_ping(); /* M2.71-bug5:IME 打字交互也重置息屏计时 */ if (e.type == MEOW_EV_TAP) meow_ime_handle_tap(e.a, e.b); } + /* M2.71-IME-fix:阻塞 IME 期间喂 App on_tick(动态内容继续刷,progress/时钟不卡死)。 + * 顺序要求:先喂 on_tick(App 重绘进 back_buf)→ 再 meow_ime_draw(IME 面板随后盖上去), + * 保证 IME 面板覆盖在最上,不被 App 的 d_paint_region 覆盖。 */ + meow_ui_feed_tick(); meow_ime_draw(); meow_ui_flush(); usleep(16000); diff --git a/meow_ui_input.c b/meow_ui_input.c index a7bbcb2..c19d3e2 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -445,3 +445,16 @@ void meow_ui_run(const meow_ui_events *ev) } } } + +/* M2.71-IME-fix:供 meow_ui_ime.c 的阻塞 IME 循环周期性喂 App on_tick。 + * 阻塞 IME 循环不经过 meow_ui_run,无法靠主循环喂 tick,故在此开独立入口; + * 与 meow_ui_run 共用 last_tick_ms(二者互斥运行,无竞争)。调用方在 meow_ime_draw 之前调用, + * 使 App 重绘先进 back_buf、IME 面板随后覆盖(IME 始终最上,不被 App 重绘盖住)。 */ +void meow_ui_feed_tick(void) +{ + if (!(g_events_set && g_events.on_tick)) return; + uint64_t now = now_ms(); + if (now - last_tick_ms < 16) return; + last_tick_ms = now; + g_events.on_tick(); +} diff --git a/test_ime_fix.c b/test_ime_fix.c new file mode 100644 index 0000000..ba170aa --- /dev/null +++ b/test_ime_fix.c @@ -0,0 +1,207 @@ +/* test_ime_fix.c — M2.71-IME-fix:宿主离屏验证三项修复(480x720,16bpp)。 + * ① shift 大小写正确:shift 关→小写('q'),开→大写('Q'); + * ② K_ENTER 只提交不关(meow_ime_active() 仍真),ESC 后关; + * ③ 阻塞 IME 循环喂 on_tick(动态内容重绘)且 IME 面板仍在最上 + * (On_tick 在 IME 面板区画红色哨兵行,IME 绘制后应盖回面板色,不被 App 覆盖)。 + * + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global): + * gcc -Wall -Wextra -o test_ime_fix test_ime_fix.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include +#include +#include +#include +#include +#include + +/* ASCII 键盘键位(build_kb:KB_TOP=420 → 首行 y=470;GW=44,GAP=4,GCOL=48,行左缘 x=2): + * row2 "qwertyuiop" y∈[518,554);row3 "asdfghjkl"+SHIFT y∈[566,602); + * row5 ESC+SPACE+ENTER y∈[662,698)。 */ +#define K_Q_X 20 +#define K_Q_Y 536 +#define K_SHIFT_X 450 +#define K_SHIFT_Y 584 +#define K_ENTER_X 430 +#define K_ENTER_Y 680 +#define K_ESC_X 20 +#define K_ESC_Y 680 + +/* ---- 像素/配色辅助(离屏 back_buf) ---- */ +static void rd_px(int x, int y, int *r, int *g, int *b) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + assert(bb && line > 0); + if (bpp == 16) { + uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2); + *r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2; + *g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4; + *b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2); + } else { + uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4); + *r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF; + } +} +static int near(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 14; } + +/* ---- ① 异步 shift 测试(直接 handle_tap,确定语义,无阻塞循环) ---- */ +static char g_echo[64] = ""; +static void ime_cb(const char *t, void *ctx) { (void)ctx; snprintf(g_echo, sizeof g_echo, "%s", t); } +static void test_shift(void) +{ + meow_ime_ctx c; memset(&c, 0, sizeof c); + c.mode = MEOW_IME_ASCII; c.maxlen = 32; c.on_change = ime_cb; + meow_ui_text_input_async(&c); + assert(meow_ime_active() == 1); + + meow_ime_handle_tap(K_Q_X, K_Q_Y); /* shift 关 → 'q' 小写 */ + assert(strcmp(meow_ime_text(), "q") == 0); + assert(strcmp(g_echo, "q") == 0); + assert(meow_ime_text()[0] >= 'a' && meow_ime_text()[0] <= 'z'); /* 明确小写 */ + + meow_ime_handle_tap(K_SHIFT_X, K_SHIFT_Y); /* shift 开 */ + meow_ime_handle_tap(K_Q_X, K_Q_Y); /* 'q' → 'Q' 大写 */ + + assert(strcmp(meow_ime_text(), "qQ") == 0); + assert(strcmp(g_echo, "qQ") == 0); + assert(meow_ime_text()[1] >= 'A' && meow_ime_text()[1] <= 'Z'); /* 明确大写 */ + printf("PASS shift: off='q'(lower), on='Q'(upper)\n"); fflush(stdout); + + meow_ime_handle_tap(K_ESC_X, K_ESC_Y); /* 关闭 */ + meow_ime_close(0); /* 复位(清 g_ime_done/active) */ + assert(meow_ime_active() == 0); +} + +/* ---- ②③ 阻塞 IME(经 meow_ui_run 驱动):K_ENTER 不关 / on_tick 喂 / IME 最上 ---- */ +static meow_widget *g_tf; +static char g_tfbuf[32] = ""; +static int g_tick = 0; +static int g_sentinel_painted = 0; +static int g_test_done = 0; + +static int g_active_after_enter = -1; /* K_ENTER 后 meow_ime_active()(期望 1) */ +static int g_active_after_esc = -1; /* ESC 后 meow_ime_active()(期望 0) */ +static int g_probe_r = -1, g_probe_g = -1, g_probe_b = -1; /* IME 面板区内取样(期望=面板色非哨兵红) */ +static char g_drv_text[64] = ""; /* shift 序列后 meow_ime_text()(期望 "qQ") */ + +/* 把 back_buf 写成 PPM(视觉目检 IME 面板渲染/最上;在 IME 空闲时调用,back_buf 稳定) */ +static void dump_ppm(const char *path) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + int W = meow_ui_width(), H = meow_ui_height(); + FILE *f = fopen(path, "wb"); + if (!f) return; + fprintf(f, "P6\n%d %d\n255\n", W, H); + for (int y = 0; y < H; y++) { + for (int x = 0; x < W; x++) { + int r, g, b; rd_px(x, y, &r, &g, &b); + fputc(r, f); fputc(g, f); fputc(b, f); + } + } + fclose(f); +} + +/* App on_tick:模拟 demo 动态重绘。IME 打开时先在 IME 面板区内画一条红色哨兵行, + * 再由 meow_ime_draw 盖回面板色 → 验证「喂 on_tick 后 IME 仍最上」的绘制顺序。 */ +static void host_tick(void) +{ + g_tick++; + if (meow_ime_active() && !g_sentinel_painted) { + g_sentinel_painted = 1; + meow_ui_fill_rect(0, 500, 480, 8, 0xFF0000); /* 哨兵行:App 内容拟 IME 面板区内 */ + meow_ui_flush_region(0, 500, 480, 8); + } + if (g_test_done) meow_ui_request_exit(); +} + +/* App on_tap:命中 TextField → 打开**阻塞** IME(同 hello demo 路径) */ +static void host_tap(int x, int y) +{ + (void)x; (void)y; + if (g_tf && meow_widget_hit(g_tf, x, y)) meow_textfield_activate(g_tf); +} + +static void *driver(void *arg) +{ + (void)arg; + usleep(600000); /* 等 meow_ui_run 起动(startup cooldown=500ms) */ + meow_input_inject(MEOW_EV_TAP, 150, 322); /* 点 TextField → 阻塞 IME */ + usleep(160000); + meow_input_inject(MEOW_EV_TAP, K_Q_X, K_Q_Y); /* shift 关 → 'q' */ + usleep(90000); + meow_input_inject(MEOW_EV_TAP, K_SHIFT_X, K_SHIFT_Y); /* shift 开 */ + usleep(60000); + meow_input_inject(MEOW_EV_TAP, K_Q_X, K_Q_Y); /* shift 开 → 'Q' → 缓冲 "qQ" */ + usleep(90000); + strncpy(g_drv_text, meow_ime_text(), sizeof g_drv_text - 1); + g_drv_text[sizeof g_drv_text - 1] = 0; + + meow_input_inject(MEOW_EV_TAP, K_ENTER_X, K_ENTER_Y); /* K_ENTER:只提交不关 */ + usleep(160000); + g_active_after_enter = meow_ime_active(); /* 期望 1(键盘仍开) */ + + rd_px(240, 500, &g_probe_r, &g_probe_g, &g_probe_b); /* IME 面板区内取样 */ + dump_ppm("/tmp/ime_panel.ppm"); /* 目检:IME 面板渲染 + 盖住哨兵 */ + printf("[drv] text=%s active_after_enter=%d probe=%02X%02X%02X sentineld=%d\n", + g_drv_text, g_active_after_enter, g_probe_r, g_probe_g, g_probe_b, + g_sentinel_painted); fflush(stdout); + + meow_input_inject(MEOW_EV_TAP, K_ESC_X, K_ESC_Y); /* ESC → 关闭 */ + usleep(160000); + g_active_after_esc = meow_ime_active(); /* 期望 0 */ + g_test_done = 1; + return NULL; +} + +int main(void) +{ + setenv("MEOW_UI_DUMP", "/tmp/ime_fix.ppm", 1); + meow_ui_opts o = { 480, 720 }; + assert(meow_ui_init(&o) == 0); + const meow_theme *th = meow_theme_get(); + + test_shift(); /* ① */ + + /* ②③ 在 meow_ui_run 里驱动(g_events 仅 meow_ui_run 能设置) */ + g_tf = meow_textfield_create(10, 300, 300, 44, "Type here...", g_tfbuf, 32, MEOW_IME_ASCII); + meow_ui_events ev; memset(&ev, 0, sizeof ev); + ev.on_tap = host_tap; + ev.on_tick = host_tick; + pthread_t thd; pthread_create(&thd, NULL, driver, NULL); + meow_ui_run(&ev); + pthread_join(thd, NULL); + + /* 断言 ② */ + printf("[assert] active_after_enter=%d (expect 1), active_after_esc=%d (expect 0)\n", + g_active_after_enter, g_active_after_esc); + assert(g_active_after_enter == 1); /* K_ENTER 不关 */ + assert(g_active_after_esc == 0); /* ESC 关 */ + + /* 断言 ①(阻塞路径经 on_change 实时同步到字段) */ + printf("[assert] blocking text=%s (expect \"qQ\"), tf=%s (expect \"qQ\")\n", + g_drv_text, meow_textfield_text(g_tf)); + assert(strcmp(g_drv_text, "qQ") == 0); + assert(strcmp(meow_textfield_text(g_tf), "qQ") == 0); + + /* 断言 ③:on_tick 在 IME 打开期间被喂过(哨兵已画)+ IME 面板盖住哨兵 */ + int pr = (th->surfaceContainerHighest >> 16) & 0xFF; + int pg = (th->surfaceContainerHighest >> 8) & 0xFF; + int pb = (th->surfaceContainerHighest) & 0xFF; + printf("[assert] sentinel_painted=%d probe=%02X%02X%02X panel=%02X%02X%02X\n", + g_sentinel_painted, g_probe_r, g_probe_g, g_probe_b, pr, pg, pb); + assert(g_sentinel_painted == 1); /* on_tick 在 IME 期间被调(重绘过) */ + assert(g_tick > 0); + assert(near(g_probe_r, pr) && near(g_probe_g, pg) && near(g_probe_b, pb)); /* IME 面板最上 */ + /* 哨兵红 0xFF0000 与面板色显著不同,确保上面断言有判别力(面板色不应近纯红) */ + assert(!(near(pr, 0xFF) && near(pg, 0x00) && near(pb, 0x00))); + + printf("ALL PASS\n"); + meow_ui_shutdown(); + return 0; +} diff --git a/test_sdk_fixes.c b/test_sdk_fixes.c index bd7c42b..e2a3401 100644 --- a/test_sdk_fixes.c +++ b/test_sdk_fixes.c @@ -3,7 +3,8 @@ * bug4 disabled 按钮圆角外无灰方块(角点 = 页面背景而非叠暗); * bug1 圆形进度环带确实画到 = 半径+thk/2 处(bbox 与真实像素一致,漏刷修复的几何前提); * bug3 modal(Dialog)open→cancel 后背景还原(scrim 不累积/无残影); - * bug7 IME 回显实时同步(on_change 随键入触发)+ 关闭后键盘区背景还原。 + * bug7 IME 回显实时同步(on_change 随键入触发)+ 关闭后键盘区背景还原; + * fix1 dialog INPUT 在 ESC/背景(result=0)关闭后文本不丢(M2.71-IME-fix 新语义:回车只提交不关)。 * * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global): * gcc -Wall -o test_sdk_fixes test_sdk_fixes.c test_host_stub.c \ @@ -39,6 +40,7 @@ static int near(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 12 /* ---- 为阻塞模态/IME 在后台注入事件 ---- */ static int g_inj_tap_x = 0, g_inj_tap_y = 0, g_inj_delay = 0; +static int K_DLG_OK_X = 0, K_DLG_OK_Y = 0; /* dialog INPUT 的 OK 按钮中心(运行时算) */ static void *inject_thread(void *arg) { (void)arg; @@ -122,10 +124,14 @@ static void test_ime_echo_and_restore(void) /* --- 异步:验『回显/输入框实时同步(on_change 随键入触发)』--- */ meow_ui_text_input_async(&c); assert(meow_ime_active() == 1); - meow_input_inject(MEOW_EV_TAP, 20, 580); /* ASCII 第3行第1键 = 'A'(shift 关 → 缓冲 'A') */ + meow_input_inject(MEOW_EV_TAP, 20, 580); /* ASCII 行3第1键 = 'a'(shift 关 → 小写 'a') */ meow_ime_handle_tap(20, 580); - assert(strcmp(g_echo, "A") == 0 && strcmp(meow_ime_text(), "A") == 0); - printf("[bug7] ime echo live-sync after 'A' -> \"%s\" (on_change fired) PASS\n", g_echo); fflush(stdout); + assert(strcmp(g_echo, "a") == 0 && strcmp(meow_ime_text(), "a") == 0); + printf("[bug7] ime echo live-sync after 'a' -> \"%s\" (shift-off lower) PASS\n", g_echo); fflush(stdout); + meow_ime_handle_tap(450, 584); /* SHIFT 开 */ + meow_ime_handle_tap(20, 580); /* 'a' → 'A' 大写 */ + assert(strcmp(g_echo, "aA") == 0 && strcmp(meow_ime_text(), "aA") == 0); + printf("[bug7] shift-on -> 'A' (echo \"%s\") PASS\n", g_echo); fflush(stdout); meow_input_inject(MEOW_EV_TAP, 20, 680); /* ESC(X) → 取消 */ meow_ime_handle_tap(20, 680); assert(meow_ime_active() == 0); @@ -143,6 +149,68 @@ static void test_ime_echo_and_restore(void) printf("PASS\n"); } +/* ---- 回归①:dialog INPUT 在 ESC/背景(result=0)关闭后文本不丢(M2.71-IME-fix 新语义)。 + * 复现 dialog.c dlg_input_ok_center → 复用 dialog 的 OK 按钮中心几何(与 dlg_calc 同一公式)。 */ +static void dialog_input_ok_center(int *ox, int *oy) +{ + const meow_theme *th = meow_theme_get(); + int W = meow_ui_width(), H = meow_ui_height(); + int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0; + int pad = 24, btn = 44; + int tH = meow_ui_text_height(th->font_title); + int bH = meow_ui_text_height(th->font_body); + int content = bH + 8; /* INPUT(无 items)→ 1 行正文预览 */ + int dw = 348; if (dw > W - 40) dw = W - 40; + int dx = (W - dw) / 2; + int dh = pad + tH + 12 + content + pad + (16 + btn); + int avail = H - sb - 20; + if (dh > avail) dh = avail; + int dy = (H - dh) / 2; if (dy < sb) dy = sb; + int row_y = dy + pad + tH + 12; + int btn_y = row_y + content + 16; + int btn_w = (dw - 2 * pad - 12) / 2; + int cx = dx + pad + btn_w + 12; /* OK = 右侧填充按钮左缘 */ + *ox = cx + btn_w / 2; + *oy = btn_y + btn / 2; +} + +static void *dlg_input_driver(void *arg) +{ + (void)arg; + usleep(40000); /* 等 dialog show 起(遮罩+卡片已绘) */ + meow_input_inject(MEOW_EV_TAP, K_DLG_OK_X, K_DLG_OK_Y); /* 点 OK → 打开阻塞 IME */ + usleep(140000); + meow_input_inject(MEOW_EV_TAP, 20, 584); /* 行3第1键 'a'(shift 关) */ + usleep(140000); + meow_input_inject(MEOW_EV_TAP, 20, 680); /* ESC → 关 IME → dialog 随之关 */ + return NULL; +} + +static void test_dialog_input_preserve(void) +{ + const meow_theme *th = meow_theme_get(); + meow_ui_clear(th->bg); + meow_ui_flush(); + meow_ime_ctx ime; memset(&ime, 0, sizeof ime); + ime.mode = MEOW_IME_ASCII; ime.maxlen = 64; + strcpy(ime.buf, ""); + meow_dialog *d = meow_dialog_create(MEOW_DLG_INPUT, "Name"); + meow_dialog_set_ime(d, &ime); + + dialog_input_ok_center(&K_DLG_OK_X, &K_DLG_OK_Y); + pthread_t thd; pthread_create(&thd, NULL, dlg_input_driver, NULL); + meow_dialog_show(d); + pthread_join(thd, NULL); + + /* ESC 关闭(result=0)→ 文本应保留:App 原始 ctx.buf 应含 'a'(不再丢失),且是取消语义 */ + printf("[fix1] dialog INPUT after ESC: result=%d App.ime.buf=\"%s\" (expect 0, \"a\")\n", + meow_dialog_result(d), ime.buf); + assert(meow_dialog_result(d) == 0); + assert(strcmp(ime.buf, "a") == 0); + printf("[fix1] dialog INPUT text preserved on ESC PASS\n"); + meow_dialog_free(d); +} + int main(void) { setenv("MEOW_UI_DUMP", "/tmp/fixes.ppm", 1); @@ -152,6 +220,7 @@ int main(void) test_circular_extent(); test_modal_restore(); test_ime_echo_and_restore(); + test_dialog_input_preserve(); meow_ui_shutdown(); printf("ALL PASS\n"); return 0;