fix(sdk/hello): M2.71控件真机bug批次 — 圆形进度脏区+整环修复/Slider拖动/Dialog·Sheet背景快照scrim不叠黑/Disabled圆角α/点击重置息屏/滑动跟手/IME残影·键位·主题配色/List选中点击 (agent-162)

This commit is contained in:
MeowOS committed 2026-08-31 00:19:40 +08:00
1 parent b12404a986
commit b8c36bd33c
14 files changed
+554 -79

No files matched your search

+12
View File
@@ -158,6 +158,11 @@ typedef struct meow_ui_events {
void (*on_swipe)(int dir, int dx, int dy); /* 左滑=返回(左缘起点+抬手触发) */
void (*on_tick)(void); /* 空闲周期回调(动画/时钟) */
void (*on_swipe_progress)(int x_start, int dx);/* 左缘返回手势过程实时回调;x_start=按下X, dx=当前X-按下X */
/* M2.71-bug2/6:拖动过程回调(跟手)。on_touch_down=按下后首个有效坐标(起点);
* on_touch_move=拖动中坐标变化(坐标变了才回调)。用于 slider 拖动 / 列表/页面滚动跟手。
* 均在 IME 弹出或模态阻塞时被吞(键盘/模态优先喂)。可 NULL。 */
void (*on_touch_down)(int x, int y);
void (*on_touch_move)(int x, int y);
} meow_ui_events;
void meow_ui_run(const meow_ui_events *ev); /* 阻塞:事件循环,直到 meow_ui_request_exit */
@@ -500,6 +505,13 @@ int meow_list_max_scroll(meow_widget *w); /* max(0, count*item -
void meow_list_scroll_by(meow_widget *w, int dy); /* 相对滚动(App on_swipe 用) */
int meow_list_set_item_h(meow_widget *w, int item); /* 改行高/列宽 */
int meow_list_hit_index(meow_widget *w, int x, int y); /* 命中项下标(含滚动换算);-1=未命中项/越界 */
/* M2.71-bug8:列表行选中 + 行点击回调。选中行已有高亮(现有绘制读 w->sel)。
* meow_list_tap_at:命中一行则置选中并回调 tap_cb(若设),返回该行 idx;未命中返回 -1。 */
void meow_list_set_selected(meow_widget *w, int idx); /* 选中行(越界 clamp);标脏 */
int meow_list_selected(meow_widget *w);
typedef void (*meow_list_tap_fn)(int idx, void *ctx);
void meow_list_set_on_tap(meow_widget *w, meow_list_tap_fn cb, void *ctx); /* 命中行→回调 */
int meow_list_tap_at(meow_widget *w, int x, int y); /* 命中行:置选中+回调,返回 idx/-1 */
/* ============================================================ */
/* §5.8.6 模态(App 层,非系统叠加):Dialog / BottomSheet
+82 -23
View File
@@ -20,6 +20,39 @@
#define MEOW_MODAL_ROW 48
#define MEOW_MODAL_BTN 44
/* M2.71-bug3:模态(Dialog/BottomSheet)背景快照/恢复。
* 旧实现直接往 back_buf 画 scrim+卡片并 flush:①每次 open 再现叠一层 α150 遮罩(叠加变黑);
* ②关闭后 scrim+卡片像素留在屏上(残影);③要整屏刷新才盖掉。此处 open 前把整帧背景拍下,
* 关闭后回填 + 整帧上屏 → scrim 每次只叠一层、关闭即还原、无残留。 */
static unsigned char *g_modal_cap = NULL;
static int g_modal_cap_line = 0, g_modal_cap_h = 0, g_modal_cap_bpp = 0;
static void modal_capture(void)
{
unsigned char *bb = meow_ui_back_buf();
int line = meow_ui_back_buf_pitch();
int h = meow_ui_height();
int bpp = meow_ui_back_buf_bpp();
if (!bb || line <= 0 || h <= 0) { g_modal_cap = NULL; return; }
size_t n = (size_t)line * (size_t)h;
if (!g_modal_cap || g_modal_cap_line != line || g_modal_cap_h != h || g_modal_cap_bpp != bpp) {
free(g_modal_cap);
g_modal_cap = (unsigned char *)malloc(n);
g_modal_cap_line = line; g_modal_cap_h = h; g_modal_cap_bpp = bpp;
}
if (g_modal_cap) memcpy(g_modal_cap, bb, n);
}
static void modal_restore(void)
{
unsigned char *bb = meow_ui_back_buf();
if (!g_modal_cap || !bb) return;
size_t n = (size_t)g_modal_cap_line * (size_t)g_modal_cap_h;
memcpy(bb, g_modal_cap, n);
meow_gfx_mark_full();
meow_ui_flush();
}
/* ---- Dialog ---- */
struct meow_dialog {
meow_dlg_t type;
@@ -62,58 +95,73 @@ static void dlg_calc(meow_dialog *d, const meow_theme *th, dlg_geom *g)
g->btn_w = (g->dw - 2 * pad - 12) / 2;
}
static void dlg_paint(meow_dialog *d, const meow_theme *th)
/* 只画卡片内容(不含遮罩)——供「仅高亮变化」时重绘卡片区,不会把遮罩再叠一层(防叠加变黑)。 */
static void dlg_card(meow_dialog *d, const meow_theme *th, const dlg_geom *g)
{
int W = meow_ui_width(), H = meow_ui_height();
meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150); /* 遮罩压暗 */
dlg_geom g;
dlg_calc(d, th, &g);
int pad = 24;
meow_gfx_fill_round(g.dx, g.dy, g.dw, g.dh, th->radius_card, th->surfaceContainerHighest);
meow_ui_draw_rect_round(g.dx, g.dy, g.dw, g.dh, th->radius_card, th->outline);
meow_ui_text(g.dx + pad, g.dy + pad, d->title, th->font_title, th->onSurface);
meow_gfx_fill_round(g->dx, g->dy, g->dw, g->dh, th->radius_card, th->surfaceContainerHighest);
meow_ui_draw_rect_round(g->dx, g->dy, g->dw, g->dh, th->radius_card, th->outline);
meow_ui_text(g->dx + pad, g->dy + pad, d->title, th->font_title, th->onSurface);
if (d->type == MEOW_DLG_SELECT) {
for (int i = 0; i < d->nitems; i++) {
int ry = g.row_y + i * g.rowh;
if (ry + g.rowh > g.dy + g.dh - 12) break;
int ry = g->row_y + i * g->rowh;
if (ry + g->rowh > g->dy + g->dh - 12) break;
if (i == d->cur) {
meow_ui_fill_rect(g.dx + 8, ry, g.dw - 16, g.rowh, th->primary);
meow_ui_text(g.dx + pad, ry + (g.rowh - meow_ui_text_height(th->font_body)) / 2,
meow_ui_fill_rect(g->dx + 8, ry, g->dw - 16, g->rowh, th->primary);
meow_ui_text(g->dx + pad, ry + (g->rowh - meow_ui_text_height(th->font_body)) / 2,
d->items[i], th->font_body, 0xFFFFFF);
} else {
meow_ui_text(g.dx + pad, ry + (g.rowh - meow_ui_text_height(th->font_body)) / 2,
meow_ui_text(g->dx + pad, ry + (g->rowh - meow_ui_text_height(th->font_body)) / 2,
d->items[i], th->font_body, th->onSurface);
}
meow_ui_fill_rect(g.dx + pad, ry + g.rowh - 1, g.dw - 2 * pad, 1, th->outlineVariant);
meow_ui_fill_rect(g->dx + pad, ry + g->rowh - 1, g->dw - 2 * pad, 1, th->outlineVariant);
}
} else {
/* INFO/CONFIRM:正文行;INPUT:文本预览 */
int nmsg = (d->type == MEOW_DLG_INFO) ? (d->nitems > 0 ? d->nitems : 1)
: (d->nitems > 0 ? d->nitems : 1);
int y = g.row_y;
int y = g->row_y;
if (d->type == MEOW_DLG_INPUT) {
char buf[300];
snprintf(buf, sizeof buf, "%s", d->ime.buf[0] ? d->ime.buf : "(tap OK to edit...)");
meow_ui_text(g.dx + pad, y, buf, th->font_body, th->onSurfaceVariant);
meow_ui_text(g->dx + pad, y, buf, th->font_body, th->onSurfaceVariant);
} else {
for (int i = 0; i < nmsg; i++) {
if (i < d->nitems && d->items[i][0])
meow_ui_text(g.dx + pad, y, d->items[i], th->font_body, th->onSurfaceVariant);
meow_ui_text(g->dx + pad, y, d->items[i], th->font_body, th->onSurfaceVariant);
y += meow_ui_text_height(th->font_body) + 8;
}
}
/* 取消(左,text 型)/ 确定(右,filled) */
int bx = g.dx + pad, cx = bx + g.btn_w + 12, by = g.btn_y;
meow_ui_draw_rect_round(bx, by, g.btn_w, g.btn_h, 10, th->primary);
meow_ui_text_center(bx + g.btn_w / 2, by + (g.btn_h - meow_ui_text_height(th->font_body)) / 2,
int bx = g->dx + pad, cx = bx + g->btn_w + 12, by = g->btn_y;
meow_ui_draw_rect_round(bx, by, g->btn_w, g->btn_h, 10, th->primary);
meow_ui_text_center(bx + g->btn_w / 2, by + (g->btn_h - meow_ui_text_height(th->font_body)) / 2,
"CANCEL", th->font_body, th->primary);
meow_gfx_fill_round(cx, by, g.btn_w, g.btn_h, 10, th->primary);
meow_ui_text_center(cx + g.btn_w / 2, by + (g.btn_h - meow_ui_text_height(th->font_body)) / 2,
meow_gfx_fill_round(cx, by, g->btn_w, g->btn_h, 10, th->primary);
meow_ui_text_center(cx + g->btn_w / 2, by + (g->btn_h - meow_ui_text_height(th->font_body)) / 2,
"OK", th->font_body, th->onPrimary);
}
}
/* 整帧绘制:遮罩 + 卡片。 */
static void dlg_paint(meow_dialog *d, const meow_theme *th)
{
meow_ui_fill_rect_alpha(0, 0, meow_ui_width(), meow_ui_height(), 0x000000, 150); /* 遮罩压暗 */
dlg_geom g;
dlg_calc(d, th, &g);
dlg_card(d, th, &g);
}
/* 仅重绘卡片区(遮罩不变),flush 卡片矩形:高亮变化用,不叠加遮罩。 */
static void dlg_repaint(meow_dialog *d, const meow_theme *th)
{
dlg_geom g;
dlg_calc(d, th, &g);
dlg_card(d, th, &g);
meow_ui_flush_region(g.dx, g.dy, g.dw, g.dh);
}
meow_dialog *meow_dialog_create(meow_dlg_t t, const char *title)
{
meow_dialog *d = (meow_dialog *)calloc(1, sizeof *d);
@@ -155,12 +203,14 @@ void meow_dialog_show(meow_dialog *d)
meow_input_start();
meow_input_drain(); /* 排空残留触摸(goodix 长尾) */
d->done = 0; d->result = 0;
modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原(scrim 只叠一层/无残影) */
dlg_paint(d, th);
meow_ui_flush();
while (!d->done) {
meow_input_ev e;
while (meow_input_pop(&e)) {
meow_ui_activity_ping(); /* M2.71-bug5:模态期间交互也重置息屏计时 */
if (e.type == MEOW_EV_TAP) {
int x = e.a, y = e.b;
dlg_geom g;
@@ -168,8 +218,13 @@ void meow_dialog_show(meow_dialog *d)
int in_card = x >= g.dx && x < g.dx + g.dw && y >= g.dy && y < g.dy + g.dh;
if (!in_card) { d->done = 1; d->result = 0; break; } /* 遮罩外=取消 */
if (d->type == MEOW_DLG_SELECT) {
/* M2.71-bug3:点项 → 立即刷「选中」高亮(不下线、不叠加遮罩);
* 再点同一项 → 确认(返回该项下标)。点卡片外=取消。 */
int idx = (y - g.row_y) / g.rowh;
if (idx >= 0 && idx < d->nitems) { d->cur = idx; d->result = idx; d->done = 1; }
if (idx >= 0 && idx < d->nitems) {
if (idx == d->cur) { d->result = idx; d->done = 1; }
else { d->cur = idx; dlg_repaint(d, th); }
}
break;
}
/* 非选择:按钮命中 */
@@ -195,6 +250,7 @@ void meow_dialog_show(meow_dialog *d)
if (d->done) break;
usleep(16000);
}
modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */
}
int meow_dialog_result(meow_dialog *d) { return d ? d->result : 0; }
@@ -257,6 +313,7 @@ int meow_sheet_open(meow_sheet *s)
int W = meow_ui_width(), H = meow_ui_height();
int ph = sheet_h(s, th);
int py = H - ph, pad = 24;
modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原 */
/* 首次绘制:遮罩 + 底部面板(顶部圆角) */
meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150);
meow_gfx_fill_round(0, py, W, ph, th->radius_card, th->surfaceContainerHighest);
@@ -273,6 +330,7 @@ int meow_sheet_open(meow_sheet *s)
while (!s->done) {
meow_input_ev e;
while (meow_input_pop(&e)) {
meow_ui_activity_ping(); /* M2.71-bug5:模态期间交互也重置息屏计时 */
if (e.type == MEOW_EV_TAP) {
int y = e.b;
if (y < py) { s->done = 1; s->result = -1; break; } /* 点面板外=取消 */
@@ -288,6 +346,7 @@ int meow_sheet_open(meow_sheet *s)
if (s->done) break;
usleep(16000);
}
modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */
return s->result;
}
void meow_sheet_free(meow_sheet *s) { if (s) free(s); }
+25 -1
View File
@@ -179,6 +179,10 @@ void meow_gfx_mark_dirty(int x, int y, int w, int h) { dirty_add(x, y, w, h); }
void meow_gfx_mark_full(void) { dirty_full(); }
void meow_gfx_dirty_reset(void) { dirty_reset(); }
/* back_buf 每行字节数 / 位深(M2.71-bug3:模态/IME 整帧快照恢复用)。 */
int meow_ui_back_buf_pitch(void) { return g_line; }
int meow_ui_back_buf_bpp(void) { return g_bpp; }
/* launcher(系统组件)与 SDK 共用同一 back_buf:launcher 的 fb 绘制指针指向它,
* 从而 launcher 与 app 统一渲染栈(同一离屏 + 同一 present)。 */
unsigned char *meow_ui_back_buf(void) { return g_back_buf; }
@@ -393,6 +397,23 @@ void meow_ui_draw_rect_round(int x, int y, int w, int h, int r, uint32_t color)
dirty_add(x1, y1, x2 - x1, y2 - y1);
}
/* 圆角矩形半透明填充(M2.71-bug4):按圆角裁剪后逐像素混合 α。供「禁用变暗」等状态层用,
* 避免在圆角外的方形区域也叠色(旧 fill_rect_alpha 会在圆角外露出灰方块)。 */
void meow_gfx_fill_round_alpha(int x, int y, int w, int h, int r, uint32_t color, int alpha)
{
if (!g_init_ok) return;
if (alpha <= 0) return;
if (alpha >= 255) { meow_gfx_fill_round(x, y, w, h, r, color); return; }
if (w <= 0 || h <= 0) return;
int x1 = x, y1 = y, x2 = x + w, y2 = y + h;
if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0;
if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres;
for (int py = y1; py < y2; py++)
for (int px = x1; px < x2; px++)
if (rr_inside(px, py, x, y, w, h, r)) put_px_blend(px, py, color, alpha);
dirty_add(x1, y1, x2 - x1, y2 - y1);
}
/* ================= §5.8.6 batch2:圆形/线段/圆弧环(软渲,无 -lm) =================
* 无 GPU 合成、构建只链 libz+pthread(无 -lm),故不用 math.h:COS 用象限对称查表、
* 圆用 bbox 距离测试、弧带用「沿弧采样成粗线(圆帽)」近似。 */
@@ -462,7 +483,9 @@ void meow_gfx_ring_arc(int cx, int cy, int r, int th, int start_deg, int sweep_d
if (!g_init_ok || r <= 0) return;
int sw = sweep_deg % 360;
if (sw < 0) sw += 360;
if (sw >= 360) { /* 整环:bbox 距离带(无角度判定,最快) */
/* 整环(|sweep|>=360):bbox 距离带(无角度判定,最快)。注意 sweep=360 → sw=0,
* 若靠 sw>=360 判整环会落下走弧分支只画一个点(顺带让 Radio 外圈/圆形进度轨道环渲染成点)。 */
if (sweep_deg >= 360 || sweep_deg <= -360) {
int r1 = r - th / 2, r2 = r + th / 2;
if (r1 < 0) r1 = 0;
int x1 = cx - r2, y1 = cy - r2, x2 = cx + r2 + 1, y2 = cy + r2 + 1;
@@ -478,6 +501,7 @@ void meow_gfx_ring_arc(int cx, int cy, int r, int th, int start_deg, int sweep_d
dirty_add(x1, y1, x2 - x1, y2 - y1);
return;
}
if (sw == 0) return; /* 零弧长:不画(sweep=0;整环已在上面处理) */
/* 弧带:沿弧采样相邻点、粗线连接(圆帽)近似。方向:deg 右(0)→上(90)→左(180)→下(270),顺时针。 */
int n = sw / 2 + 1;
if (n < 2) n = 2;
+50 -15
View File
@@ -22,6 +22,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; };
@@ -91,18 +121,18 @@ 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 */
} 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 * 9, y + GCOL * 2, GW, GH, K_BACK, '<');
add_key(x + GCOL * 9, y + GCOL * 2, GW, GH, K_ENTER, '>'); /* 覆盖:回车 */
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;
@@ -149,25 +179,27 @@ 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];
/* 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, 0xFFFFFF);
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];
int is_fn = (k->kind != K_CH);
uint32_t bg = is_fn ? 0x33333C : 0xFFFFFF;
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);
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 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); }
}
}
@@ -199,6 +231,7 @@ void meow_ime_close(int result)
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)
@@ -211,6 +244,7 @@ void meow_ui_text_input_async(meow_ime_ctx *ctx)
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();
}
@@ -223,6 +257,7 @@ int meow_ui_text_input(meow_ime_ctx *ctx)
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();
+42 -3
View File
@@ -37,6 +37,7 @@
#define EDGE_W 24 /* 左滑返回的「左缘起点」宽度(t_dx < EDGE_W 才认返回手势) */
#define QUEUE_N 128
#define MAX_DEVS 8
#define MEOW_APP_ACTIVITY_FILE "/tmp/meow_app_activity" /* M2.71-bug5:App 活动→launcher 重置息屏计时 */
/* ── 内部事件:系统级「下拉展开通知中心」(M2.5x 接线)──
* 触摸状态机在抬手判定处识别「SWIPE_DOWN 起始于顶部热区」后压入;事件循环按系统手势处理
@@ -126,6 +127,8 @@ static int t_dx = 0, t_dy = 0; /* 按下原点(BTN_TOUCH 后首个有效 A
static int t_cx = 0, t_cy = 0; /* 当前坐标(-1=本次尚未见到有效坐标) */
static int t_origin_valid = 0; /* 原点是否已锁定 */
static uint64_t t_down_ms = 0; /* 按下时刻(monotonic ms),供实时按住时长用 */
/* M2.71-bug2/6:拖动过程事件上次上报坐标(坐标变化才入队 MEOW_EV_TOUCH_MOVE,防刷屏)。 */
static int t_mv_x = -1, t_mv_y = -1;
/* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。
* t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */
@@ -138,22 +141,32 @@ static void touch_press(int x, int y)
t_cx = -1; t_cy = -1;
t_dx = 0; t_dy = 0;
t_down_ms = now_ms(); /* 记按下时刻:供实时按住时长/按住视觉 */
t_mv_x = -1; t_mv_y = -1; /* M2.71:拖动过程起点未定 */
}
/* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 */
/* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。
* M2.71-bug2/6:原点锁定即报 MEOW_EV_TOUCH_DOWN(拖动起点);随后坐标变化报 MEOW_EV_TOUCH_MOVE
* (坐标变了才报,省队列)。这两类事件供 slider 拖动 / 列表&页面滚动跟手。 */
static void touch_update(int x, int y)
{
if (t_down && !t_origin_valid && x >= 0 && y >= 0) {
t_dx = x; t_dy = y; t_origin_valid = 1;
t_mv_x = x; t_mv_y = y;
push_ev(MEOW_EV_TOUCH_DOWN, x, y);
}
if (x >= 0) t_cx = x;
if (y >= 0) t_cy = y;
if (t_down && t_origin_valid && t_cx >= 0 && t_cy >= 0 &&
(t_cx != t_mv_x || t_cy != t_mv_y)) {
t_mv_x = t_cx; t_mv_y = t_cy;
push_ev(MEOW_EV_TOUCH_MOVE, t_cx, t_cy);
}
}
static void touch_release(void)
{
if (!t_down) return;
t_down = 0; t_press_valid = 0;
/* 本次按下从未锁到有效坐标(幽灵触摸/坐标未就绪):丢弃,且清空全部轨迹。 */
if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; return; }
if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1; return; }
t_origin_valid = 0;
int mdx = t_cx - t_dx, mdy = t_cy - t_dy;
int adx = mdx < 0 ? -mdx : mdx, ady = mdy < 0 ? -mdy : mdy;
@@ -194,7 +207,7 @@ static void touch_release(void)
push_ev(MEOW_EV_TAP, t_cx, t_cy);
}
/* 手势级干净起点:清空全部轨迹,下一手势从零开始,杜绝污染无自愈。 */
t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0;
t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1;
}
/* ---- 实时触摸态(内部:控件绘制按压/按住视觉用)----
@@ -221,6 +234,7 @@ void meow_input_touch_set(int down, int x, int y, unsigned hold_ms)
} else {
t_down = 0; t_press_valid = 0; t_origin_valid = 0;
t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; t_down_ms = 0;
t_mv_x = -1; t_mv_y = -1;
}
}
@@ -301,6 +315,19 @@ void meow_input_start(void)
/* ---- 事件循环 ---- */
void meow_ui_request_exit(void) { g_exit = 1; }
/* M2.71-bug5:App 活动信号(节流 ≤2/s 写标记文件,限 I/O)。
* 仅真机写(宿主/qemu 无 launcher 消费,避免噪音);launcher 前台 app 循环消费它重置息屏计时。 */
static uint64_t g_activity_last_ms = 0;
void meow_ui_activity_ping(void)
{
uint64_t now = now_ms();
if (now - g_activity_last_ms < 500) return; /* 节流:任意交互连续触摸也只 ~2 次/s */
g_activity_last_ms = now;
if (!meow_gfx_is_device()) return; /* 离屏/宿主:无 launcher 消费 */
int fd = open(MEOW_APP_ACTIVITY_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd >= 0) { if (write(fd, "1", 1) < 0) { } close(fd); }
}
void meow_ui_run(const meow_ui_events *ev)
{
if (ev) { g_events = *ev; g_events_set = 1; }
@@ -319,6 +346,7 @@ void meow_ui_run(const meow_ui_events *ev)
while (meow_input_pop(&e)) {
if (g_exit) break; /* 已请求退出:不再分发剩余事件,杜绝「退出后又触发」的偶发再进/退 */
got = 1;
meow_ui_activity_ping(); /* M2.71-bug5:任意交互事件都喂 launcher 重置息屏计时(节流) */
int in_startup = (now_ms() < startup_cooldown_until); /* M2.22(C) */
if (e.type == MEOW_EV_TAP) {
if (in_startup) continue; /* 启动窗口也吞 TAP:旧手势残留点按(如上一次左滑抬起)
@@ -354,6 +382,17 @@ void meow_ui_run(const meow_ui_events *ev)
/* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发
* APP on_swipe;路由到系统钩子(当前为记录占位,通知中心 UI 为后续里程碑)。 */
meow_ui_sys_notif_pull();
} else if (e.type == MEOW_EV_TOUCH_DOWN || e.type == MEOW_EV_TOUCH_MOVE) {
/* M2.71-bug2/6:拖动过程(跟手)。IME 弹出时触摸优先喂键盘(键盘覆盖层拦截,
* on_touch_* 丢弃,避免按到键盘却在背后控件上锁拖拽);启动窗口也吞(防残留点按
* 误锁拖拽)。模态(dialog/sheet)期间本主循环被阻塞,天然不在此分发。 */
if (in_startup) continue;
if (meow_ime_active()) continue;
if (g_events_set && g_events.on_touch_down && e.type == MEOW_EV_TOUCH_DOWN)
g_events.on_touch_down(e.a, e.b);
if (g_events_set && g_events.on_touch_move && e.type == MEOW_EV_TOUCH_MOVE)
g_events.on_touch_move(e.a, e.b);
got = 1; /* 交互事件:不睡眠,保持跟手帧同步 */
} else if (e.type == MEOW_EV_SWIPE_PROGRESS) {
if (in_startup) continue; /* 启动窗口进度条静音 */
int x0 = e.a, xc = e.b, dx = xc - x0; /* dx=当前X-按下X */
+13
View File
@@ -108,9 +108,14 @@ void meow_gfx_dirty_reset(void);
/* 返回离屏 back_buf 指针(launcher 系统组件与 SDK 共用同一 back_buf 用)。 */
unsigned char *meow_ui_back_buf(void);
/* back_buf 的每行字节数(pitch)与位深(16/32)。M2.71-bug3:模态/IME 快照恢复用。 */
int meow_ui_back_buf_pitch(void);
int meow_ui_back_buf_bpp(void);
/* 实心圆角矩形(控件按钮底色用;公共 §3 只有空心 draw_rect_round)。[D3] */
void meow_gfx_fill_round(int x, int y, int w, int h, int r, uint32_t color);
/* 圆角矩形半透明填充(M2.71-bug4):禁用变暗等状态层用,圆角外不叠色(防灰方块)。 */
void meow_gfx_fill_round_alpha(int x, int y, int w, int h, int r, uint32_t color, int alpha);
/* §5.8.6 batch2:软渲圆形/线段/圆弧环原语(无 GPU、无 -lm,内部整数/查表实现)。
* 供 Radio(外圈/内点)、Badge(红点)、ProgressBar(环形轨道/进度弧)、Checkbox(勾线段)使用。 */
@@ -131,6 +136,8 @@ int meow_gfx_mfont_ready(void);
#define MEOW_EV_TAP 2 /* a=x, b=y(触摸抬起落点) */
#define MEOW_EV_SWIPE 3 /* a=MEOW_SWIPE_*, b=距离(px) */
#define MEOW_EV_SWIPE_PROGRESS 4 /* a=按下X(t_dx), b=当前X(t_cx);dx=b-a(>0=左缘向内划=返回手势) */
#define MEOW_EV_TOUCH_DOWN 6 /* a=x, b=y:按下后首个有效坐标(拖动起点,M2.71-bug2/6) */
#define MEOW_EV_TOUCH_MOVE 7 /* a=x, b=y:拖动过程坐标变化(坐标变才入队;跟手用) */
typedef struct meow_input_ev {
uint8_t type;
@@ -146,6 +153,12 @@ void meow_input_inject(int type, int a, int b);
/* 清空队列(IME 打开前丢弃残留触摸,避免误敲)。 */
void meow_input_drain(void);
/* ---- M2.71-bug5:App 活动 → 通知 launcher 重置息屏计时 ----
* launcher 前台 app 会话的 app_last_input 只在物理键边沿重置(它看不到应用内触摸)。SDK 在
* 收到任意交互事件(tap/swipe/拖动/IME/模态)时节流 touch 出 /tmp/meow_app_activity 标记;
* launcher app 循环探测到即 unlink + 重置计时(若已自动息屏则照 key-wake 路径唤醒)。 */
void meow_ui_activity_ping(void);
/* ---- 实时触摸态(供控件绘制按压/按住视觉;输入线程写、app 主线读)----
* 与语义事件队列无关:这些直接读输入线程的触摸状态机当前值,让 meow_widget_draw
* 在「手指按下期间」能如实反映按钮的按下/按住视觉,而无需 app 自行追踪按下。 */
+59 -12
View File
@@ -26,6 +26,11 @@ enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3,
* 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */
#define MEOW_BTN_HOLD_MS 500u
/* 圆形 ProgressBar 环带厚 / 外扩边距(bgg 修复 M2.71-bug1:控件几何必须覆盖真实像素)。
* 实际像素范围 = 中心 ± (r + thk/2);margin = thk/2 + 1 保证外接框覆盖环带外缘与圆帽。 */
#define MEOW_PROGRESS_ARC_THK 4
#define MEOW_PROGRESS_ARC_MARGIN (MEOW_PROGRESS_ARC_THK / 2 + 1)
/* MD3 状态层混色:c 为基色、o 为状态色、t 为 o 的占比(0-100)。
* 用于把主题 accent 向黑/白调出「按下/按住」状态层(日/夜主题各配色)。 */
static uint32_t blend_color(uint32_t c, uint32_t o, int t)
@@ -73,6 +78,9 @@ struct meow_widget {
char items[MEOW_CONTAINER_MAX][24]; /* W_TOPAPPBAR 动作 / W_NAVBAR 项 / W_TABBAR 页签 */
int nitems; /* 上述项数 */
int selected; /* W_NAVBAR / W_TABBAR 选中下标 */
/* M2.71-bug8:W_LIST 行点击回调(可选)。 */
meow_list_tap_fn tap_cb; /* 命中行回调(可 NULL) */
void *tap_ctx; /* 回调 ctx */
};
/* ---- §5.8 batch1 内部辅助:opts 取值(opts_used==0 时按控件内建默认)----
@@ -584,7 +592,13 @@ meow_widget *meow_progress_circular_create(int cx, int cy, int r, int value)
{
meow_widget *t = (meow_widget *)calloc(1, sizeof *t);
if (!t) return NULL;
t->type = W_PROGRESS; t->x = cx; t->y = cy; t->w = r; t->h = r;
/* bbox = 整圆像素外接框(中心 ± (r + margin)):使 w->x/y/w/h 即真实绘制范围,
* 于是 meow_progress_set 的 mark_dirty 与 App 的 flush_region(w->x,w->y,w->w,w->h)
* 都天然对齐,不漏刷/不多刷(修复 bgg M2.71 圆形进度脏区)。 */
int m = MEOW_PROGRESS_ARC_MARGIN;
t->type = W_PROGRESS;
t->x = cx - r - m; t->y = cy - r - m;
t->w = 2 * (r + m); t->h = 2 * (r + m);
t->owner = meow_ui_activity_active();
t->state = MEOW_WS_ENABLED;
t->variant = 1; /* 圆形 */
@@ -791,6 +805,34 @@ int meow_list_hit_index(meow_widget *w, int x, int y)
return (idx >= 0 && idx < w->count) ? idx : -1;
}
/* M2.71-bug8:选中行(越界 clamp)+ 标脏(高亮刷新)。 */
void meow_list_set_selected(meow_widget *w, int idx)
{
if (!w || w->type != W_LIST) return;
if (idx < 0) idx = 0;
if (w->count > 0 && idx >= w->count) idx = w->count - 1;
if (idx == w->sel) return;
w->sel = idx;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
}
int meow_list_selected(meow_widget *w) { return (w && w->type == W_LIST) ? w->sel : -1; }
void meow_list_set_on_tap(meow_widget *w, meow_list_tap_fn cb, void *ctx)
{
if (!w || w->type != W_LIST) return;
w->tap_cb = cb; w->tap_ctx = ctx;
}
/* 命中行:置选中 + 回调(若设)+ 返回 idx;未命中返回 -1(不置选中)。 */
int meow_list_tap_at(meow_widget *w, int x, int y)
{
int idx = meow_list_hit_index(w, x, y);
if (idx < 0) return -1;
meow_list_set_selected(w, idx);
if (w->tap_cb) w->tap_cb(idx, w->tap_ctx);
return idx;
}
static int hit(meow_widget *w, int x, int y)
{
if (!w) return 0;
@@ -816,7 +858,9 @@ void meow_slider_set(meow_widget *w, int val)
{
if (!w || w->type != W_SLIDER) return;
if (val < w->min) val = w->min; if (val > w->max) val = w->max;
if (val == w->val) return;
w->val = val;
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); /* 滑块/轨道随值变化:标脏 */
}
int meow_slider_get(meow_widget *w) { return w && w->type == W_SLIDER ? w->val : 0; }
void meow_switch_set(meow_widget *w, int on)
@@ -899,8 +943,8 @@ void meow_widget_draw(meow_widget *w)
if (sw > 0 && sc) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, sc);
if (w->state & MEOW_WS_FOCUSED)
meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary);
/* 禁用:整体变暗(状态层向 onSurface 叠 α) */
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
/* 禁用:整体变暗(圆角 α,防在圆角外叠出灰方块——M2.71-bug4) */
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90);
break;
}
case W_SLIDER: {
@@ -922,7 +966,7 @@ void meow_widget_draw(meow_widget *w)
int knob_r = w->h / 2 - 3;
int kx = w->on ? w->x + w->w - knob_r * 2 - 3 : w->x + 3;
meow_gfx_fill_round(kx, w->y + 3, knob_r * 2, knob_r * 2, knob_r, 0xFFFFFF);
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, w->h / 2, th->onSurface, 90);
break;
}
case W_LIST: {
@@ -995,7 +1039,7 @@ void meow_widget_draw(meow_widget *w)
int tw = meow_ui_text_width(w->buf, th->font_body);
meow_ui_fill_rect(tx + tw, ty, 2, meow_ui_text_height(th->font_body), th->primary);
}
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90);
break;
}
case W_CARD: {
@@ -1008,7 +1052,7 @@ void meow_widget_draw(meow_widget *w)
meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->primary, 24);
if (w->nchildren > 0 && w->children[0]) meow_widget_draw(w->children[0]);
if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary);
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90);
break;
}
case W_DIVIDER: {
@@ -1046,7 +1090,7 @@ void meow_widget_draw(meow_widget *w)
meow_ui_draw_rect_round(w->x, w->y, sz, sz, r, th->outline);
}
if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, sz, sz, r, th->primary);
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, sz, sz, th->onSurface, 90);
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, sz, sz, r, th->onSurface, 90);
break;
}
case W_RADIO: {
@@ -1054,13 +1098,16 @@ void meow_widget_draw(meow_widget *w)
int ccx = w->x + w->w / 2, ccy = w->y + w->h / 2;
meow_gfx_ring_arc(ccx, ccy, 12, 2, 0, 360, sel ? th->primary : th->outline);
if (sel) meow_gfx_fill_circle(ccx, ccy, 6, th->primary);
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, w->w / 2, th->onSurface, 90);
break;
}
case W_PROGRESS: {
if (w->variant == 1) { /* 圆形 */
int ccx = w->x, ccy = w->y, rr = w->w;
int thk = 4;
if (w->variant == 1) { /* 圆形(几何 = 整圆像素外接框;由此解回圆心/半径) */
int thk = MEOW_PROGRESS_ARC_THK;
int m = MEOW_PROGRESS_ARC_MARGIN;
int ccx = w->x + w->w / 2, ccy = w->y + w->h / 2;
int rr = w->w / 2 - m; /* 与 create 一致:回到调用方传入的半径 r */
if (rr <= 0) break;
meow_gfx_ring_arc(ccx, ccy, rr, thk, 0, 360, th->surfaceVariant);
if (w->value < 0) { /* 不定:按时间推进一段弧(App 在 on_tick 里重绘驱动) */
int start = (int)((meow_ui_time_ms() / 30) % 360);
@@ -1094,7 +1141,7 @@ void meow_widget_draw(meow_widget *w)
meow_ui_text_center(w->x + w->w / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2,
w->label, th->font_body, tc);
if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary);
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90);
break;
}
case W_TOPAPPBAR: {
+8 -1
View File
@@ -144,11 +144,18 @@ int main(void)
assert(meow_list_hit_index(lc, 30, 480) == ((480 - 460 + 3500) / 40)); /* 可见项换算 */
meow_list_set_item_h(lc, 50);
assert(meow_widget_hit(lc, 30, 470) == 1);
/* M2.71-bug8:行选中 + 命中点击回调 */
meow_list_set_selected(lc, 5);
assert(meow_list_selected(lc) == 5);
/* 验证 tap_at 返回命中下标并置选中 */
int hidx = meow_list_tap_at(lc, 30, 480);
assert(hidx == ((480 - 460 + meow_list_scroll(lc)) / 50));
assert(meow_list_selected(lc) == hidx);
/* LazyRow 横向 */
meow_widget *lr = W(meow_lazyrow_create(20, 640, 380, 40, 60, 100, list_src, NULL));
meow_list_scroll_by(lr, 200);
assert(meow_list_hit_index(lr, 40, 660) == ((40 - 20 + 200) / 60));
printf("PASS lazy list: scroll clamp/virtualize/hit_index (col+row)\n");
printf("PASS lazy list: scroll clamp/virtualize/hit_index/tap_at/select (col+row)\n");
/* ---------- Dialog / BottomSheet 纯逻辑(create/set/add;show 阻塞故不调) ---------- */
meow_dialog *dlg = meow_dialog_create(MEOW_DLG_SELECT, "Pick one");
+158
View File
@@ -0,0 +1,158 @@
/* test_sdk_fixes.c — M2.71 修复项宿主验证(离屏 480x720,16bpp)。
* 覆盖:
* bug4 disabled 按钮圆角外无灰方块(角点 = 页面背景而非叠暗);
* bug1 圆形进度环带确实画到 = 半径+thk/2 处(bbox 与真实像素一致,漏刷修复的几何前提);
* bug3 modal(Dialog)open→cancel 后背景还原(scrim 不累积/无残影);
* bug7 IME 回显实时同步(on_change 随键入触发)+ 关闭后键盘区背景还原。
*
* 编译(宿主 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 \
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
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 <= 12; }
/* ---- 为阻塞模态/IME 在后台注入事件 ---- */
static int g_inj_tap_x = 0, g_inj_tap_y = 0, g_inj_delay = 0;
static void *inject_thread(void *arg)
{
(void)arg;
usleep((useconds_t)g_inj_delay);
meow_input_inject(MEOW_EV_TAP, g_inj_tap_x, g_inj_tap_y);
return NULL;
}
static void inject_after(int ms, int x, int y)
{
pthread_t th; g_inj_tap_x = x; g_inj_tap_y = y; g_inj_delay = ms;
pthread_create(&th, NULL, inject_thread, NULL);
pthread_detach(th);
}
static void test_disabled_corner(void)
{
const meow_theme *th = meow_theme_get();
meow_ui_clear(th->bg);
meow_widget *b = meow_btn_create(100, 100, 200, 60, "DISABLED");
meow_widget_set_enabled(b, 0); /* 禁用:old 用方形 α 盖整 bbox → 圆角外露灰 */
meow_widget_draw(b);
meow_ui_flush();
int r, g, bb;
rd_px(100, 100, &r, &g, &bb); /* bbox 左上角(圆角外):应为页面背景 */
printf("[bug4] disabled corner rgb=%02X%02X%02X (bg=%02X%02X%02X) ",
r, g, bb, (th->bg >> 16) & 0xFF, (th->bg >> 8) & 0xFF, th->bg & 0xFF);
assert(near(r, (th->bg >> 16) & 0xFF) && near(g, (th->bg >> 8) & 0xFF) && near(bb, th->bg & 0xFF));
printf("PASS\n");
meow_widget_free(b);
}
static void test_circular_extent(void)
{
const meow_theme *th = meow_theme_get();
meow_ui_clear(th->bg);
/* r=30, thk=4 → 环外缘 = r+thk/2 = 32;create 几何外接框 = 中心±(r+margin)=±33 */
int cx = 240, cy = 200, r = 30;
meow_widget *pg = meow_progress_circular_create(cx, cy, r, 100);
meow_widget_draw(pg);
meow_ui_flush();
int rd, g, b;
rd_px(cx - 32, cy, &rd, &g, &b); /* 最左环外缘:应被画(非背景) */
int bg_r = (th->bg >> 16) & 0xFF, bg_g = (th->bg >> 8) & 0xFF, bg_b = th->bg & 0xFF;
printf("[bug1] ring-left-extent rgb=%02X%02X%02X bg=%02X%02X%02X ", rd, g, b, bg_r, bg_g, bg_b);
assert(!(near(rd, bg_r) && near(g, bg_g) && near(b, bg_b))); /* 环带已画到外缘 */
printf("PASS\n"); fflush(stdout);
meow_widget_free(pg);
}
static void test_modal_restore(void)
{
const meow_theme *th = meow_theme_get();
meow_ui_clear(th->bg);
meow_ui_flush();
meow_dialog *d = meow_dialog_create(MEOW_DLG_CONFIRM, "Sure?");
/* 两次 open→cancel:背景应两次均还原(scrim 不累积、无残影) */
for (int i = 0; i < 2; i++) {
inject_after(30, 1, 1); /* (1,1) 在居中卡片外 → 取消 */
meow_dialog_show(d);
int r, g, b;
rd_px(400, 100, &r, &g, &b); /* 卡片外右上:应 == 背景 */
printf("[bug3] open%d cancel bg rgb=%02X%02X%02X bg=%02X%02X%02X ", i, r, g, b,
(th->bg >> 16) & 0xFF, (th->bg >> 8) & 0xFF, th->bg & 0xFF);
assert(near(r, (th->bg >> 16) & 0xFF) && near(g, (th->bg >> 8) & 0xFF) && near(b, th->bg & 0xFF));
}
printf("PASS\n");
meow_dialog_free(d);
}
/* IME 回显同步回调 */
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_ime_echo_and_restore(void)
{
const meow_theme *th = meow_theme_get();
meow_ui_clear(th->bg);
meow_ui_flush();
meow_ime_ctx c; memset(&c, 0, sizeof c);
c.mode = MEOW_IME_ASCII; c.maxlen = 32; c.on_change = ime_cb;
/* --- 异步:验『回显/输入框实时同步(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_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);
meow_input_inject(MEOW_EV_TAP, 20, 680); /* ESC(X) → 取消 */
meow_ime_handle_tap(20, 680);
assert(meow_ime_active() == 0);
/* --- 阻塞:验『关闭后键盘区背景还原』--- */
meow_ui_clear(th->bg);
meow_ui_flush();
inject_after(30, 20, 680); /* ESC 关闭 */
int rc = meow_ui_text_input(&c);
assert(rc == 0); /* 取消 */
int r, g, b;
rd_px(240, 600, &r, &g, &b); /* 键盘区内一点:应 == 背景 */
printf("[bug7] ime close restore px rgb=%02X%02X%02X bg=%02X%02X%02X ", r, g, b,
(th->bg >> 16) & 0xFF, (th->bg >> 8) & 0xFF, th->bg & 0xFF);
assert(near(r, (th->bg >> 16) & 0xFF) && near(g, (th->bg >> 8) & 0xFF) && near(b, th->bg & 0xFF));
printf("PASS\n");
}
int main(void)
{
setenv("MEOW_UI_DUMP", "/tmp/fixes.ppm", 1);
meow_ui_opts o = { 480, 720 };
assert(meow_ui_init(&o) == 0);
test_disabled_corner();
test_circular_extent();
test_modal_restore();
test_ime_echo_and_restore();
meow_ui_shutdown();
printf("ALL PASS\n");
return 0;
}