• 全新单提交:清除含敏感信息的旧历史(sudo 口令、内网 IP、SSH 密钥路径、热点 SSID、设备 MAC) • 本地开发文档/测试记录/构建脚本全部移入 gitignored 的 devdocs/ 与 scripts/build/(不入库) • 保留:MeowOS 源码(launcher/watchdog/bootmenu/meow_svcd/SDK)、项目规范文档、mips-repo 增补组件:SDK 统一日志+通知 API、meow_svcd 服务管理器、mips-repo 软件仓库
236 lines
8.3 KiB
C
236 lines
8.3 KiB
C
/*
|
||
* 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 */
|
||
|
||
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 */
|
||
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 * 9, y + GCOL * 2, GW, GH, K_BACK, '<');
|
||
add_key(x + GCOL * 9, y + GCOL * 2, GW, GH, K_ENTER, '>'); /* 覆盖:回车 */
|
||
}
|
||
/* 单独一行回车 */
|
||
{
|
||
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();
|
||
/* 面板 */
|
||
meow_ui_fill_rect(0, KB_TOP, 480, 720 - KB_TOP, 0x1C1C22);
|
||
/* 预览行 */
|
||
meow_ui_fill_rect(0, KB_TOP, 480, 42, 0x2A2A33);
|
||
char preview[256];
|
||
snprintf(preview, sizeof preview, "%s_", g_ime_buf);
|
||
meow_ui_text(14, KB_TOP + 9, preview, th->font_body, 0xFFFFFF);
|
||
/* 键 */
|
||
for (int i = 0; i < g_nkeys; i++) {
|
||
const struct kbkey *k = &g_keys[i];
|
||
int is_fn = (k->kind != K_CH);
|
||
uint32_t bg = is_fn ? 0x33333C : 0xFFFFFF;
|
||
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);
|
||
uint32_t fg = is_fn ? 0xFFFFFF : 0x1A1A1E;
|
||
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 { 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;
|
||
}
|
||
|
||
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);
|
||
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)) {
|
||
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;
|
||
}
|