/* * meow_lazygrid.c — §12.4 2 维虚拟化宫格(M7.4 主界面前置)。 * * 自包含控件(非 meow_widget,不注册 z 序/绘制注册表):宿主在 draw 阶段调 meow_lazygrid_draw、 * 命中阶段调 meow_lazygrid_hit/tap_at、滚动喂 meow_scroll(meow_lazygrid_scroll 返回会话)。 * 只实例化**可见行**:固定 item 池(视口行数 + 2 × cols,静态有界 ≤MEOW_LAZYGRID_POOL_MAX), * bind 仅在 item 进入视口时调一次(回滚同槽不重绑,同 listview 池语义)。 * * 内存纪律:池在 create/set_vsource/set_cell(冷路径)分配;draw/hit/set_scroll 热路径零 malloc。 * 颜色全取 meow_theme_get() 语义色(日/夜/style 自动),不硬编码。 */ #include "meow_ui.h" #include "include/meow_lazygrid.h" #include "meow_ui_internal.h" /* meow_gfx_clip_push/pop + meow_gfx_fill_round */ #include "include/meow_ui_scroll.h" #include #include struct meow_lazygrid { int x, y, w, h; /* 视口矩形(绝对坐标) */ int cols; /* 列数 */ int cell_w, cell_h, gap; /* 格宽/格高/间距 */ int row_h; /* 行高 = cell_h + gap(缓存) */ int scroll; /* 滚动偏移(像素) */ int sel; /* 选中项(-1=无) */ meow_lazygrid_count_fn count_fn; void *vctx; meow_lazygrid_bind_fn bind; meow_lazygrid_draw_fn draw_cb; void *draw_ctx; meow_lazygrid_tap_fn tap_cb; void *tap_ctx; /* 固定 item 池(冷路径分配;slot 按 item index mod pool_n 映射) */ meow_lazygrid_item *pool; int *pool_idx; /* 每槽绑定的 item index(-1=空闲) */ int pool_n; char empty_text[64]; /* 空态占位(首字符 0=不画) */ }; /* 行高(= cell_h + gap)。 */ int meow_lazygrid_row_h(const meow_lazygrid *g) { return g ? g->row_h : 0; } /* 总行数。 */ static int lg_rows(const meow_lazygrid *g) { int n = g->count_fn ? g->count_fn(g->vctx) : 0; if (n < 0) n = 0; if (n == 0) return 0; return (n + g->cols - 1) / g->cols; } /* 总项数。 */ static int lg_count(const meow_lazygrid *g) { return g->count_fn ? g->count_fn(g->vctx) : 0; } /* 内容总高(行高 × 行数 − 末行后无 gap)。 */ static int lg_content_h(const meow_lazygrid *g) { int rows = lg_rows(g); if (rows <= 0) return 0; return rows * g->cell_h + (rows - 1) * g->gap; } /* ---- 生命周期 ---- */ meow_lazygrid *meow_lazygrid_create(int x, int y, int w, int h, int cols, int cell_w, int cell_h, int gap) { if (cols < 1) cols = 1; meow_lazygrid *g = (meow_lazygrid *)calloc(1, sizeof *g); if (!g) return NULL; g->x = x; g->y = y; g->w = w; g->h = h; g->cols = cols; g->cell_w = cell_w > 0 ? cell_w : 1; g->cell_h = cell_h > 0 ? cell_h : 1; g->gap = gap > 0 ? gap : 0; g->row_h = g->cell_h + g->gap; g->sel = -1; return g; } void meow_lazygrid_free(meow_lazygrid *g) { if (!g) return; free(g->pool); free(g->pool_idx); free(g); } void meow_lazygrid_set_rect(meow_lazygrid *g, int x, int y, int w, int h) { if (!g) return; g->x = x; g->y = y; g->w = w; g->h = h; } void meow_lazygrid_set_cols(meow_lazygrid *g, int cols) { if (!g) return; if (cols < 1) cols = 1; if (g->cols == cols) return; g->cols = cols; } void meow_lazygrid_set_cell(meow_lazygrid *g, int cell_w, int cell_h, int gap) { if (!g) return; g->cell_w = cell_w > 0 ? cell_w : 1; g->cell_h = cell_h > 0 ? cell_h : 1; g->gap = gap > 0 ? gap : 0; g->row_h = g->cell_h + g->gap; /* 行高变化 → 可见行数变 → 池容量随之调整 */ } /* ---- item 池(冷路径;有界 ≤MEOW_LAZYGRID_POOL_MAX)---- */ static int lg_pool_cap(const meow_lazygrid *g) { int rh = g->row_h; if (rh < 8) rh = 8; int rows = (g->h / rh) + 1; if (rows < 1) rows = 1; int cap = (rows + 2) * g->cols; if (cap < 8) cap = 8; if (cap > MEOW_LAZYGRID_POOL_MAX) cap = MEOW_LAZYGRID_POOL_MAX; return cap; } /* 确保池容量 ≥ cap(有界扩张;重分配清空绑定 → 下一帧按需重绑)。返回 0=成功;-1=分配失败。 */ static int lg_ensure_pool(meow_lazygrid *g) { if (!g->count_fn || !g->bind) return 0; int cap = lg_pool_cap(g); if (g->pool && g->pool_n >= cap) return 0; meow_lazygrid_item *np = (meow_lazygrid_item *)calloc((size_t)cap, sizeof *np); int *ni = (int *)calloc((size_t)cap, sizeof *ni); if (!np || !ni) { free(np); free(ni); return -1; } for (int i = 0; i < cap; i++) ni[i] = -1; free(g->pool); free(g->pool_idx); g->pool = np; g->pool_idx = ni; g->pool_n = cap; return 0; } /* 取 index 对应的池槽(按 item index mod pool_n 映射;槽绑定不匹配 → 防御性重绑)。 */ static meow_lazygrid_item *lg_slot(meow_lazygrid *g, int index) { if (!g->pool || g->pool_n <= 0) return NULL; int s = (int)((unsigned)index % (unsigned)g->pool_n); if (g->pool_idx[s] != index) { memset(&g->pool[s], 0, sizeof g->pool[s]); g->bind(index, &g->pool[s], g->vctx); g->pool_idx[s] = index; } return &g->pool[s]; } /* ---- 数据源 + 渲染 ---- */ int meow_lazygrid_set_vsource(meow_lazygrid *g, meow_lazygrid_count_fn count, meow_lazygrid_bind_fn bind, void *ctx) { if (!g || !count || !bind) return -1; g->count_fn = count; g->bind = bind; g->vctx = ctx; if (lg_ensure_pool(g) != 0) { /* 分配固定 item 池(有界,冷路径) */ g->count_fn = NULL; g->bind = NULL; /* 池分配失败:回退为空 */ return -1; } return 0; } int meow_lazygrid_set_item_draw(meow_lazygrid *g, meow_lazygrid_draw_fn draw, void *ctx) { if (!g) return -1; g->draw_cb = draw; g->draw_ctx = ctx; /* NULL=用默认卡片 renderer */ return 0; } int meow_lazygrid_set_empty_text(meow_lazygrid *g, const char *text) { if (!g) return -1; if (text) { strncpy(g->empty_text, text, sizeof g->empty_text - 1); g->empty_text[sizeof g->empty_text - 1] = '\0'; } else { g->empty_text[0] = '\0'; } return 0; } /* ---- 滚动 ---- */ int meow_lazygrid_max_scroll(meow_lazygrid *g) { if (!g) return 0; int ch = lg_content_h(g); int ms = ch - g->h; return ms > 0 ? ms : 0; } void meow_lazygrid_set_scroll(meow_lazygrid *g, int off) { if (!g) return; int ms = meow_lazygrid_max_scroll(g); if (off < 0) off = 0; if (off > ms) off = ms; g->scroll = off; } void meow_lazygrid_scroll_by(meow_lazygrid *g, int dy) { if (g) meow_lazygrid_set_scroll(g, g->scroll + dy); } int meow_lazygrid_scroll(meow_lazygrid *g) { return g ? g->scroll : 0; } /* ---- meow_scroll 集成(callbacks 指回本宫格)---- */ static void lg_s_set(void *c, int off) { meow_lazygrid_set_scroll((meow_lazygrid *)c, off); } static int lg_s_max(void *c) { return meow_lazygrid_max_scroll((meow_lazygrid *)c); } static int lg_s_get(void *c) { return meow_lazygrid_scroll((meow_lazygrid *)c); } static void lg_s_redraw(void *c) { meow_lazygrid *g = (meow_lazygrid *)c; if (g && g->w > 0 && g->h > 0) { meow_lazygrid_draw(g, meow_theme_get()); meow_ui_flush_region(g->x, g->y, g->w, g->h); /* 只刷视口区 */ } } meow_scroll *meow_lazygrid_scroll_attach(meow_lazygrid *g) { if (!g) return NULL; return meow_scroll_create(lg_s_set, lg_s_max, lg_s_get, lg_s_redraw, g); } /* ---- 选中 ---- */ void meow_lazygrid_set_sel(meow_lazygrid *g, int idx) { if (!g) return; int n = lg_count(g); if (idx < 0) { g->sel = -1; return; } if (n > 0 && idx >= n) idx = n - 1; g->sel = idx; } int meow_lazygrid_sel(const meow_lazygrid *g) { return g ? g->sel : -1; } void meow_lazygrid_set_on_tap(meow_lazygrid *g, meow_lazygrid_tap_fn cb, void *ctx) { if (!g) return; g->tap_cb = cb; g->tap_ctx = ctx; } int meow_lazygrid_tap_at(meow_lazygrid *g, int x, int y) { int idx = meow_lazygrid_hit(g, x, y); if (idx < 0) return -1; meow_lazygrid_set_sel(g, idx); if (g->tap_cb) g->tap_cb(idx, g->tap_ctx); return idx; } /* ---- 命中(滚动 + 网格坐标 → item idx)---- * 点在格内(跨过 gap 区域返回 -1);禁用格不响应(同 listview enabled=0 语义)。 */ int meow_lazygrid_hit(meow_lazygrid *g, int x, int y) { if (!g) return -1; if (x < g->x || x >= g->x + g->w || y < g->y || y >= g->y + g->h) return -1; /* 列格(水平):col = px / (cell_w+gap),落点在 gap 内 → 未命中。 */ int px = x - g->x; int cstep = g->cell_w + g->gap; int col = px / cstep; if (col < 0 || col >= g->cols) return -1; if ((px - col * cstep) >= g->cell_w) return -1; /* 行格(垂直,含滚动):row = (py+scroll)/row_h;落点在 gap 内 → 未命中。 */ int py = y - g->y + g->scroll; int rstep = g->row_h; int row = py / rstep; if (row < 0) return -1; if ((py - row * rstep) >= g->cell_h) return -1; int idx = row * g->cols + col; int n = lg_count(g); if (idx < 0 || idx >= n) return -1; /* 禁用格不响应(顺手 bind 临时 enabled 查:与 listview hit 同法)。 */ if (g->bind) { meow_lazygrid_item it; memset(&it, 0, sizeof it); g->bind(idx, &it, g->vctx); if (!it.enabled) return -1; } return idx; } /* ---- 绘制(只画可见行;clip 钳到宫格视口)---- */ static void lg_draw_cell(meow_lazygrid *g, const meow_theme *th, int index, int cell_x, int cell_y, meow_lazygrid_item *it) { int sel = (index == g->sel); int txtcol = sel ? th->onPrimaryContainer : th->onSurface; int subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant; uint32_t bg = sel ? th->primaryContainer : th->surfaceContainerLow; meow_gfx_fill_round(cell_x, cell_y, g->cell_w, g->cell_h, th->radius_card, bg); int pad = 8; int thh = meow_ui_text_height(th->font_body); int chh = g->cell_h, cw = g->cell_w; /* 图标(可选):居中于格顶 ~50%。 */ if (it->icon) { int isz = cw * 6 / 10; if (isz > chh * 5 / 10) isz = chh * 5 / 10; if (isz < 16) isz = 16; int icx = cell_x + cw / 2, icy = cell_y + pad + isz / 2; meow_ui_img_scaled(icx - isz / 2, icy - isz / 2, isz, isz, (meow_img *)it->icon); } /* 标题 + 副标题(主界面:名 + 状态)。底部对齐。 */ int has_sub = (it->subtitle && it->subtitle[0]); int ty = cell_y + chh - pad - thh - (has_sub ? (thh + 2) : 0); if (it->title) meow_ui_text_center(cell_x + cw / 2, ty, it->title, th->font_body, txtcol); if (has_sub) meow_ui_text_center(cell_x + cw / 2, ty + thh + 2, it->subtitle, th->font_body, subcol); /* 选中描边(轮廓);禁用整体变淡。 */ if (sel) meow_ui_draw_rect_round(cell_x, cell_y, cw, chh, th->radius_card, th->primary); if (!it->enabled) meow_ui_fill_rect_alpha(cell_x, cell_y, cw, chh, th->onSurface, 90); } void meow_lazygrid_draw(meow_lazygrid *g, const meow_theme *th) { if (!g || !th) return; meow_gfx_clip_push(g->x, g->y, g->w, g->h); /* 格画不出宫格视口(防泄漏) */ int n = lg_count(g); int rows = lg_rows(g); lg_ensure_pool(g); /* 防御:视口/列几何变化时确保池容量 */ /* 空态占位:count==0 且设 empty_text → 视口内居中。 */ if (n == 0 && g->empty_text[0]) { int thh = meow_ui_text_height(th->font_body); meow_ui_text_center(g->x + g->w / 2, g->y + (g->h - thh) / 2, g->empty_text, th->font_body, th->onSurfaceVariant); meow_gfx_clip_pop(); return; } int off = g->scroll; int cstep = g->cell_w + g->gap; int rstep = g->row_h; if (rstep <= 0) rstep = 1; int first = off / rstep; int last = (off + g->h) / rstep + 1; if (last > rows) last = rows; if (first < 0) first = 0; if (last > rows) last = rows; for (int r = first; r < last; r++) { int ry = g->y + (r * rstep - off); /* 本行格顶视口坐标 */ for (int c = 0; c < g->cols; c++) { int index = r * g->cols + c; if (index >= n) break; /* 末行余下空格 */ int cx = g->x + c * cstep; /* 行完全滚出视口(裁剪由 clip 兜底,这里跳过整行) */ if (ry >= g->y + g->h) break; if (ry + g->cell_h <= g->y) continue; if (g->draw_cb) { int st = (index == g->sel) ? MEOW_LAZYGRID_ST_SELECTED : 0; g->draw_cb(index, cx, ry, g->cell_w, g->cell_h, st, g->draw_ctx); } else { meow_lazygrid_item *it = lg_slot(g, index); if (!it) continue; lg_draw_cell(g, th, index, cx, ry, it); } } } meow_gfx_clip_pop(); }