Files
MeowOS-For-R3P2/patch_v2/sdk/meow_ui_ime.c
T

293 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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;
}