meow_svc_perf fa073bf9→7ea196de; meow_ui.a d814d0ed→ece0ecdb; hello 2ce09466→f45ba051; launcher 9ca4c5e8→bd37c8b0(重链) 宿主验证: test_ime_fix/test_p1p2p3_repro/test_meow_ui_widgets 全 PASS; perf_host all 输出经 python json 校验合法
293 lines
13 KiB
C
293 lines
13 KiB
C
/*
|
||
* meow_ui_ime.c — 内置触屏软键盘(B1.6)。仅英文 QWERTY / 数字,ASCII。
|
||
*
|
||
* 阻塞: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"
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <ctype.h>
|
||
#include <unistd.h>
|
||
|
||
/* 阻塞 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 /* 键宽 */
|
||
#define GH 36 /* 键高 */
|
||
#define GAP 4 /* 键间距 */
|
||
#define GCOL 48 /* 列步进 = GW+GAP */
|
||
|
||
/* M2.71-bug7:键盘区背景快照/恢复。旧实现关闭后键盘像素留在屏底(残影)。此处打开前拍下
|
||
* 键盘区 [KB_TOP,H),关闭后回填 + 上屏 → 无残影。 */
|
||
static unsigned char *g_ime_cap = NULL;
|
||
static int g_ime_cap_line = 0, g_ime_cap_h = 0;
|
||
|
||
static void ime_capture(void)
|
||
{
|
||
unsigned char *bb = meow_ui_back_buf();
|
||
int line = meow_ui_back_buf_pitch();
|
||
int h = meow_ui_height() - KB_TOP;
|
||
if (!bb || line <= 0 || h <= 0) { g_ime_cap = NULL; return; }
|
||
size_t n = (size_t)line * (size_t)h;
|
||
if (!g_ime_cap || g_ime_cap_line != line || g_ime_cap_h != h) {
|
||
free(g_ime_cap);
|
||
g_ime_cap = (unsigned char *)malloc(n);
|
||
g_ime_cap_line = line; g_ime_cap_h = h;
|
||
}
|
||
if (g_ime_cap) memcpy(g_ime_cap, bb + (size_t)line * KB_TOP, n);
|
||
}
|
||
|
||
static void ime_restore(void)
|
||
{
|
||
unsigned char *bb = meow_ui_back_buf();
|
||
if (!g_ime_cap || !bb) return;
|
||
size_t n = (size_t)g_ime_cap_line * (size_t)g_ime_cap_h;
|
||
memcpy(bb + (size_t)g_ime_cap_line * KB_TOP, g_ime_cap, n);
|
||
meow_gfx_mark_dirty(0, KB_TOP, meow_ui_width(), g_ime_cap_h);
|
||
meow_ui_flush();
|
||
}
|
||
|
||
typedef enum { K_CH, K_SHIFT, K_SPACE, K_BACK, K_ENTER, K_ESC } kkind;
|
||
|
||
struct kbkey { int x, y, w, h; kkind kind; char ch; };
|
||
|
||
static struct kbkey g_keys[IME_MAXKEYS];
|
||
static int g_nkeys = 0;
|
||
|
||
static int g_ime_active = 0;
|
||
static int g_ime_done = 0;
|
||
static int g_ime_result = 0;
|
||
static int g_shift = 0;
|
||
|
||
static meow_ime_ctx g_ime_ctx; /* 拷贝(on_change 回调用) */
|
||
static meow_ime_ctx *g_ime_orig = NULL; /* 应用真实 ctx(buf 回填用) */
|
||
static char g_ime_buf[256];
|
||
static int g_maxlen = 255;
|
||
|
||
/* ---- 布局 ---- */
|
||
static void add_key(int x, int y, int w, int h, kkind k, char ch)
|
||
{
|
||
if (g_nkeys >= IME_MAXKEYS) return;
|
||
g_keys[g_nkeys].x = x; g_keys[g_nkeys].y = y;
|
||
g_keys[g_nkeys].w = w; g_keys[g_nkeys].h = h;
|
||
g_keys[g_nkeys].kind = k; g_keys[g_nkeys].ch = ch;
|
||
g_nkeys++;
|
||
}
|
||
|
||
/* 加入一行居中字符键;返回该行开始 x */
|
||
static void add_ch_row(int y, const char *chars)
|
||
{
|
||
int n = (int)strlen(chars);
|
||
int total = n * GW + (n - 1) * GAP;
|
||
int x = (480 - total) / 2;
|
||
for (int i = 0; i < n; i++) add_key(x + i * GCOL, y, GW, GH, K_CH, chars[i]);
|
||
}
|
||
|
||
static void build_kb(meow_ime_mode mode)
|
||
{
|
||
g_nkeys = 0;
|
||
int y = KB_TOP + 50;
|
||
if (mode == MEOW_IME_ASCII) {
|
||
add_ch_row(y, "1234567890");
|
||
add_ch_row(y + GCOL, "qwertyuiop");
|
||
/* 第 3 行:9 字母 + SHIFT */
|
||
{
|
||
const char *r = "asdfghjkl";
|
||
int n = (int)strlen(r);
|
||
int total = 10 * GW + 9 * GAP;
|
||
int x = (480 - total) / 2;
|
||
for (int i = 0; i < n; i++) add_key(x + i * GCOL, y + GCOL * 2, GW, GH, K_CH, r[i]);
|
||
add_key(x + 9 * GCOL, y + GCOL * 2, GW, GH, K_SHIFT, '^');
|
||
}
|
||
/* 第 4 行:7 字母 + BACKSPACE */
|
||
{
|
||
const char *r = "zxcvbnm";
|
||
int n = (int)strlen(r);
|
||
int total = 10 * GW + 9 * GAP;
|
||
int x = (480 - total) / 2;
|
||
for (int i = 0; i < n; i++) add_key(x + i * GCOL, y + GCOL * 3, GW, GH, K_CH, r[i]);
|
||
add_key(x + 7 * GCOL, y + GCOL * 3, (GW + GAP) * 3, GH, K_BACK, '<');
|
||
}
|
||
/* 第 5 行:ESC + SPACE(宽) + ENTER */
|
||
{
|
||
int total = 10 * GW + 9 * GAP;
|
||
int x = (480 - total) / 2;
|
||
add_key(x, y + GCOL * 4, GW, GH, K_ESC, 'X');
|
||
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」与重复回车。
|
||
* M2.71-IME-fix:回车不再关闭(与 ASCII 一致),关闭走 ESC/背景。 */
|
||
add_ch_row(y, "1234567890");
|
||
add_ch_row(y + GCOL, "-/.:*#+=");
|
||
{
|
||
int total = 10 * GW + 9 * GAP;
|
||
int x = (480 - total) / 2;
|
||
add_key(x, y + GCOL * 2, GW, GH, K_ESC, 'X'); /* 取消/关闭(丢弃) */
|
||
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=只提交不关闭) */
|
||
{
|
||
int total = 10 * GW + 9 * GAP;
|
||
int x = (480 - total) / 2;
|
||
add_key(x + GCOL * 4, y + GCOL * 3, 2 * GW + GAP, GH, K_ENTER, '>');
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ---- 编辑动作 ---- */
|
||
static int is_letter(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); }
|
||
|
||
static void apply_key(const struct kbkey *k)
|
||
{
|
||
switch (k->kind) {
|
||
case 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;
|
||
if (g_ime_ctx.on_change) g_ime_ctx.on_change(g_ime_buf, g_ime_ctx.ctx);
|
||
}
|
||
break;
|
||
}
|
||
case K_SHIFT: g_shift = !g_shift; break;
|
||
case K_SPACE: {
|
||
int len = (int)strlen(g_ime_buf);
|
||
if (len < g_maxlen - 1) { g_ime_buf[len] = ' '; g_ime_buf[len + 1] = 0;
|
||
if (g_ime_ctx.on_change) g_ime_ctx.on_change(g_ime_buf, g_ime_ctx.ctx); }
|
||
break;
|
||
}
|
||
case K_BACK: {
|
||
int len = (int)strlen(g_ime_buf);
|
||
if (len > 0) { g_ime_buf[len - 1] = 0;
|
||
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; break; /* M2.71-IME-fix:回车只提交(mark),不关键盘;关闭走 ESC/背景 */
|
||
case K_ESC: g_ime_result = 0; g_ime_done = 1; break;
|
||
}
|
||
}
|
||
|
||
/* ---- 绘制 ---- */
|
||
void meow_ime_draw(void)
|
||
{
|
||
if (!g_ime_active) return;
|
||
const meow_theme *th = meow_theme_get();
|
||
/* M2.71-bug7:配色全走 meow_theme(深/浅主题自动切),不再硬编码黑底白键。 */
|
||
meow_ui_fill_rect(0, KB_TOP, 480, 720 - KB_TOP, th->surfaceContainerHighest); /* 面板 */
|
||
meow_ui_fill_rect(0, KB_TOP, 480, 42, th->surfaceContainerHigh); /* 回显条 */
|
||
char preview[260]; /* g_ime_buf(≤255) + '_' + NUL → 需 ≥257,防截断告警 */
|
||
/* 回显=用户已输入的字符(live echo,非候选/联想),随键入实时刷新。 */
|
||
snprintf(preview, sizeof preview, "%s_", g_ime_buf);
|
||
meow_ui_text(14, KB_TOP + 9, preview, th->font_body, th->onSurface);
|
||
/* 键 */
|
||
for (int i = 0; i < g_nkeys; i++) {
|
||
const struct kbkey *k = &g_keys[i];
|
||
uint32_t bg, fg;
|
||
if (k->kind == K_ENTER) { bg = th->primary; fg = th->onPrimary; } /* OK=提交 */
|
||
else if (k->kind == K_CH) { bg = th->surfaceContainerLow; fg = th->onSurface; } /* 字符键 */
|
||
else { bg = th->primaryContainer; fg = th->onPrimaryContainer; } /* 功能键(ESC/SHIFT/空格/退格) */
|
||
meow_gfx_fill_round(k->x, k->y, k->w, k->h, 8, bg);
|
||
char label[2] = { k->ch, 0 };
|
||
if (is_letter(k->ch)) label[0] = g_shift ? (char)toupper((unsigned char)k->ch)
|
||
: (char)tolower((unsigned char)k->ch);
|
||
if (k->kind == K_SPACE) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "SPACE", th->font_body, fg); }
|
||
else if (k->kind == K_ENTER) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "OK", 20, fg); }
|
||
else if (k->kind == K_ESC) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "X", 20, fg); }
|
||
else { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, label, 20, fg); }
|
||
}
|
||
}
|
||
|
||
/* ---- 命中 ---- */
|
||
int meow_ime_handle_tap(int x, int y)
|
||
{
|
||
if (!g_ime_active || g_ime_done) return 0;
|
||
if (y < KB_TOP) { g_ime_result = 0; g_ime_done = 1; return 1; } /* 背景点=取消 */
|
||
for (int i = 0; i < g_nkeys; i++) {
|
||
const struct kbkey *k = &g_keys[i];
|
||
if (x >= k->x && x < k->x + k->w && y >= k->y && y < k->y + k->h) {
|
||
apply_key(k);
|
||
return 1;
|
||
}
|
||
}
|
||
return 1; /* 面板空隙:消费但不做 */
|
||
}
|
||
|
||
/* ---- 对外 ---- */
|
||
int meow_ime_active(void) { return g_ime_active && !g_ime_done; }
|
||
const char *meow_ime_text(void) { return g_ime_buf; }
|
||
|
||
void meow_ime_close(int result)
|
||
{
|
||
if (!g_ime_active) return;
|
||
if (result && g_ime_orig) {
|
||
strncpy(g_ime_orig->buf, g_ime_buf, sizeof g_ime_orig->buf - 1);
|
||
g_ime_orig->buf[sizeof g_ime_orig->buf - 1] = 0;
|
||
}
|
||
g_ime_active = 0; g_ime_done = 0;
|
||
ime_restore(); /* M2.71-bug7:关闭还原键盘区背景,无残影 */
|
||
}
|
||
|
||
void meow_ui_text_input_async(meow_ime_ctx *ctx)
|
||
{
|
||
if (!ctx) return;
|
||
g_ime_orig = ctx;
|
||
g_ime_ctx = *ctx; /* 拷贝(on_change 用) */
|
||
strncpy(g_ime_buf, ctx->buf, sizeof g_ime_buf - 1);
|
||
g_ime_buf[sizeof g_ime_buf - 1] = 0;
|
||
g_maxlen = ctx->maxlen > 0 ? ctx->maxlen : 255;
|
||
g_shift = 0; g_ime_result = 0; g_ime_done = 0; g_ime_active = 1;
|
||
build_kb(ctx->mode);
|
||
ime_capture(); /* M2.71-bug7:打开前拍下键盘区背景(关闭即还原) */
|
||
meow_input_start();
|
||
meow_input_drain();
|
||
}
|
||
|
||
int meow_ui_text_input(meow_ime_ctx *ctx)
|
||
{
|
||
if (!ctx) return 0;
|
||
meow_ui_text_input_async(ctx);
|
||
/* 阻塞循环:直到 done;on_change 由 handle_tap 触发 */
|
||
while (meow_ime_active()) {
|
||
/* flush 挂起:整个周期内(事件处理触发的 on_change 重绘 + App on_tick 重绘)
|
||
* 只记脏不上屏;IME 画完后一次 flush 原子上屏——否则 on_tick 的 invalidate
|
||
* 会先把进度条等区域刷上屏盖住键盘,IME 随后再盖回,同区一帧两刷→频闪。 */
|
||
meow_gfx_flush_hold(1);
|
||
meow_input_ev e;
|
||
while (meow_input_pop(&e)) {
|
||
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_gfx_flush_hold(0);
|
||
meow_ime_draw();
|
||
meow_ui_flush();
|
||
usleep(16000);
|
||
}
|
||
int result = g_ime_result;
|
||
meow_ime_close(result);
|
||
return result;
|
||
}
|