sdk/: - 同步 meow_ui_widget/input/gfx/lazygrid/audio/media/img 等源码与头 (SDK 默认列表/容器控件默认 MD3 — meow_list_set_container 可回退; 全局 App 内底部上滑回桌面 — meow_ui_input_is_home_swipe 系统保留手势; LazyGrid/LazyColumn/LazyRow MD3 化;叠加让位自抑改 app_id 判定; DSD 音频格式等) - 新增独立模块源码 meow_img_color / meow_lyric / meow_lyric_view 及对应宿主 test_*.c - 保留公开仓自包含适配:vendored meow_font.c/meow_font.h/meow_notif.h 不动; build_sdk.sh 自包含版本不动;meow_ui_gfx.c 仅改 meow_font.h include 路径; meow_ui_internal.h 去除 FreeType 内部声明 - meow_ipc.h 同步到主仓 launcher 版本(含 AUDIO_FMT_DOP/媒体歌词/蓝牙输出协议) - 不含 FreeType 矢量字 meow_ft_*(需 freetype vendor,公开仓以位图 meow_font 自包含);不含 mipsel 二进制 / .o / .a / 固件 / 密钥 / devdocs examples/hello/: - 新增 ui/demo/sdk2/sdk2.c;build_hello.sh 纳入该文件(保持自包含) docs / README / .gitignore: - docs/MEOW_APP_SDK.md 补 §12.5(默认 MD3 容器 + 底部上滑回桌面 + 两个新 API) - README 能力清单补「默认 MD3 容器」与「底部上滑回桌面」 - .gitignore 补新增宿主测试二进制 验证:sdk/meow_ui.a 与 examples/hello/hello 交叉编译均 0 warning;工作区无生成物。
362 lines
14 KiB
C
362 lines
14 KiB
C
/*
|
||
* 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 <string.h>
|
||
#include <stdlib.h>
|
||
|
||
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 钳到宫格视口)---- */
|
||
/* MD3 容器(默认 renderer P0):格=格内对称内缩 surfaceContainer 圆角卡(radius_card)+ 卡内 tonal
|
||
* 选中(primaryContainer)/ 按压(primary α26);画布=整格(触控目标),卡在其内对称内缩(同
|
||
* meow_listview 行>卡范式:卡片外仍命中整格,视觉卡留出呼吸空隙)。完全自绘(set_item_draw)
|
||
* 覆盖本 renderer,App 自理容器观感。 */
|
||
#define MEOW_LAZYGRID_CARD_MARGIN 8 /* 格内对称边距(卡内缩;与 cell gap 叠加出卡间空隙) */
|
||
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;
|
||
int m = MEOW_LAZYGRID_CARD_MARGIN;
|
||
int cw = g->cell_w - 2 * m, chh = g->cell_h - 2 * m; /* 卡尺寸(格内对称内缩) */
|
||
int cx = cell_x + m, cy = cell_y + m; /* 卡左上 */
|
||
int press = meow_input_touch_down() &&
|
||
meow_input_touch_x() >= cell_x && meow_input_touch_x() < cell_x + g->cell_w &&
|
||
meow_input_touch_y() >= cell_y && meow_input_touch_y() < cell_y + g->cell_h;
|
||
meow_gfx_fill_round(cx, cy, cw, chh, th->radius_card, th->surfaceContainer);
|
||
if (sel) meow_gfx_fill_round(cx, cy, cw, chh, th->radius_card, th->primaryContainer);
|
||
else if (press) meow_gfx_fill_round_alpha(cx, cy, cw, chh, th->radius_card, th->primary, 26);
|
||
|
||
int pad = 8;
|
||
int thh = meow_ui_text_height(th->font_body);
|
||
/* 图标(可选):居中于卡顶 ~50%(卡中心=格中心,因对称内缩)。 */
|
||
if (it->icon) {
|
||
int isz = g->cell_w * 6 / 10; if (isz > g->cell_h * 5 / 10) isz = g->cell_h * 5 / 10;
|
||
if (isz < 16) isz = 16;
|
||
int icx = cx + cw / 2, icy = cy + 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 = cy + chh - pad - thh - (has_sub ? (thh + 2) : 0);
|
||
if (it->title) meow_ui_text_center(cx + cw / 2, ty, it->title, th->font_body, txtcol);
|
||
if (has_sub) meow_ui_text_center(cx + cw / 2, ty + thh + 2, it->subtitle, th->font_body, subcol);
|
||
|
||
/* 选中描边(轮廓);禁用卡内变淡(圆角 α,防圆角外叠出灰方块)。 */
|
||
if (sel) meow_ui_draw_rect_round(cx, cy, cw, chh, th->radius_card, th->primary);
|
||
if (!it->enabled)
|
||
meow_gfx_fill_round_alpha(cx, cy, cw, chh, th->radius_card, 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();
|
||
}
|