/* * meow_ui_dialog.c — §5.8.6 模态控件化(App 层,非系统叠加):Dialog / BottomSheet。 * * 复用 §5.6 B③ 弹窗 / §5.7 B④ 底部面板模板(与 launcher s_dd_modal 同构):遮罩压暗 + * 圆角卡片 + 标题 + 内容 + 操作;返回手势=取消;不压状态栏。阻塞式(内层 meow_input_pop * 轮询,与 meow_ui_text_input 同构),打开前排空残留触摸(goodix 长尾免疫)。 * 命中一律用抬手最新坐标;即画即显(打开一次性绘制,仅高亮变化才重绘,逐帧不重画静态内容)。 * * 属 App 层模态:不注册进系统顶条/通知中心渲染(系统级控件归属不动)。物理键在 B1 未下发 * 给 App(on_key 无键),故本层关闭途径=触摸 + 左缘返回手势;音量/电源高亮导航为后续批次。 */ #include "meow_ui.h" #include "meow_ui_internal.h" #include #include #include #include #define MEOW_MODAL_MAX_ITEMS 32 #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; char title[96]; char items[MEOW_MODAL_MAX_ITEMS][64]; int nitems; int cur; /* SELECT 预选/高亮项 */ int result; int done; meow_ime_ctx ime; /* INPUT 型(拷贝) */ int has_ime; }; /* 卡片几何:返回标题之后内容起点(行/按钮相对坐标)。 */ typedef struct { int dx,dy,dw,dh; int row_y,rowh; int btn_y,btn_w,btn_h; } dlg_geom; static void dlg_calc(meow_dialog *d, const meow_theme *th, dlg_geom *g) { int W = meow_ui_width(), H = meow_ui_height(); int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0; int pad = 24; int tH = meow_ui_text_height(th->font_title); int bH = meow_ui_text_height(th->font_body); int select = (d->type == MEOW_DLG_SELECT); int nrows = select ? d->nitems : 0; int nmsg = (d->type == MEOW_DLG_INFO) ? (d->nitems > 0 ? d->nitems : 1) : (select ? 0 : (d->nitems > 0 ? d->nitems : 1)); int have_btns = !select; g->dw = 348; if (g->dw > W - 40) g->dw = W - 40; g->dx = (W - g->dw) / 2; int content = nrows * MEOW_MODAL_ROW + nmsg * (bH + 8); g->dh = pad + tH + 12 + content + pad + (have_btns ? 16 + MEOW_MODAL_BTN : 0); int avail = H - sb - 20; if (g->dh > avail) g->dh = avail; g->dy = (H - g->dh) / 2; if (g->dy < sb) g->dy = sb; g->row_y = g->dy + pad + tH + 12; g->rowh = (MEOW_MODAL_ROW < 0) ? 40 : MEOW_MODAL_ROW; g->btn_y = g->row_y + content + 16; g->btn_h = MEOW_MODAL_BTN; g->btn_w = (g->dw - 2 * pad - 12) / 2; } /* 只画卡片内容(不含遮罩)——供「仅高亮变化」时重绘卡片区,不会把遮罩再叠一层(防叠加变黑)。 */ static void dlg_card(meow_dialog *d, const meow_theme *th, const dlg_geom *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); 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; 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, 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, 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); } } 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; 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); } 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); 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, "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, "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); if (!d) return NULL; d->type = t; if (title) { strncpy(d->title, title, sizeof d->title - 1); d->title[sizeof d->title - 1] = 0; } d->cur = -1; d->result = 0; d->done = 0; return d; } void meow_dialog_set_items(meow_dialog *d, const char *const *item, int n, int cur) { if (!d) return; d->nitems = 0; if (item) for (int i = 0; i < n && i < MEOW_MODAL_MAX_ITEMS; i++) if (item[i]) { strncpy(d->items[d->nitems], item[i], 63); d->items[d->nitems][63] = 0; d->nitems++; } d->cur = cur; } void meow_dialog_set_ime(meow_dialog *d, meow_ime_ctx *ime) { if (!d || !ime) return; d->ime = *ime; d->has_ime = 1; } void meow_dialog_close(meow_dialog *d) { if (d) d->done = 1; } void meow_dialog_show(meow_dialog *d) { if (!d) return; const meow_theme *th = meow_theme_get(); /* INPUT 型若未带 ime,用空 ctx 兜底 */ if (d->type == MEOW_DLG_INPUT && !d->has_ime) { memset(&d->ime, 0, sizeof d->ime); d->ime.maxlen = 255; } 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; dlg_calc(d, th, &g); 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) { if (idx == d->cur) { d->result = idx; d->done = 1; } else { d->cur = idx; dlg_repaint(d, th); } } break; } /* 非选择:按钮命中 */ int pad = 24, by = g.btn_y; int bx = g.dx + pad, cx = bx + g.btn_w + 12; if (y >= by && y < by + g.btn_h) { if (x >= bx && x < bx + g.btn_w) { /* CANCEL */ d->done = 1; d->result = 0; } else if (x >= cx && x < cx + g.btn_w) { /* OK */ if (d->type == MEOW_DLG_INPUT) { d->result = meow_ui_text_input(&d->ime); /* 阻塞编辑 */ } else { d->result = (d->type == MEOW_DLG_CONFIRM) ? 1 : 1; } d->done = 1; } break; } } else if (e.type == MEOW_EV_SWIPE && e.a == MEOW_SWIPE_LEFT) { d->done = 1; d->result = 0; /* 返回=取消 */ } } if (d->done) break; usleep(16000); } modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */ } int meow_dialog_result(meow_dialog *d) { return d ? d->result : 0; } int meow_dialog_confirm(meow_dialog *d) { if (!d || d->type != MEOW_DLG_CONFIRM) return 0; meow_dialog_show(d); return d->result == 1; } void meow_dialog_free(meow_dialog *d) { if (d) free(d); } /* ---- BottomSheet ---- */ struct meow_sheet { char title[96]; char items[MEOW_MODAL_MAX_ITEMS][64]; int nitems; int h; int result; int done; }; static int sheet_h(meow_sheet *s, const meow_theme *th) { int H = meow_ui_height(); int sb = meow_ui_statusbar_height(); if (sb < 0) sb = 0; int avail = H - sb - 20; int title_h = meow_ui_text_height(th->font_title); int min_h = 24 + title_h + 12 + MEOW_MODAL_ROW + 20; int want = (s->h > 0) ? s->h : min_h; int est = 24 + title_h + 12 + s->nitems * MEOW_MODAL_ROW + 20; if (est > want) want = est; if (want > avail) want = avail; if (want < min_h) want = min_h < avail ? min_h : avail; return want; } meow_sheet *meow_sheet_create(const char *title, int h) { meow_sheet *s = (meow_sheet *)calloc(1, sizeof *s); if (!s) return NULL; if (title) { strncpy(s->title, title, sizeof s->title - 1); s->title[sizeof s->title - 1] = 0; } s->h = h > 0 ? h : 0; return s; } int meow_sheet_add_item(meow_sheet *s, const char *label) { if (!s || s->nitems >= MEOW_MODAL_MAX_ITEMS) return -1; if (label) { strncpy(s->items[s->nitems], label, 63); s->items[s->nitems][63] = 0; } int idx = s->nitems; s->nitems++; return idx; } int meow_sheet_open(meow_sheet *s) { if (!s) return -1; const meow_theme *th = meow_theme_get(); meow_input_start(); meow_input_drain(); s->done = 0; s->result = -1; 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); meow_ui_text(pad, py + pad, s->title, th->font_title, th->onSurface); for (int i = 0; i < s->nitems; i++) { int ry = py + pad + meow_ui_text_height(th->font_title) + 12 + i * MEOW_MODAL_ROW; if (ry + MEOW_MODAL_ROW > H - 8) break; meow_ui_text(pad, ry + (MEOW_MODAL_ROW - meow_ui_text_height(th->font_body)) / 2, s->items[i], th->font_body, th->onSurface); meow_ui_fill_rect(pad, ry + MEOW_MODAL_ROW - 1, W - 2 * pad, 1, th->outlineVariant); } meow_ui_flush(); 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; } /* 点面板外=取消 */ int title_h = meow_ui_text_height(th->font_title); int cy = py + pad + title_h + 12; int idx = (y - cy) / MEOW_MODAL_ROW; if (idx >= 0 && idx < s->nitems) { s->result = idx; s->done = 1; } break; } else if (e.type == MEOW_EV_SWIPE && e.a == MEOW_SWIPE_LEFT) { s->done = 1; s->result = -1; } } 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); }