Files
MeowOS-SDK/meow_ui_ime.c
T

271 lines
11 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) 弹出键盘,自绘自分发直到确认(enter)/取消(esc|背景)。
* 非阻塞:meow_ui_text_input_async(ctx) 打开后返回;meow_ui_run 在 on_tick 间隙
* 随帧绘制并拦截触摸,app 用 meow_ime_active()/meow_ime_text() 轮询。
*
* 键盘覆盖屏幕底部约 300px;点按输入;键位 44×36(touch target 达标)。
*/
#include "meow_ui.h"
#include "meow_ui_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#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」与重复回车。 */
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: {
char c = g_shift && is_letter(k->ch) ? (char)toupper((unsigned char)k->ch) : k->ch;
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; g_ime_done = 1; break;
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()) {
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);
}
meow_ime_draw();
meow_ui_flush();
usleep(16000);
}
int result = g_ime_result;
meow_ime_close(result);
return result;
}