sdk(ui): W_LISTVIEW 虚拟化层 — 固定 item 池 + 多行富 item + 完全自绘 (§5.5)
在既有 W_LISTVIEW (src2 每帧取行) 之上叠加虚拟化层 (API 追加式, 旧 API 一字不改): - 数据源 count_fn + bind_fn (index, meow_lv_item*, ctx); count 每帧实时查 (任意长度≥65535, 不常驻 item 对象); bind 仅在行进入视口时被调一次 - 固定 item 池: 视口行数+2 静态槽 (有界 ≤96), 回滚不重绑 (同 index 命中同槽); 整表扫过后 wrap 回收合法但每次 draw 有界 (≤视口窗口) - 多行富 item: 主标题/副标题/左图标·缩略图/右尾件; 行高 set_item_h(px) 或 set_item_tier(1→48/2→56); 默认 renderer 主题语义色绘制; set_item_draw 完全自绘; section 段头占位行(非粘性); 空态占位 - 集成: tap_at→meow_ui_focus_acquire (§6.4); meow_scroll_wrap(lv,1) track+fling+snap(行边界); 选中/按压态走主题; enabled=0 变淡不响应 - 兼容: src2==NULL 走既有路径; W_LIST/W_LISTVIEW 单行用法零改动 验证(宿主): 新测试 test_meow_ui_listvirt T1-T5 全 44 PASS (10000 项滚动窗口内容正确/hit 映射/ bind 有界=count/回滚不重绑/多行渲染/截断/图标占位/段头/空态/自绘/焦点/fling 吸附); 全量回归 widgets/dispatch/box/anim/scroll/ime/pinyin/overlay/notif/sdk_input_keys/p1p2p3 全 PASS; 交叉 zig-mipsel -Wall -Wextra 全 SDK TU 0 warning; meow_ui.a 新 md5=2a2d8000b95dd211d09dc736d5538ad0 文档: MEOW_APP_SDK.md §5.5 同步虚拟化层(API 一览/池语义/集成/回归).
This commit is contained in:
1 parent
85e1d03157
commit
9d54cf89c5
4 files changed
+797
-69
No files matched your search
+327
-68
@@ -90,6 +90,20 @@ struct meow_widget {
|
||||
int sel_bits_n; /* 位图字数 */
|
||||
meow_listview_tap_fn lv_tap_cb; /* 行点击回调(idx, op) */
|
||||
void *lv_tap_ctx; /* 回调 ctx */
|
||||
|
||||
/* §5.5 虚拟化层(W_LISTVIEW 扩展:固定 item 池 + 多行渲染器 + 完全自绘 + 空态。
|
||||
* calloc 归零,安全;vs_count/vs_bind 均非 NULL 才进入 pool 模式,src2 模式不受影响。) */
|
||||
meow_listview_count_fn vs_count; /* 虚拟化总数回调(NULL=src2 模式) */
|
||||
meow_listview_bind_fn vs_bind; /* 虚拟化 bind 回调(NULL=src2 模式) */
|
||||
meow_listview_draw_fn vs_draw; /* 完全自绘回调(非 NULL 覆盖默认 renderer) */
|
||||
void *vs_ctx; /* 上述回调的 ctx */
|
||||
meow_lv_item *vs_pool; /* 固定 item 池(槽数 vs_pool_n) */
|
||||
int vs_pool_n; /* 池容量(视口行数 + 2,静态有界) */
|
||||
int *vs_bound; /* 每槽绑定的行号(-1=空闲;pool 索引 mod 映射) */
|
||||
int lines; /* 行档位 1/2(默认 2=双行) */
|
||||
int show_icon; /* 1=画左图标/缩略图(默认 1) */
|
||||
int show_right;/* 1=画右尾件(默认 1) */
|
||||
char empty_text[64]; /* 空态占位文案(首字符 0=不画) */
|
||||
};
|
||||
|
||||
/* ---- §5.8 batch1 内部辅助:opts 取值(opts_used==0 时按控件内建默认)----
|
||||
@@ -338,6 +352,8 @@ void meow_widget_free(meow_widget *w)
|
||||
meow_draw_widget_unregister(w); /* §6.2:绘制物注册表自动注销(stale id 失效勿用) */
|
||||
live_btn_unregister(w);
|
||||
if (w->sel_bits) { free(w->sel_bits); w->sel_bits = NULL; } /* W_LISTVIEW 多选位图 */
|
||||
if (w->vs_pool) { free(w->vs_pool); w->vs_pool = NULL; } /* W_LISTVIEW 虚拟化池 */
|
||||
if (w->vs_bound) { free(w->vs_bound); w->vs_bound = NULL; }
|
||||
free(w);
|
||||
}
|
||||
|
||||
@@ -923,6 +939,58 @@ extern void meow_ui_img_scaled(int x, int y, int w, int h, meow_img *img) __attr
|
||||
|
||||
static int listv_item_h(const meow_widget *w) { return (w->item_h > 0) ? w->item_h : 56; }
|
||||
|
||||
/* §5.5 虚拟化辅助:当前行数(pool 模式实时查 count_fn;src2 模式用缓存的 w->count)。 */
|
||||
static int listv_count(const meow_widget *w)
|
||||
{
|
||||
if (!w) return 0;
|
||||
if (w->vs_count) { int n = w->vs_count(w->vs_ctx); return n < 0 ? 0 : n; }
|
||||
return w->count;
|
||||
}
|
||||
|
||||
/* 池容量估算:视口行数 + 2 静态槽(按最小行高 MEOW_LV_MIN_ITEM_H 估算,静态有界)。
|
||||
* 真实列表行高远大于最小行高,故 `viewport/item_h + 1 <= cap` 恒成立 → 无槽冲突重绑。 */
|
||||
#define MEOW_LV_MIN_ITEM_H 8 /* 池容量估算用的最小行高(真实列表远大于此) */
|
||||
#define MEOW_LV_POOL_MAX 96 /* 池容量上限(固定;保证静态有界) */
|
||||
static int listv_pool_cap(const meow_widget *w)
|
||||
{
|
||||
int cap = (w->h > 0) ? (w->h / MEOW_LV_MIN_ITEM_H + 2) : 8;
|
||||
if (cap < 8) cap = 8;
|
||||
if (cap > MEOW_LV_POOL_MAX) cap = MEOW_LV_POOL_MAX;
|
||||
return cap;
|
||||
}
|
||||
/* 确保池容量 ≥ cap(有界扩张;pool 模式调用)。返回 0=成功;-1=分配失败(仍可用,槽冲突时防御性重绑)。 */
|
||||
static int listv_ensure_pool(meow_widget *w)
|
||||
{
|
||||
if (!w->vs_count || !w->vs_bind) return 0;
|
||||
int cap = listv_pool_cap(w);
|
||||
if (w->vs_pool && w->vs_pool_n >= cap) return 0;
|
||||
/* 重分配(有界:cap ≤ MEOW_LV_POOL_MAX,与 item 总数无关)。 */
|
||||
meow_lv_item *np = (meow_lv_item *)calloc((size_t)cap, sizeof *np);
|
||||
int *nb = (int *)calloc((size_t)cap, sizeof *nb);
|
||||
if (!np || !nb) { free(np); free(nb); return -1; }
|
||||
if (w->vs_pool && w->vs_pool_n > 0) {
|
||||
memcpy(np, w->vs_pool, (size_t)(w->vs_pool_n < cap ? w->vs_pool_n : cap) * sizeof *np);
|
||||
memcpy(nb, w->vs_bound, (size_t)(w->vs_pool_n < cap ? w->vs_pool_n : cap) * sizeof *nb);
|
||||
}
|
||||
for (int i = 0; i < cap; i++) nb[i] = -1; /* 全新/重分配:全部槽空闲 → 下一帧按需绑定 */
|
||||
free(w->vs_pool); free(w->vs_bound);
|
||||
w->vs_pool = np; w->vs_bound = nb; w->vs_pool_n = cap;
|
||||
return 0;
|
||||
}
|
||||
/* 取 index 对应的池槽:pool 槽按 index mod cap 映射(回滚命中同槽 → 不重绑)。
|
||||
* 槽绑定的 index 不匹配 → 防御性重新 bind(保证内容正确,即使池不足)。返回槽指针。 */
|
||||
static meow_lv_item *listv_slot(meow_widget *w, int index)
|
||||
{
|
||||
if (!w->vs_pool || w->vs_pool_n <= 0) return NULL;
|
||||
int s = (int)((unsigned)index % (unsigned)w->vs_pool_n);
|
||||
if (w->vs_bound[s] != index) {
|
||||
memset(&w->vs_pool[s], 0, sizeof w->vs_pool[s]);
|
||||
w->vs_bind(index, &w->vs_pool[s], w->vs_ctx);
|
||||
w->vs_bound[s] = index;
|
||||
}
|
||||
return &w->vs_pool[s];
|
||||
}
|
||||
|
||||
/* 文本截断:超出 maxw 加 "..."(复用 browser_text_trunc 的逐字符宽度累加)。 */
|
||||
static void listv_text_trunc(int x, int y, const char *s, int maxw, int size, uint32_t col)
|
||||
{
|
||||
@@ -1023,6 +1091,7 @@ meow_widget *meow_listview_create(int x, int y, int w, int h, int count,
|
||||
t->count = count > 0 ? count : 0; t->src2 = src; t->ctx = ctx;
|
||||
t->sel = -1; t->item_h = 56; t->scroll = 0;
|
||||
t->sel_mode = (mode >= MEOW_LIST_SEL_NONE && mode <= MEOW_LIST_SEL_MULTI) ? mode : MEOW_LIST_SEL_NONE;
|
||||
t->lines = 2; t->show_icon = 1; t->show_right = 1; /* 默认双行富 item + 图标 + 右尾件(src2 布局不变) */
|
||||
if (t->sel_mode == MEOW_LIST_SEL_MULTI && t->count > 0) {
|
||||
t->sel_bits_n = (t->count + 31) / 32;
|
||||
t->sel_bits = (uint32_t *)calloc((size_t)t->sel_bits_n, sizeof(uint32_t));
|
||||
@@ -1034,7 +1103,11 @@ meow_widget *meow_listview_create(int x, int y, int w, int h, int count,
|
||||
int meow_listview_set_item_h(meow_widget *w, int item)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return -1;
|
||||
if (item > 0) { w->item_h = item; meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); }
|
||||
if (item > 0) {
|
||||
w->item_h = item;
|
||||
listv_ensure_pool(w); /* 行高变小 → 可见行增多 → 池容量随之调整 */
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
}
|
||||
return w->item_h;
|
||||
}
|
||||
void meow_listview_set_count(meow_widget *w, int count)
|
||||
@@ -1043,10 +1116,11 @@ void meow_listview_set_count(meow_widget *w, int count)
|
||||
if (count < 0) count = 0;
|
||||
if (count == w->count) return;
|
||||
w->count = count;
|
||||
if (w->sel >= w->count) w->sel = -1;
|
||||
if (w->sel_mode == MEOW_LIST_SEL_MULTI) { /* 重分配多选位图 */
|
||||
int n = listv_count(w); /* pool 模式忽略 set_count(count 实时查) */
|
||||
if (w->sel >= n) w->sel = -1;
|
||||
if (w->sel_mode == MEOW_LIST_SEL_MULTI && !w->vs_count) { /* 重分配多选位图(src2 模式) */
|
||||
free(w->sel_bits); w->sel_bits = NULL; w->sel_bits_n = 0;
|
||||
int need = (count + 31) / 32;
|
||||
int need = (n + 31) / 32;
|
||||
if (need > 0) {
|
||||
w->sel_bits_n = need;
|
||||
w->sel_bits = (uint32_t *)calloc((size_t)need, sizeof(uint32_t));
|
||||
@@ -1060,8 +1134,9 @@ void meow_listview_set_count(meow_widget *w, int count)
|
||||
int meow_listview_max_scroll(meow_widget *w)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return 0;
|
||||
int ms = w->count * listv_item_h(w) - w->h;
|
||||
return ms < 0 ? 0 : ms;
|
||||
long long ms = (long long)listv_count(w) * listv_item_h(w) - w->h;
|
||||
if (ms < 0) return 0;
|
||||
return ms > 0x7FFFFFFF ? 0x7FFFFFFF : (int)ms;
|
||||
}
|
||||
|
||||
void meow_listview_set_scroll(meow_widget *w, int offset)
|
||||
@@ -1081,8 +1156,9 @@ void meow_listview_set_sel(meow_widget *w, int idx)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return;
|
||||
if (w->sel_mode == MEOW_LIST_SEL_NONE) return;
|
||||
if (w->count <= 0) { w->sel = -1; return; }
|
||||
if (idx >= w->count) idx = w->count - 1; /* 越界 clamp(§5.5 首/末行钳制) */
|
||||
int n = listv_count(w);
|
||||
if (n <= 0) { w->sel = -1; return; }
|
||||
if (idx >= n) idx = n - 1; /* 越界 clamp(§5.5 首/末行钳制) */
|
||||
if (w->sel_mode == MEOW_LIST_SEL_SINGLE) {
|
||||
if (idx == w->sel) return;
|
||||
w->sel = idx; /* idx<0 = 清除 */
|
||||
@@ -1103,9 +1179,9 @@ int meow_listview_get_sel(meow_widget *w, int *out, int max)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return 0;
|
||||
if (!out || max <= 0) return 0;
|
||||
int n = 0;
|
||||
int n = 0, total = listv_count(w);
|
||||
if (w->sel_mode == MEOW_LIST_SEL_MULTI) {
|
||||
for (int i = 0; i < w->count && n < max; i++)
|
||||
for (int i = 0; i < total && n < max; i++)
|
||||
if (w->sel_bits && (w->sel_bits[i >> 5] & (1u << (i & 31)))) out[n++] = i;
|
||||
} else if (w->sel >= 0 && n < max) {
|
||||
out[n++] = w->sel;
|
||||
@@ -1116,8 +1192,8 @@ int meow_listview_sel_count(meow_widget *w)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return 0;
|
||||
if (w->sel_mode == MEOW_LIST_SEL_MULTI) {
|
||||
int n = 0;
|
||||
for (int i = 0; i < w->count; i++)
|
||||
int n = 0, total = listv_count(w);
|
||||
for (int i = 0; i < total; i++)
|
||||
if (w->sel_bits && (w->sel_bits[i >> 5] & (1u << (i & 31)))) n++;
|
||||
return n;
|
||||
}
|
||||
@@ -1131,12 +1207,18 @@ int meow_listview_hit(meow_widget *w, int x, int y)
|
||||
if (x < w->x || x >= w->x + w->w || y < w->y || y >= w->y + w->h) return -1;
|
||||
int item = listv_item_h(w);
|
||||
int idx = (y - w->y + w->scroll) / item;
|
||||
if (idx < 0 || idx >= w->count) return -1;
|
||||
int n = listv_count(w);
|
||||
if (idx < 0 || idx >= n) return -1;
|
||||
if (w->src2) { /* 禁用行不响应(§5.5) */
|
||||
meow_list_item it;
|
||||
memset(&it, 0, sizeof it);
|
||||
if (!w->src2(idx, &it, w->ctx)) return -1;
|
||||
if (!it.enabled) return -1;
|
||||
} else if (w->vs_bind) { /* pool 模式:bind 临时查 enabled */
|
||||
meow_lv_item it;
|
||||
memset(&it, 0, sizeof it);
|
||||
w->vs_bind(idx, &it, w->vs_ctx);
|
||||
if (!it.enabled) return -1;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
@@ -1151,6 +1233,11 @@ int meow_listview_hit_op(meow_widget *w, int x, int y)
|
||||
memset(&it, 0, sizeof it);
|
||||
if (!w->src2(idx, &it, w->ctx)) return 0;
|
||||
if (it.right == MEOW_LIST_RIGHT_NONE) return 0;
|
||||
} else if (w->vs_bind) {
|
||||
meow_lv_item it;
|
||||
memset(&it, 0, sizeof it);
|
||||
w->vs_bind(idx, &it, w->vs_ctx);
|
||||
if (it.right == MEOW_LIST_RIGHT_NONE) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -1170,6 +1257,70 @@ int meow_listview_tap_at(meow_widget *w, int x, int y)
|
||||
return idx;
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
/* §5.5 虚拟化层 API(W_LISTVIEW 扩展;pool 模式;src2 模式不变) */
|
||||
/* ============================================================ */
|
||||
int meow_listview_set_vsource(meow_widget *w, meow_listview_count_fn count,
|
||||
meow_listview_bind_fn bind, void *ctx)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW || !count || !bind) return -1;
|
||||
w->vs_count = count; w->vs_bind = bind; w->vs_ctx = ctx;
|
||||
w->vs_draw = NULL; /* 重挂源:清自绘(默认 renderer) */
|
||||
if (listv_ensure_pool(w) != 0) { /* 分配固定 item 池(有界) */
|
||||
w->vs_count = NULL; w->vs_bind = NULL; /* 池分配失败:回退 src2 模式 */
|
||||
return -1;
|
||||
}
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_listview_set_item_renderer(meow_widget *w, int lines, int show_icon,
|
||||
int show_right)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return -1;
|
||||
if (lines < 1) lines = 1;
|
||||
if (lines > 2) lines = 2;
|
||||
w->lines = lines; w->show_icon = show_icon ? 1 : 0; w->show_right = show_right ? 1 : 0;
|
||||
if (w->item_h <= 0) w->item_h = (lines == 1) ? 48 : 56; /* 未显式定行高 → 档位默认 */
|
||||
listv_ensure_pool(w); /* 行档位可能变化 → 确保池容量 */
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_listview_set_item_tier(meow_widget *w, int lines)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return -1;
|
||||
if (lines < 1) lines = 1;
|
||||
if (lines > 2) lines = 2;
|
||||
w->lines = lines;
|
||||
w->item_h = (lines == 1) ? 48 : 56; /* 档位默认高(可随后 set_item_h 覆盖) */
|
||||
listv_ensure_pool(w);
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_listview_set_item_draw(meow_widget *w, meow_listview_draw_fn draw,
|
||||
void *ctx)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return -1;
|
||||
w->vs_draw = draw; w->vs_ctx = ctx; /* 自绘覆盖默认 renderer;NULL=用默认 */
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_listview_set_empty_text(meow_widget *w, const char *text)
|
||||
{
|
||||
if (!w || w->type != W_LISTVIEW) return -1;
|
||||
if (text) {
|
||||
strncpy(w->empty_text, text, sizeof w->empty_text - 1);
|
||||
w->empty_text[sizeof w->empty_text - 1] = '\0';
|
||||
} else {
|
||||
w->empty_text[0] = '\0';
|
||||
}
|
||||
meow_gfx_mark_dirty(w->x, w->y, w->w, w->h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hit(meow_widget *w, int x, int y)
|
||||
{
|
||||
if (!w) return 0;
|
||||
@@ -1366,79 +1517,187 @@ void meow_widget_draw(meow_widget *w)
|
||||
break;
|
||||
}
|
||||
case W_LISTVIEW: {
|
||||
/* §5.5 结构化列表(纵向 LazyColumn):只画可见行(虚拟化)+ 视口裁剪(M2.73 纪律)。
|
||||
* 行 = 图标(左) + 标题 + 副标题(次要色) + 右侧操作区;选中行 = primaryContainer /
|
||||
* onPrimaryContainer(§5.8.3);禁用行变淡不响应(命中由 hit 层保证)。全语义色。 */
|
||||
/* §5.5 结构化列表(纵向):只画可见行 + 视口裁剪(M2.73)。三路:
|
||||
* ① src2 模式(既有):每帧按需取行(meow_list2_source),行为不变;
|
||||
* ② 虚拟化 pool 模式(set_vsource + set_item_renderer):固定 item 池(进入视口才 bind),
|
||||
* 默认 renderer 画多行富 item(图标+标题+副标题+右尾件),行高可配;滚动只移 viewport;
|
||||
* ③ 完全自绘(set_item_draw):App 自绘每可见行(SDK 只给矩形+状态位)。
|
||||
* 空态:count==0 且设 empty_text → 视口内居中。全语义色;选中/按压/禁用态走主题。 */
|
||||
int item = listv_item_h(w);
|
||||
int vp = w->h; /* 纵向 */
|
||||
int off = w->scroll;
|
||||
int maxs = w->count * item - vp;
|
||||
int count = listv_count(w);
|
||||
int maxs = (int)((long long)count * item - vp);
|
||||
if (maxs < 0) maxs = 0;
|
||||
if (off < 0) off = 0;
|
||||
if (off > maxs) off = maxs;
|
||||
int first = (item > 0) ? off / item : 0;
|
||||
int last = (item > 0) ? (off + vp) / item + 1 : 0;
|
||||
if (first < 0) first = 0;
|
||||
if (last > w->count) last = w->count;
|
||||
if (last > count) last = count;
|
||||
int vx0 = w->x, vy0 = w->y, vy1 = w->y + w->h;
|
||||
int thh = meow_ui_text_height(th->font_body);
|
||||
int pad = MEOW_LISTV_PAD, isz = MEOW_LISTV_ICON;
|
||||
int text_x = vx0 + pad + isz + pad;
|
||||
int op_start = vx0 + (w->w * 2 / 3); /* 行右 1/3 = 操作区(§5.5 命中) */
|
||||
int text_maxw = op_start - pad - text_x; /* 标题/副标题可用宽(不压操作区) */
|
||||
int text_x = vx0 + pad + ((w->show_icon) ? (isz + pad) : 0); /* 无图标则无图标间隙 */
|
||||
int op_start = (w->show_right) ? (vx0 + (w->w * 2 / 3)) /* 行右 1/3 = 操作区 */
|
||||
: (vx0 + w->w - pad);
|
||||
int text_maxw = (w->show_right) ? (op_start - pad - text_x) /* 标题/副标题可用宽(不压操作区) */
|
||||
: ((vx0 + w->w - pad) - text_x);
|
||||
if (text_maxw < 8) text_maxw = 8;
|
||||
for (int i = first; i < last; i++) {
|
||||
meow_list_item it;
|
||||
memset(&it, 0, sizeof it);
|
||||
if (!w->src2 || !w->src2(i, &it, w->ctx)) continue;
|
||||
int ry = w->y + (i * item - off);
|
||||
if (ry >= vy1) break;
|
||||
if (ry + item <= vy0) continue;
|
||||
int cl = ry > vy0 ? ry : vy0; /* 可见顶界(裁剪用) */
|
||||
int cr = (ry + item) < vy1 ? (ry + item) : vy1; /* 可见底界 */
|
||||
int sel = (i == w->sel);
|
||||
uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface;
|
||||
uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
|
||||
if (sel) meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->primaryContainer);
|
||||
|
||||
/* 图标(左) */
|
||||
uint32_t iccol = sel ? th->onPrimaryContainer
|
||||
: ((it.icon_kind == MEOW_LIST_ICON_FOLDER) ? th->primary
|
||||
: th->onSurfaceVariant);
|
||||
int icy = ry + (item - isz) / 2;
|
||||
if (icy + isz >= vy0 && icy < vy1) /* 图标不越视口边界才画(防泄漏) */
|
||||
listv_draw_icon(th, vx0 + pad, icy, it.icon_kind, it.icon, iccol);
|
||||
/* 空态占位(§5.5):count==0 且设了 empty_text → 视口内居中画文案。 */
|
||||
if (count == 0 && w->empty_text[0]) {
|
||||
int ty = vy0 + (w->h - thh) / 2;
|
||||
meow_ui_text_center(vx0 + w->w / 2, ty, w->empty_text, th->font_body,
|
||||
th->onSurfaceVariant);
|
||||
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
|
||||
break;
|
||||
}
|
||||
|
||||
/* 标题 + 副标题(两行居中;无副标题=单行居中;越界不画防泄漏) */
|
||||
int ty;
|
||||
if (it.subtitle && it.subtitle[0]) {
|
||||
ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
||||
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it.title, text_maxw, th->font_body, textcol);
|
||||
int sy = ty + thh + MEOW_LISTV_SUB_GAP;
|
||||
if (sy >= vy0 && sy + thh <= vy1)
|
||||
listv_text_trunc(text_x, sy, it.subtitle, text_maxw, th->font_body, subcol);
|
||||
} else {
|
||||
ty = ry + (item - thh) / 2;
|
||||
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it.title, text_maxw, th->font_body, textcol);
|
||||
if (w->vs_count && w->vs_bind) { /* 虚拟化 pool 模式 */
|
||||
listv_ensure_pool(w); /* 防御:视口/行高变化时确保池容量(cap 已够则零开销保留绑定) */
|
||||
if (w->vs_draw) { /* ③ 完全自绘:App 画整行(widget clip 已裁到列表视口) */
|
||||
for (int i = first; i < last; i++) {
|
||||
int ry = w->y + (i * item - off);
|
||||
if (ry >= vy1) break;
|
||||
int st = 0;
|
||||
if (i == w->sel && w->sel_mode != MEOW_LIST_SEL_NONE) st |= MEOW_LISTVIEW_ST_SELECTED;
|
||||
if (meow_input_touch_down() &&
|
||||
meow_input_touch_x() >= vx0 && meow_input_touch_x() < vx0 + w->w &&
|
||||
meow_input_touch_y() >= ry && meow_input_touch_y() < ry + item)
|
||||
st |= MEOW_LISTVIEW_ST_PRESSED;
|
||||
w->vs_draw(i, vx0, ry, w->w, item, st, w->vs_ctx);
|
||||
}
|
||||
} else { /* ② 默认 renderer:固定 item 池(进入视口才 bind) */
|
||||
for (int i = first; i < last; i++) {
|
||||
meow_lv_item *it = listv_slot(w, i); /* 绑定到固定池槽(回滚同槽不重绑) */
|
||||
if (!it) continue;
|
||||
int ry = w->y + (i * item - off);
|
||||
if (ry >= vy1) break;
|
||||
if (ry + item <= vy0) continue;
|
||||
int cl = ry > vy0 ? ry : vy0; /* 可见顶界(裁剪用) */
|
||||
int cr = (ry + item) < vy1 ? (ry + item) : vy1;
|
||||
int sel = (i == w->sel && w->sel_mode != MEOW_LIST_SEL_NONE);
|
||||
int press = meow_input_touch_down() &&
|
||||
meow_input_touch_x() >= vx0 && meow_input_touch_x() < vx0 + w->w &&
|
||||
meow_input_touch_y() >= ry && meow_input_touch_y() < ry + item;
|
||||
|
||||
if (it->section) { /* 段头占位行(非粘性):surfaceContainerHigh 底 + primary 段名 */
|
||||
meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->surfaceContainerHigh);
|
||||
int sty = ry + (item - thh) / 2;
|
||||
if (it->title && sty >= vy0 && sty + thh <= vy1)
|
||||
listv_text_trunc(text_x, sty, it->title, text_maxw, th->font_body, th->primary);
|
||||
int sy = ry + item - 1;
|
||||
if (sy >= cl && sy < cr)
|
||||
meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant);
|
||||
if (!it->enabled)
|
||||
meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 选中/按压背景(primaryContainer / 半透明主题色按压层) */
|
||||
if (sel) meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->primaryContainer);
|
||||
else if (press) meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->primary, 26);
|
||||
uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface;
|
||||
uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
|
||||
|
||||
/* 左图标/缩略图(show_icon=1;越界不画防泄漏) */
|
||||
if (w->show_icon) {
|
||||
uint32_t iccol = sel ? th->onPrimaryContainer
|
||||
: ((it->icon_kind == MEOW_LIST_ICON_FOLDER) ? th->primary
|
||||
: th->onSurfaceVariant);
|
||||
int icy = ry + (item - isz) / 2;
|
||||
if (icy + isz >= vy0 && icy < vy1)
|
||||
listv_draw_icon(th, vx0 + pad, icy, it->icon_kind, it->icon, iccol);
|
||||
}
|
||||
|
||||
/* 标题 + 副标题(双行居中;单行(lines==1 或无副标题)单行居中);越界不画 */
|
||||
if (w->lines == 2 && it->subtitle && it->subtitle[0]) {
|
||||
int ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
||||
if (it->title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it->title, text_maxw, th->font_body, textcol);
|
||||
int sy = ty + thh + MEOW_LISTV_SUB_GAP;
|
||||
if (sy >= vy0 && sy + thh <= vy1)
|
||||
listv_text_trunc(text_x, sy, it->subtitle, text_maxw, th->font_body, subcol);
|
||||
} else {
|
||||
int ty = ry + (item - thh) / 2;
|
||||
if (it->title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it->title, text_maxw, th->font_body, textcol);
|
||||
}
|
||||
|
||||
/* 右尾件(show_right=1:箭头/开关/复选/⋯) */
|
||||
if (w->show_right && it->right != MEOW_LIST_RIGHT_NONE) {
|
||||
int ax = w->x + w->w - pad - 24; /* 右缘动作槽(约 24px) */
|
||||
if (ax >= op_start)
|
||||
listv_draw_right(th, ax, ry, item, it->right, it->on, subcol);
|
||||
}
|
||||
|
||||
/* 分隔线(仅落在可见带内才画) */
|
||||
int sy = ry + item - 1;
|
||||
if (sy >= cl && sy < cr)
|
||||
meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant);
|
||||
|
||||
/* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */
|
||||
if (!it->enabled)
|
||||
meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* ① src2 模式(既有):每帧按需取行(meow_list2_source),行为不变。
|
||||
* 行 = 图标(左) + 标题 + 副标题(次要色) + 右侧操作区;选中行 = primaryContainer。 */
|
||||
for (int i = first; i < last; i++) {
|
||||
meow_list_item it;
|
||||
memset(&it, 0, sizeof it);
|
||||
if (!w->src2 || !w->src2(i, &it, w->ctx)) continue;
|
||||
int ry = w->y + (i * item - off);
|
||||
if (ry >= vy1) break;
|
||||
if (ry + item <= vy0) continue;
|
||||
int cl = ry > vy0 ? ry : vy0; /* 可见顶界(裁剪用) */
|
||||
int cr = (ry + item) < vy1 ? (ry + item) : vy1; /* 可见底界 */
|
||||
int sel = (i == w->sel);
|
||||
uint32_t textcol = sel ? th->onPrimaryContainer : th->onSurface;
|
||||
uint32_t subcol = sel ? th->onPrimaryContainer : th->onSurfaceVariant;
|
||||
if (sel) meow_ui_fill_rect(vx0, cl, w->w, cr - cl, th->primaryContainer);
|
||||
|
||||
/* 右侧操作区 */
|
||||
if (it.right != MEOW_LIST_RIGHT_NONE) {
|
||||
int ax = w->x + w->w - pad - 24; /* 右缘动作槽(约 24px) */
|
||||
if (ax >= op_start)
|
||||
listv_draw_right(th, ax, ry, item, it.right, it.on, subcol);
|
||||
/* 图标(左) */
|
||||
uint32_t iccol = sel ? th->onPrimaryContainer
|
||||
: ((it.icon_kind == MEOW_LIST_ICON_FOLDER) ? th->primary
|
||||
: th->onSurfaceVariant);
|
||||
int icy = ry + (item - isz) / 2;
|
||||
if (icy + isz >= vy0 && icy < vy1) /* 图标不越视口边界才画(防泄漏) */
|
||||
listv_draw_icon(th, vx0 + pad, icy, it.icon_kind, it.icon, iccol);
|
||||
|
||||
/* 标题 + 副标题(两行居中;无副标题=单行居中;越界不画防泄漏) */
|
||||
int ty;
|
||||
if (it.subtitle && it.subtitle[0]) {
|
||||
ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
||||
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it.title, text_maxw, th->font_body, textcol);
|
||||
int sy = ty + thh + MEOW_LISTV_SUB_GAP;
|
||||
if (sy >= vy0 && sy + thh <= vy1)
|
||||
listv_text_trunc(text_x, sy, it.subtitle, text_maxw, th->font_body, subcol);
|
||||
} else {
|
||||
ty = ry + (item - thh) / 2;
|
||||
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(text_x, ty, it.title, text_maxw, th->font_body, textcol);
|
||||
}
|
||||
|
||||
/* 右侧操作区 */
|
||||
if (it.right != MEOW_LIST_RIGHT_NONE) {
|
||||
int ax = w->x + w->w - pad - 24; /* 右缘动作槽(约 24px) */
|
||||
if (ax >= op_start)
|
||||
listv_draw_right(th, ax, ry, item, it.right, it.on, subcol);
|
||||
}
|
||||
|
||||
/* 分隔线(仅落在可见带内才画) */
|
||||
int sy = ry + item - 1;
|
||||
if (sy >= cl && sy < cr)
|
||||
meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant);
|
||||
|
||||
/* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */
|
||||
if (!it.enabled)
|
||||
meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90);
|
||||
}
|
||||
|
||||
/* 分隔线(仅落在可见带内才画) */
|
||||
int sy = ry + item - 1;
|
||||
if (sy >= cl && sy < cr)
|
||||
meow_ui_fill_rect(vx0, sy, w->w, 1, th->outlineVariant);
|
||||
|
||||
/* 禁用行整体变淡(§5.5/§5.8.3 enabled=0 → 变暗) */
|
||||
if (!it.enabled)
|
||||
meow_ui_fill_rect_alpha(vx0, cl, w->w, cr - cl, th->onSurface, 90);
|
||||
}
|
||||
if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90);
|
||||
break;
|
||||
|
||||
Reference in new issue
Block a user