/* ui/demo/sdk2/sdk2.c — 控件库 Demo 页内「SDK 批次2 新控件」CONTAINER 子视图(agent-363)。 * * 目标(SDK_VIEW_FRAMEWORK_DESIGN.md §B.4/§E③,同 list.c):把 demo 页底部的三个 SDK 批次2 * 新控件演示区块抽成一个 meow_view CONTAINER 子视图,挂到 demo ACTIVITY view 的 children: * ① meow_select (§5.8.9 枚举/预设选择器:下拉模态 + 单选列表 + on_change 回调) * ② meow_lazygrid (§12.4 2 维虚拟化宫格:非 meow_widget,on_draw 直绘 meow_lazygrid_draw) * ③ meow_listview 左滑删除(§5.5 swipe-delete:swipe_begin/move/end + on_delete + 回弹/删除动画) * * 职责边界(诚实,与 list.c 同折中): * - 本子视图承接三节**整体**:章节标题 + select 按钮 + 宫格 + swipe-列表 的创建/释放与视图态 * (grid 虚拟源/选中/滚动、swipe 行集/滚动、select 选中值)。控件**创建**在 demo_build_at 链路内 * (sdk2_build_at,owner=ACT_DEMO)、**释放**在 sdk2_release(demo_free_content 调)——与 list.c * 相同折中:完整「控件归容器拥有」需 SDK 侧支持(本批冻结 patch_v2/sdk/)。 * - meow_lazygrid 是**非 meow_widget**:不注册 z 序/绘制注册表。本子视图 on_draw 直接调 * meow_lazygrid_draw(view → 注册表 → invalidate 链路,与 list 区块标题同法)。 * - 章节标题(三段)与 select 当前值文本也走 on_draw 直绘(列表区标题同法),不逐标题注册 * CUSTOM 绘制物;on_draw 是回调、view 不缓存像素(非保留,§5.8.7)。 * - 嵌套生命周期由 SDK view_enter/view_leave 递归驱动(随 demo 页进出,同 list.c);绘制经 * meow_draw_view_entered(含祖先已进入判定)在 meow_ui_paint_full/invalidate 触发。 * * 事件接线(demo 页把 touch/tap/swipe 分发改派到本子视图,实现「子>父」命中): * - tap → sdk2_hit (select 按钮 / 宫格 tap / 列表行 tap / 行左滑松手 swipe_end) * - down → sdk2_down (宫格聚焦拖拽 / 列表行左滑 begin) * - move → sdk2_move (宫格滚动 / 列表左滑 move 或列表竖向滚动) * - swipe → sdk2_swipe (宫格/列表聚焦时滚自身;否则回退页面滚动) */ #include "hello_internal.h" #include #include #include /* ---- 本子视图(CONTAINER,非页面:id 用越界值避免占用 g_view_by_id 顶层页槽位)---- */ static meow_view *g_sdk2_view = NULL; /* ---- demo 内容布局常量(与 demo.c 一致):内容区 x16 宽 448 ---- */ #define S2_X 16 #define S2_W 448 #define S2_DCW 212 #define S2_DBH 44 #define S2_CH 52 /* 三节(章节标题几何在 sdk2_build_at 计算后存入,on_draw 直绘用;屏幕坐标)。 */ static int s2_t1_y, s2_t2_y, s2_t3_y; /* 三节标题 y(on_draw 直绘) */ /* ============================================================ */ /* ① meow_select — 枚举/预设选择器 */ /* ============================================================ */ static meow_widget *g_btn_sel = NULL; /* 「打开选择器」按钮(sdk2_release 释放) */ static meow_select *g_sel = NULL; /* 选择器实例(sdk2_view_create 建一次,destroy 释放) */ static rect_t r_selbtn = { 0, 0, 0, 0 }; static rect_t r_selval = { 0, 0, 0, 0 }; /* 当前选择值文本区(on_draw 直绘) */ static int g_sel_cur = 0; /* 当前选中项(跨重建保留;on_change 更新) */ static const char *g_sel_items[] = { /* 枚举演示:音频档位(对齐 M7.2 设置页采样率/码率) */ "Auto", "44.1kHz", "48kHz", "96kHz", "192kHz", }; #define SEL_N ((int)(sizeof g_sel_items / sizeof g_sel_items[0])) /* on_change 回调(meow_select_open 确认后触发:更新选中下标 + 重画值文本区)。 */ static void sdk2_sel_change(int idx, void *ctx) { (void)ctx; if (idx >= 0 && idx < SEL_N) g_sel_cur = idx; if (r_selval.w > 0) meow_ui_invalidate(r_selval.x, r_selval.y, r_selval.w, r_selval.h); } /* 打开选择器(阻塞到确认/取消;on_change 已更新选中值)。 */ static void sdk2_select_open(void) { if (!g_sel) return; int r = meow_select_open(g_sel); meow_log(MEOW_LOG_INFO, "sdk2 select return=%d (%s)", r, (r >= 0 && r < SEL_N) ? g_sel_items[r] : "cancel"); } /* ============================================================ */ /* ② meow_lazygrid — 2 维虚拟化宫格(非 widget) */ /* ============================================================ */ static meow_lazygrid *g_grid = NULL; static rect_t r_grid = { 0, 0, 0, 0 }; static int g_grid_scroll = 0; /* 跨重建保留:宫格滚动偏移 */ static int g_grid_sel = -1; /* 跨重建保留:选中项 */ static int g_drag_grid = 0; /* 触摸拖宫格(down 置位;move 滚动) */ static int g_grid_focus = 0; /* 1=宫格聚焦(swipe 滚宫格而非页面) */ static int g_grid_last_y = 0; #define GRID_N 36 /* 总项数(count > 视口行数 → 可滚动) */ #define GRID_COLS 3 #define GRID_CW 138 #define GRID_CH 86 #define GRID_GAP 10 #define GRID_H 190 /* 虚拟源:count 总项数(实时查);bind 项进入视口时调一次(同 listview 池语义)。 */ static int grid_count(void *ctx) { (void)ctx; return GRID_N; } static void grid_bind(int idx, meow_lazygrid_item *item, void *ctx) { (void)ctx; static char t[24], s[24]; snprintf(t, sizeof t, "App %d", idx); snprintf(s, sizeof s, "v1.%d", idx); memset(item, 0, sizeof *item); item->title = t; item->subtitle = s; item->enabled = (idx % 9 != 8); /* 演示禁用变淡(每 9 个禁用 1 个) */ } /* 完全自绘 cell(set_item_draw):icon+label 风格,对齐主界面用途。 */ static void grid_cell(int idx, int x, int y, int w, int h, int st, void *ctx) { (void)ctx; const meow_theme *th = meow_theme_get(); int sel = st & MEOW_LAZYGRID_ST_SELECTED; int dis = st & MEOW_LAZYGRID_ST_DISABLED; uint32_t bg = sel ? th->primaryContainer : th->surfaceContainerLow; uint32_t icon = dis ? th->onSurfaceVariant : th->primary; uint32_t txt = sel ? th->onPrimaryContainer : (dis ? th->onSurfaceVariant : th->onSurface); meow_ui_fill_rect(x, y, w, h, bg); /* 格底卡片 */ /* icon 块(无图资源:主题 primary 方块代替主界面图标位) */ int isz = w / 3; if (isz > h / 2) isz = h / 2; meow_ui_fill_rect(x + (w - isz) / 2, y + 10, isz, isz, icon); /* 名 + 状态(底部两行,中心对齐) */ static char t[24], s[24]; snprintf(t, sizeof t, "App %d", idx); snprintf(s, sizeof s, "v1.%d", idx); int thh = meow_ui_text_height(th->font_body); meow_ui_text_center(x + w / 2, y + h - thh - 22, t, th->font_body, txt); meow_ui_text_center(x + w / 2, y + h - thh - 3, s, th->font_body, txt); if (sel) meow_ui_draw_rect_round(x, y, w, h, th->radius_card, th->primary); if (dis) meow_ui_fill_rect_alpha(x, y, w, h, th->onSurface, 90); } /* 宫格 tap:置选中 + 局部刷 + 日志。 */ static void grid_tap(int idx, void *ctx) { (void)ctx; g_grid_sel = idx; meow_log(MEOW_LOG_INFO, "sdk2 grid tap idx=%d", idx); if (r_grid.w > 0) meow_ui_invalidate(r_grid.x, r_grid.y, r_grid.w, r_grid.h); } /* ============================================================ */ /* ③ meow_listview 行左滑删除(swipe-delete) */ /* ============================================================ */ #define LV_MAX 9 static meow_widget *g_lv = NULL; /* W_LISTVIEW(注册表自动画:行 + 右露删除区) */ static rect_t r_lv = { 0, 0, 0, 0 }; static int g_lv_deleted[LV_MAX]; /* 1=已删除(行移除;count 实时收缩) */ static char g_lv_label[LV_MAX][24]; /* 行标题(bind 填充) */ static int g_lv_scroll = 0; /* 跨重建保留:列表滚动 */ static int g_lv_focus = 0; /* 1=swipe-list 聚焦(swipe 滚列表而非页面) */ static int g_lv_swipe = 0; /* 行左滑在途(swipe_begin 命中;hit 里 swipe_end) */ static int g_drag_lv = 0; /* 触摸拖列表(竖向滚动);d. 左滑时同置位 */ static int g_lv_last_y = 0; #define LV_H 190 static int lv_count_remaining(void) { int n = 0; for (int i = 0; i < LV_MAX; i++) if (!g_lv_deleted[i]) n++; return n; } static int lv_count(void *ctx) { (void)ctx; return lv_count_remaining(); } /* 显示下标(0..count-1)→ 源行 id(跳过已删除行)。删除后行前移,身份按源行稳定。 */ static int lv_real_index(int display) { if (display < 0) return -1; int c = 0; for (int i = 0; i < LV_MAX; i++) { if (g_lv_deleted[i]) continue; if (c == display) return i; c++; } return -1; } static void lv_bind(int idx, meow_lv_item *item, void *ctx) { (void)ctx; int real = lv_real_index(idx); static char s[24]; memset(item, 0, sizeof *item); snprintf(s, sizeof s, "swipe left"); item->title = (real >= 0) ? g_lv_label[real] : ""; item->subtitle = s; item->icon_kind = (real >= 0 && real % 3 == 0) ? MEOW_LIST_ICON_FOLDER : MEOW_LIST_ICON_NONE; item->right = MEOW_LIST_RIGHT_NONE; item->enabled = 1; } /* on_delete(swipe 删除动画滑出完成回调):删除该显示下标对应的源行 + 局部刷。 * 注:swipe_end 传的是显示下标(含删除前定位的 swipe_idx);映射回源行 id 标记。 */ static void lv_delete(int idx, void *ctx) { (void)ctx; int real = lv_real_index(idx); if (real >= 0) g_lv_deleted[real] = 1; meow_log(MEOW_LOG_INFO, "sdk2 swipe delete display=%d real=%d", idx, real); if (g_lv && r_lv.w > 0) meow_ui_invalidate(r_lv.x, r_lv.y, r_lv.w, r_lv.h); } /* 建三节时填充行标题(demo_init 一次性;跨重建保留)。 */ static void sdk2_init_rows(void) { for (int i = 0; i < LV_MAX; i++) { snprintf(g_lv_label[i], sizeof g_lv_label[i], "notif %d", i); g_lv_deleted[i] = 0; } } static int s2_built = 0; /* 1=本子视图内容已建(on_draw / 事件分发判空) */ /* ============================================================ */ /* 释放 / 建三节 */ /* ============================================================ */ /* 内容释放(demo_free_content 调):清引用(本子视图建的控件在 sdk2_release 释放)。 */ void sdk2_release(void) { if (g_grid) { meow_lazygrid_free(g_grid); g_grid = NULL; } if (g_btn_sel) { meow_widget_free(g_btn_sel); g_btn_sel = NULL; } if (g_lv) { meow_widget_free(g_lv); g_lv = NULL; } r_selbtn = r_selval = r_grid = r_lv = RX(0, 0, 0, 0); g_drag_grid = 0; g_grid_focus = 0; g_grid_last_y = 0; g_lv_focus = 0; g_lv_swipe = 0; g_drag_lv = 0; g_lv_last_y = 0; s2_built = 0; if (g_sdk2_view) g_sdk2_view->x = g_sdk2_view->y = g_sdk2_view->w = g_sdk2_view->h = 0; } /* 内容重建后建三节(demo_build_at 调)。入参:yb_base=base 空间 y;ct/scroll 同 demo.c * (SY(base)=ct+base-scroll)。返回下一 base y。 */ int sdk2_build_at(int yb_base, int ct, int scroll) { #define SY2(b) (ct + (b) - scroll) int yb = yb_base; /* ---- ① SELECT ---- */ s2_t1_y = SY2(yb); yb += 26; r_selbtn = RX(S2_X, SY2(yb), S2_DCW, S2_DBH); g_btn_sel = meow_btn_create_variant(r_selbtn.x, r_selbtn.y, r_selbtn.w, r_selbtn.h, "CHOOSE", MEOW_BTN_OUTLINED); r_selval = RX(S2_X + S2_DCW + 14, SY2(yb) + 10, S2_W - S2_DCW - 14, 24); yb += S2_CH; /* ---- ② LAZYGRID ---- */ s2_t2_y = SY2(yb); yb += 30; r_grid = RX(S2_X, SY2(yb), S2_W, GRID_H); g_grid = meow_lazygrid_create(r_grid.x, r_grid.y, r_grid.w, r_grid.h, GRID_COLS, GRID_CW, GRID_CH, GRID_GAP); if (g_grid) { meow_lazygrid_set_vsource(g_grid, grid_count, grid_bind, NULL); meow_lazygrid_set_item_draw(g_grid, grid_cell, NULL); meow_lazygrid_set_empty_text(g_grid, "No apps"); meow_lazygrid_set_on_tap(g_grid, grid_tap, NULL); meow_lazygrid_set_scroll(g_grid, g_grid_scroll); meow_lazygrid_set_sel(g_grid, g_grid_sel); } yb += GRID_H + 30; /* ---- ③ SWIPE-DELETE ---- */ s2_t3_y = SY2(yb); yb += 26; r_lv = RX(S2_X, SY2(yb), S2_W, LV_H); g_lv = meow_listview_create(r_lv.x, r_lv.y, r_lv.w, r_lv.h, 0, NULL, NULL, MEOW_LIST_SEL_NONE); if (g_lv) { meow_listview_set_item_h(g_lv, 48); meow_listview_set_vsource(g_lv, lv_count, lv_bind, NULL); meow_listview_set_item_renderer(g_lv, 2, 1, 0); /* 双行、左图标、无右尾件 */ meow_listview_set_empty_text(g_lv, "All rows deleted"); meow_listview_set_swipe_delete(g_lv, 1, lv_delete, NULL); meow_listview_set_scroll(g_lv, g_lv_scroll); } yb += LV_H + 10; if (g_sdk2_view) { g_sdk2_view->x = S2_X; g_sdk2_view->y = s2_t1_y; g_sdk2_view->w = S2_W; g_sdk2_view->h = (r_lv.y + r_lv.h) - s2_t1_y; } s2_built = 1; return yb; #undef SY2 } /* ---- on_draw:三节标题 + select 当前值 + 宫格(非保留,invalidate 触发重画)---- */ static void sdk2_draw_on(meow_view *v, const meow_theme *th, void *ctx) { (void)v; (void)th; (void)ctx; if (!s2_built) return; const meow_theme *t = meow_theme_get(); meow_ui_text(S2_X, s2_t1_y, "SELECT (enum/preset)", 20, t->primary); meow_ui_text(S2_X, s2_t2_y, "LAZYGRID (2D virtualized)", 20, t->primary); meow_ui_text(S2_X, s2_t3_y, "LISTVIEW swipe-delete", 20, t->primary); char buf[40]; snprintf(buf, sizeof buf, "sel = %s", g_sel_items[g_sel_cur]); meow_ui_text(r_selval.x, r_selval.y, buf, 20, t->dim); if (g_grid) meow_lazygrid_draw(g_grid, t); } /* ============================================================ */ /* 事件分发(demo 页转发) */ /* ============================================================ */ /* tap:select 按钮 → 打开模态;列表行左滑松手 → swipe_end;宫格 → tap_at;列表行 → tap。 */ int sdk2_hit(int x, int y) { if (!s2_built) return 0; /* 行左滑【松手】最优先:滑动后手指可能已移出列表矩形(只判在途标志)。 */ if (g_lv && g_lv_swipe) { meow_listview_set_scroll(g_lv, g_lv_scroll); int c = meow_listview_swipe_end(g_lv, x, y); /* 判删/回弹;动画由 meow_anim_tick 推进 */ g_lv_swipe = 0; if (c) return 1; /* 消费(删/回弹动画中);未滑动=0 落行选中 */ } if (g_btn_sel && meow_widget_hit(g_btn_sel, x, y)) { sdk2_select_open(); meow_ui_invalidate(r_selbtn.x, r_selbtn.y, r_selbtn.w, r_selbtn.h); return 1; } if (g_grid && meow_lazygrid_hit(g_grid, x, y) >= 0) { meow_lazygrid_tap_at(g_grid, x, y); /* 置选中 + grid_tap 回调 + 局部刷 */ return 1; } if (g_lv && meow_listview_hit(g_lv, x, y) >= 0) { meow_listview_tap_at(g_lv, x, y); /* 行点击(演示选中)+ 局部刷 */ meow_ui_invalidate(r_lv.x, r_lv.y, r_lv.w, r_lv.h); return 1; } return 0; } /* touch_down:命中区置拖拽/聚焦。宫格>列表。返回 1=本区消费。 */ int sdk2_down(int x, int y) { if (!s2_built) return 0; /* 新手势先复位本子视图拖拽态:demo_touch_down 只复位 list/slider/page 标志,不触及 sdk2 * 的 g_drag_grid/g_drag_lv/g_lv_swipe(sdk2.c 内部,demo 页经 extern 看不到);不复位则上一条 * sdk2 拖拽残留会在本次「落页内容区」新手势的 move 里继续劫持页面拖动(sdk2_move 守卫误判)。 */ g_drag_grid = 0; g_drag_lv = 0; g_lv_swipe = 0; if (g_grid && meow_lazygrid_hit(g_grid, x, y) >= 0) { g_drag_grid = 1; g_grid_focus = 1; g_grid_last_y = y; return 1; } if (g_lv && meow_listview_hit(g_lv, x, y) >= 0) { meow_listview_set_scroll(g_lv, g_lv_scroll); g_lv_swipe = meow_listview_swipe_begin(g_lv, x, y); /* 命中可滑行 → 1 */ g_lv_focus = 1; g_drag_lv = 1; g_lv_last_y = y; return 1; } return 0; } /* touch_move:宫格滚动 / 列表左滑 move(消费→不滚页面)或列表竖向滚动。返回 1=消费。 */ int sdk2_move(int x, int y) { if (!s2_built) return 0; if (g_drag_grid && g_grid) { int dy = y - g_grid_last_y; g_grid_last_y = y; meow_lazygrid_scroll_by(g_grid, -dy); g_grid_scroll = meow_lazygrid_scroll(g_grid); if (r_grid.w > 0) meow_ui_invalidate(r_grid.x, r_grid.y, r_grid.w, r_grid.h); return 1; } /* 列表滚动须在「本次手势命中列表」才消费(g_drag_lv,由 sdk2_down 命中列表置位)。 * 若仅因 g_lv 存在就无条件消费,会劫持 demo 页内容区拖拽(任何 move 都被这里吃掉, * 页面永不滚动)——与 grid(g_drag_grid 守卫)同构;359 教训同源(SDK 控件局部刷对, * 错在调用方控制流把页面滚动让给了控件)。 */ if (g_drag_lv && g_lv) { if (g_lv_swipe) { /* 行左滑在途:喂 move;1=横向消费;0=纵向主导→取消本行 swipe */ meow_listview_set_scroll(g_lv, g_lv_scroll); if (meow_listview_swipe_move(g_lv, x, y)) return 1; g_lv_swipe = 0; } int dy = y - g_lv_last_y; g_lv_last_y = y; /* 竖向滚动(跟手) */ meow_listview_scroll_by(g_lv, -dy); g_lv_scroll = meow_listview_scroll(g_lv); if (r_lv.w > 0) meow_ui_invalidate(r_lv.x, r_lv.y, r_lv.w, r_lv.h); return 1; } return 0; } /* swipe UP/DOWN:宫格/列表聚焦时滚自身(del 已按方向加权),1=已消费。 */ int sdk2_swipe(int del) { if (!s2_built) return 0; if (g_grid_focus && g_grid) { meow_lazygrid_scroll_by(g_grid, del); g_grid_scroll = meow_lazygrid_scroll(g_grid); if (r_grid.w > 0) meow_ui_invalidate(r_grid.x, r_grid.y, r_grid.w, r_grid.h); return 1; } if (g_lv_focus && g_lv) { meow_listview_scroll_by(g_lv, del); g_lv_scroll = meow_listview_scroll(g_lv); if (r_lv.w > 0) meow_ui_invalidate(r_lv.x, r_lv.y, r_lv.w, r_lv.h); return 1; } return 0; } /* 本次手势是否已被本子视图实时滚过(demo_swipe 早退用,防 on_swipe 二次滚动——同 list 的 g_drag_list)。 */ int sdk2_dragging(void) { return (g_drag_grid || g_drag_lv) ? 1 : 0; } /* ============================================================ */ /* 子视图生命周期(on_enter/on_leave 由 SDK 递归驱动;on_destroy 清引用) */ /* ============================================================ */ static void sdk2_page_enter(meow_view *v, void *ctx) { (void)v; (void)ctx; meow_log(MEOW_LOG_INFO, "demo sdk2 view enter"); } static void sdk2_page_leave(meow_view *v, void *ctx) { (void)v; (void)ctx; meow_log(MEOW_LOG_INFO, "demo sdk2 view leave"); } static void sdk2_page_destroy(meow_view *v, void *ctx) { (void)v; (void)ctx; if (g_sel) { meow_select_free(g_sel); g_sel = NULL; } s2_built = 0; g_sdk2_view = NULL; /* meow_view_free 递归释放前清引用,防悬垂 */ } meow_view *sdk2_view_create(void) { static const meow_view_hooks h = { .on_enter = sdk2_page_enter, .on_leave = sdk2_page_leave, .on_destroy = sdk2_page_destroy, .on_draw = sdk2_draw_on, }; /* id 用非页面值:容器不占 g_view_by_id(那是 page_push/pop 顶层页查找表)。 */ g_sdk2_view = meow_view_create(0x2000, MEOW_VIEW_CONTAINER, &h, NULL); /* meow_select 实例一次性创建(控制态随本子视图存活,不受内容重建影响);行标题一次性填充。 */ sdk2_init_rows(); if (g_sdk2_view && !g_sel) { g_sel = meow_select_create("Sample rate", 0); if (g_sel) { for (int i = 0; i < SEL_N; i++) meow_select_add_item(g_sel, g_sel_items[i]); meow_select_set_on_change(g_sel, sdk2_sel_change, NULL); } } return g_sdk2_view; } meow_view *sdk2_view_get(void) { return g_sdk2_view; }