/* test_notif_center.c — M7.3 通知中心内层迁 SDK 控件(宿主验证)。 * * 复现 notif_center.c M7.3 SDK 路径所建控件集(meow_listview 虚拟化 vsource + set_item_draw 行自绘 + * set_empty_text 空态 + set_swipe_delete 左滑删除 + 双 meow_slider + meow_switch + 自定义画布 chip),验证: * P1 listview 渲染通知行:行卡非背景像素(图标/标题/正文)+ set_item_draw 命中(含滚动换算); * P2 空态:count==0 且 set_empty_text 设了 → 视口内居中画文案(非背景像素); * P3 左滑删除:begin/move(超阈值)/end → on_delete 回调恰一次 + swipe 状态复位(撤销回调不触发); * P4 滑条/开关:meow_slider_set/get 与 meow_switch_set/get 往返一致(值写回控件); * P5 自定义画布 chip:meow_widget_rect 取几何 + render 出非背景像素 + meow_widget_hit 命中(owner 闸门开)。 * * 几何/控件创建与 notif_center.c「M7.3 SDK 路径」镜像(owner 钉 NCP_WIDGET_ACT;行高 NCP_ROW_H)。 * 注:宿主不含 launcher sb_icons / s_ui_theme / g_rows,故行/芯片用 meow_ui_text + 简化绘制复现契约; * notif 行自绘函数体(ncp_lv_row_draw)依赖 launcher 全局,真机由 notif_center.c 提供。 * * 编译(在 patch_v2/meow_launcherd 下,宿主 gcc): * gcc -Wall -Wextra -o test_notif_center test_notif_center.c \ * ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \ * ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \ * ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \ * ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \ * ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \ * ../sdk/meow_ui_scroll.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \ * ../sdk/meow_notify_ipc.c ../sdk/stb_image.c -I../sdk -DMEOW_UI_HOST_TEST -pthread -lm * 运行:./test_notif_center */ #include "meow_ui.h" #include "meow_ui_internal.h" /* meow_ui_back_buf / meow_gfx_mark_dirty / meow_widget_rect */ #include #include #include #include static int g_pass = 0, g_fail = 0; #define CHECK(cond, ...) do { \ if (cond) { g_pass++; } \ else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \ } while (0) /* ---- 与 notif_center.c / meow_launcher_internal.h 同源几何(镜像) ---- */ #define XRES 480 #define YRES 720 #define NCP_TOP 28 #define NCP_CHIP_Y 86 #define NCP_CHIP_H 44 #define NCP_CHIP_W 70 #define NCP_CHIP_GAP 8 #define NCP_CHIP_N 5 #define NCP_SLIDER_X 20 #define NCP_SLIDER_W (XRES - 40) #define NCP_SL1_Y 162 #define NCP_SL2_Y 214 #define NCP_DND_Y 264 #define NCP_DND_H 40 #define NCP_DND_SPLIT_X (XRES / 2) #define NCP_LIST_TOP 308 #define NCP_LIST_BOT 694 #define NCP_ROW_H 92 #define NCP_ROW_X 12 #define NCP_ROW_W (XRES - 24) #define NCP_ICON_X 26 #define NCP_TEXT_X 72 #define NCP_TEXT_W (NCP_ROW_W - (NCP_TEXT_X - NCP_ROW_X) - 10) #define NCP_WIDGET_ACT 0x54 #define NCP_MAX_ROWS 8 /* 通知行镜像(宿主自持,替代 launcher g_rows[]/g_ncp_apps)。 */ typedef struct { const char *summary; const char *body; int progress; } trow_t; static trow_t g_rows[NCP_MAX_ROWS]; static int g_nrows = 0; /* ---- 像素探测 ---- */ static int px565(int x, int y, uint32_t bg) { unsigned short bg565 = ((bg >> 8) & 0xF800) | ((bg >> 5) & 0x07E0) | ((bg >> 3) & 0x001F); unsigned short p; unsigned char *bb = meow_ui_back_buf(); int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); if (!bb || x < 0 || y < 0) { p = 0; return 0; } if (bpp == 16) p = *(unsigned short *)(bb + (size_t)y * line + (size_t)x * 2); else p = (unsigned short)(*(unsigned int *)(bb + (size_t)y * line + (size_t)x * 4) >> 16); return (p != bg565) ? 1 : 0; } static int region_nonbg(int x, int y, int w, int h, uint32_t bg) { int cnt = 0; for (int dy = 0; dy < h; dy++) for (int dx = 0; dx < w; dx++) if (px565(x + dx, y + dy, bg)) cnt++; return cnt; } /* ---- listview 虚拟化源(镜像 ncp_lv_count / ncp_lv_bind) ---- */ static int lv_count(void *ctx) { (void)ctx; return g_nrows; } static void lv_bind(int idx, meow_lv_item *it, void *ctx) { (void)ctx; memset(it, 0, sizeof *it); it->enabled = (idx >= 0 && idx < g_nrows) ? 1 : 0; } /* 行自绘(镜像 ncp_lv_row_draw 的 SDK 文本渲染;宿主无 launcher 字体/sb_icons,用 meow_ui_text 近似)。 */ static void lv_draw(int idx, int x, int y, int w, int h, int state, void *ctx) { (void)w; (void)state; (void)ctx; if (idx < 0 || idx >= g_nrows) return; const meow_theme *th = meow_theme_get(); meow_gfx_fill_round(x + NCP_ROW_X, y, NCP_ROW_W, h - 6, 12, th->surfaceContainer); /* 图标位(无图回退:primary 圆点) */ int icx = x + NCP_ICON_X, icy = y + (h - 32) / 2; meow_gfx_fill_round(icx + 16, icy + 16, 16, 16, 8, th->primary); /* app 名 + 标题 + 正文 */ meow_ui_text(x + NCP_TEXT_X, y + 10, "yao", th->font_body, th->onSurfaceVariant); meow_ui_text(x + NCP_TEXT_X, y + 30, g_rows[idx].summary, th->font_body, th->onSurface); meow_ui_text(x + NCP_TEXT_X, y + 56, g_rows[idx].body, th->font_body, th->onSurfaceVariant); if (g_rows[idx].progress >= 0) { int px = x + NCP_TEXT_X, pw = NCP_TEXT_W, py = y + h - 14; meow_ui_fill_rect(px, py, pw, 4, th->surfaceVariant); meow_ui_fill_rect(px, py, g_rows[idx].progress * pw / 100, 4, th->primary); } } /* ---- 左滑删除回调计数 ---- */ static int del_count = 0; static int del_idx = -1; static void lv_on_delete(int idx, void *ctx) { (void)ctx; del_count++; del_idx = idx; } static meow_widget *make_list(void) { meow_widget *lv = meow_listview_create(0, NCP_LIST_TOP, XRES, NCP_LIST_BOT - NCP_LIST_TOP, 0, NULL, NULL, MEOW_LIST_SEL_NONE); meow_listview_set_vsource(lv, lv_count, lv_bind, NULL); meow_listview_set_item_draw(lv, lv_draw, NULL); meow_listview_set_item_h(lv, NCP_ROW_H); meow_listview_set_empty_text(lv, "暂无通知"); meow_listview_set_swipe_delete(lv, 1, lv_on_delete, NULL); return lv; } /* 推进 meow_anim 直到 swipe 动画结束(模拟 on_tick 节拍)。 */ static void pump_swipe(meow_widget *lv) { int guard = 0; while (meow_listview_swipe_active(lv) && guard++ < 80) { meow_anim_tick(); usleep(10000); } meow_anim_tick(); } /* 自定义画布 chip(镜像 ncp_chip_render:圆角 + 文本;用 meow_widget_rect 取几何)。 */ static void chip_render(meow_widget *w, const meow_theme *th, void *ctx) { (void)ctx; int rc[4]; meow_widget_rect(w, rc); meow_gfx_fill_round(rc[0], rc[1], rc[2], rc[3], 12, th->surfaceContainer); meow_ui_text_center(rc[0] + rc[2] / 2, rc[1] + rc[3] / 2, "chip", th->font_body, th->onSurfaceVariant); } int main(void) { meow_ui_opts o = { XRES, YRES }; if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; } const meow_theme *th = meow_theme_get(); /* 通知行数据(镜像:多行 + 进度)。8 行(736px) > 视口(386px) → 可滚动。 */ g_nrows = 8; static const char *summ[8] = {"下载完成","媒体进行中","系统通知","新消息","更新提醒","日程","备份完成","低电量"}; static const char *body[8] = {"文件已就绪","正在播放","低电量提醒","收到 2 条","已就绪","10 分钟后","已成功","请充电"}; for (int i = 0; i < 8; i++) { g_rows[i].summary = summ[i]; g_rows[i].body = body[i]; g_rows[i].progress = (i < 2) ? (100 - i * 60) : -1; } /* ============ P1 listview 渲染通知行 + 行命中 ============ */ printf("P1 notif listview render + hit\n"); { meow_ui_activity_push(NCP_WIDGET_ACT); meow_widget *lv = make_list(); meow_ui_fill_rect(0, 0, XRES, YRES, th->bg); meow_ui_fill_rect(0, NCP_LIST_TOP, XRES, NCP_LIST_BOT - NCP_LIST_TOP, th->surfaceVariant); meow_widget_draw(lv); /* 行 0 卡底(surfaceContainer 覆盖表面 surfaceVariant),行带内非背景像素 */ int row0y = NCP_LIST_TOP + 8; int rc = region_nonbg(NCP_ROW_X, row0y, NCP_ROW_W - 8, NCP_ROW_H - 16, th->surfaceVariant); CHECK(rc > 40, "notif row renders (icon/title/body) cnt=%d", rc); /* 行命中(含滚动换算):点行 1 中心 → idx1 */ CHECK(meow_listview_hit(lv, 100, NCP_LIST_TOP + NCP_ROW_H + NCP_ROW_H / 2) == 1, "row1 center hits idx1 got %d", meow_listview_hit(lv, 100, NCP_LIST_TOP + NCP_ROW_H + NCP_ROW_H / 2)); /* 列表区外 → -1 */ CHECK(meow_listview_hit(lv, 100, NCP_LIST_TOP - 4) == -1, "above list -> -1"); /* 滚动后命中换算:set_scroll=92 时点行 0 中心 → 视口第一行是 idx1 */ meow_listview_set_scroll(lv, NCP_ROW_H); CHECK(meow_listview_hit(lv, 100, NCP_LIST_TOP + NCP_ROW_H / 2) == 1, "scrolled +92px: first visible center hits idx1 got %d", meow_listview_hit(lv, 100, NCP_LIST_TOP + NCP_ROW_H / 2)); CHECK(meow_listview_max_scroll(lv) > 0, "max_scroll>0 (3 rows overflow? got %d)", meow_listview_max_scroll(lv)); meow_widget_free(lv); meow_ui_activity_pop(); } /* ============ P2 空态(set_empty_text,count==0 走空态路径) ============ */ printf("P2 empty state\n"); { meow_ui_activity_push(NCP_WIDGET_ACT); g_nrows = 0; meow_widget *lv = make_list(); meow_ui_fill_rect(0, NCP_LIST_TOP, XRES, NCP_LIST_BOT - NCP_LIST_TOP, th->surfaceVariant); meow_widget_draw(lv); /* count==0 → 不画任何行卡(空态路径:视口仍 surfaceVariant 底,无 surfaceContainer 行卡)。 * 宿主无 CJK 字体,「暂无通知」文案可能不显形,故以「无行卡」为健壮判据(空态路径已走)。 */ int rowc = region_nonbg(NCP_ROW_X, NCP_LIST_TOP + 8, NCP_ROW_W - 8, NCP_ROW_H - 16, th->surfaceVariant); CHECK(rowc == 0, "empty: no row cards rendered cnt=%d", rowc); meow_widget_free(lv); g_nrows = 8; /* 还原 */ meow_ui_activity_pop(); } /* ============ P3 左滑删除(SDK swipe-delete 回调) ============ */ printf("P3 notif row swipe delete\n"); { meow_ui_activity_push(NCP_WIDGET_ACT); del_count = 0; del_idx = -1; meow_widget *lv = make_list(); int row0y = NCP_LIST_TOP + NCP_ROW_H / 2; CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "P3 begin on row0 = 1"); /* 左滑超阈值(NCP_ROW_W*2/5 ≈ 182) */ CHECK(meow_listview_swipe_move(lv, 100 - 260, row0y) == 1, "P3 move big left = 1"); CHECK(meow_listview_swipe_end(lv, 100 - 260, row0y) == 1, "P3 end = 1 (delete anim)"); CHECK(meow_listview_swipe_active(lv) == 1, "P3 delete anim active"); pump_swipe(lv); CHECK(del_count == 1, "P3 on_delete fired once got %d", del_count); CHECK(del_idx == 0, "P3 on_delete idx0 got %d", del_idx); CHECK(meow_listview_swipe_active(lv) == 0, "P3 state reset"); CHECK(meow_listview_swipe_offset(lv) == 0, "P3 dx reset 0 got %d", meow_listview_swipe_offset(lv)); /* 撤销:未超阈值 → 回弹,不触发 on_delete */ del_count = 0; CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "P3 undo begin = 1"); CHECK(meow_listview_swipe_move(lv, 70, row0y) == 1, "P3 undo move left 30px = 1"); CHECK(meow_listview_swipe_end(lv, 70, row0y) == 1, "P3 undo end = 1 (bounce)"); pump_swipe(lv); CHECK(del_count == 0, "P3 undo: on_delete NOT fired got %d", del_count); CHECK(meow_listview_swipe_offset(lv) == 0, "P3 undo dx=0 got %d", meow_listview_swipe_offset(lv)); meow_widget_free(lv); meow_ui_activity_pop(); } /* ============ P4 滑条 / 开关 set/get ============ */ printf("P4 slider/switch roundtrip\n"); { meow_ui_activity_push(NCP_WIDGET_ACT); meow_widget *sl = meow_slider_create(NCP_SLIDER_X, NCP_SL1_Y - 14, NCP_SLIDER_W, 1, 100, 50); CHECK(meow_slider_get(sl) == 50, "P4 slider init got %d", meow_slider_get(sl)); meow_slider_set(sl, 78); CHECK(meow_slider_get(sl) == 78, "P4 slider set 78 got %d", meow_slider_get(sl)); meow_slider_set(sl, 5); CHECK(meow_slider_get(sl) == 5, "P4 slider clamp low 5 got %d", meow_slider_get(sl)); meow_widget *sw = meow_switch_create(0, 0, 0); CHECK(meow_switch_get(sw) == 0, "P4 switch init off got %d", meow_switch_get(sw)); meow_switch_set(sw, 1); CHECK(meow_switch_get(sw) == 1, "P4 switch on got %d", meow_switch_get(sw)); CHECK(meow_widget_selected(sw) == 1, "P4 switch on -> selected bit"); meow_widget_free(sl); meow_widget_free(sw); meow_ui_activity_pop(); } /* ============ P5 自定义画布 chip:几何 + 渲染 + 命中 ============ */ printf("P5 custom chip widget\n"); { meow_ui_activity_push(NCP_WIDGET_ACT); meow_widget *chip = meow_widget_custom_create(NCP_CHIP_Y, NCP_CHIP_Y, NCP_CHIP_W, NCP_CHIP_H, chip_render, NULL, NULL); int rc[4]; meow_widget_rect(chip, rc); CHECK(rc[0] == NCP_CHIP_Y && rc[1] == NCP_CHIP_Y && rc[2] == NCP_CHIP_W && rc[3] == NCP_CHIP_H, "P5 chip rect x=%d y=%d w=%d h=%d", rc[0], rc[1], rc[2], rc[3]); meow_ui_fill_rect(0, 0, XRES, YRES, th->bg); meow_widget_draw(chip); int cc = region_nonbg(rc[0] + 4, rc[1] + 4, rc[2] - 8, rc[3] - 8, th->bg); CHECK(cc > 20, "P5 chip renders non-bg cnt=%d", cc); /* meow_widget_hit(owner 闸门已开):中心命中、区外未命中 */ int cx = rc[0] + rc[2] / 2, cy = rc[1] + rc[3] / 2; CHECK(meow_widget_hit(chip, cx, cy) == 1, "P5 chip center hit"); CHECK(meow_widget_hit(chip, cx + NCP_CHIP_W + 30, cy) == 0, "P5 chip outside miss"); meow_widget_free(chip); meow_ui_activity_pop(); } meow_ui_shutdown(); printf("notif_center: %d pass / %d fail\n", g_pass, g_fail); return g_fail ? 1 : 0; }