fix(ncm): 列表首行标题不显示 — 行高按字体行高派生(M12)
根因(真机 seated,非数据/颜色/遮挡):SDK meow_listview 双行渲染把 「2*行高 + 2」的文本块在行内垂直居中,且标题只在 ty >= 视口顶时才画 (meow_ui_widget.c:2061 的越界守卫)。真机 source_han_serif 20px 面 ascent 24 + descent 6 → 行高 30 → 文本块 62px,而 ncm 行高 APP_ROW_H=56 → ty = 行顶 - 3;首行行顶 == 视口顶 → ty < 视口顶 → 标题被整条跳过, 副标题(ty+32)仍在视口内照画。两类列表同用该路径 + 同一行高,故症状一致。 修复(不动 SDK):ncm_app_internal.h 新增 ncm_row_h()/ncm_row_h_for_line_h(), 行高 = max(56, 2*字体行高 + 2 + 8)(偶数;本机 30 → 70,卡高 62 == 文本块); home/playlist/queue/cache/search 在 page create 取一次 g_item_h,统一用于 set_item_h 与卡片几何/可见行/页脚/补载视口换算。 回归: - app/test/test_meow_row_text_fit.c(新):真机字库行高矩阵 14/21/26/27/30 48 断言(文本块 ≤ 行高、首行 ty ≥ 0、卡高 ≥ 文本块、旧 56 哨兵)。 - app/test/test_first_row_title_render.c(新):--wrap 行高=30 驱动真实 SDK 渲染 + 读回 PPM 像素计数:旧 56 → 首行标题 0 像素/副标题 186;新 70 → 标题 84/副标题 100。 - test_meow_row_gap、plpage_sim 全 PASS;ncm_host 7 页出图与改动前逐字节相同 (宿主行高 14 → 行高仍 56,证重构无视觉回归);5 页自检 PASS;0 warning。 - plpage_sim 临时目录改 $TMPDIR(Termux /tmp 不可写,原写死无法运行)。 - REPORT §43:根因/排除方向/修复/验证/真机验收要点。
This commit is contained in:
1 parent
98b9169b86
commit
22f9dab2ce
11 files changed
+440
-34
No files matched your search
@@ -3726,3 +3726,100 @@ if (now - last_meta >= 1000) { send_meta(fd, ...); last_meta = now; }
|
|||||||
(旧:5-7s);launcher 日志 `ipc: META app=dev.player.ncm` 空闲时应约每 30s 一条(旧:~10/s);
|
(旧:5-7s);launcher 日志 `ipc: META app=dev.player.ncm` 空闲时应约每 30s 一条(旧:~10/s);
|
||||||
播放中约每 5s 一条、暂停/切歌各多一条;同时观察 ncm CPU 与首屏/列表滑动是否恢复。
|
播放中约每 5s 一条、暂停/切歌各多一条;同时观察 ncm CPU 与首屏/列表滑动是否恢复。
|
||||||
- **未部署/未 push**:设备只读访问,未改设备;本节所有验证均在宿主完成。
|
- **未部署/未 push**:设备只读访问,未改设备;本节所有验证均在宿主完成。
|
||||||
|
|
||||||
|
## 43. M12:列表首行主标题不显示(两行列表行高 < 文本块,SDK 视口顶裁剪把首行标题整条跳过)(2026-11-05)
|
||||||
|
|
||||||
|
### 43.1 现象(用户原话)
|
||||||
|
|
||||||
|
> 「网易云有一个问题 歌单列表的第1个歌单名字不显示 副标题(歌曲数目)有显示 。点开歌单 加载歌曲列表
|
||||||
|
> 第1个歌曲名字不显示 副标题(歌手名)有显示。」
|
||||||
|
|
||||||
|
特征:两类列表(首页歌单列表 / 歌单详情曲目列表)都只有**首行主标题完全空白**、副标题正常,
|
||||||
|
其余行标题/副标题都正常。设备二进制 431ca4dd(含 M5/M6/M10/M11)。
|
||||||
|
|
||||||
|
### 43.2 根因(代码路径 + 触发机制,已坐实)
|
||||||
|
|
||||||
|
SDK `meow_listview` 的双行渲染(`patch_v2/sdk/meow_ui_widget.c:2061`,src2 分支):
|
||||||
|
|
||||||
|
```c
|
||||||
|
ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
||||||
|
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
||||||
|
listv_text_trunc(rtx, ty, it.title, ...); /* 标题:视口顶裁剪守卫 */
|
||||||
|
int sy = ty + thh + MEOW_LISTV_SUB_GAP;
|
||||||
|
if (sy >= vy0 && sy + thh <= vy1)
|
||||||
|
listv_text_trunc(rtx, sy, it.subtitle, ...); /* 副标题 */
|
||||||
|
```
|
||||||
|
|
||||||
|
- `thh = meow_ui_text_height(theme->font_body)`。真机 ncm 进程(pid 6693)环境:
|
||||||
|
`MEOW_MULTI_FONT=1`、主库 `MEOW_FONT_APP=source_han_serif`;从设备
|
||||||
|
`/usr/data/mnt/sd_0/MeowOS/fonts/source_han_serif/regular/font.idx` 的 MFNI 面头直接读出
|
||||||
|
20px 面 `ascent=24 / descent=6` → `meow_font_line_height(20) = 30`
|
||||||
|
(多字库取最大:google_sans 类库 20px 面 = 26,不改变结果)→ **文本块 = 2*30 + 2 = 62**。
|
||||||
|
- ncm 行高 `APP_ROW_H = 56`(与 SDK `item_h` 默认 56 相同)→ `ty = ry + (56-62)/2 = ry - 3`。
|
||||||
|
- 首行(scroll=0)`ry == vy0`(行顶 == 控件视口顶)→ `ty = vy0 - 3 < vy0` → **标题被整条跳过**
|
||||||
|
(不是画了被裁,是守卫直接不画);`sy = vy0 + 29` 在视口内 → 副标题照画。
|
||||||
|
- 其余行 `ty = ry - 3 > vy0` → 标题仍画(比本行顶高 3px,文字整体仍在视口内,肉眼难察觉)。
|
||||||
|
- 两类列表同用 `meow_listview_create(..., src2, ...)` + 双行 + `APP_ROW_H`,故症状完全一致。
|
||||||
|
|
||||||
|
排除的其它方向:
|
||||||
|
|
||||||
|
| 怀疑方向 | 结论 |
|
||||||
|
|---|---|
|
||||||
|
| ① title 指针悬垂/空串(绑定生命周期) | **排除**:`home_src2`/`pl_src2` 每帧刷新静态缓冲,副标题取自同一缓冲且可见 |
|
||||||
|
| ② 首行被顶部栏/裁剪区遮住(布局偏移) | **排除**:组头红条在 `r_list` 上方 8px;行卡片 z=5、文本 z=10、叠层 z=60 均不覆盖首行标题位 |
|
||||||
|
| ③ 首行字体色 = 背景色(选中/主题色) | **排除**:同一次绘制里 title=`onSurface`、subtitle=`onSurfaceVariant`;且用户确认「完全空白」非同色 |
|
||||||
|
| ④ 首项特殊路径(我喜欢的音乐/当前播放) | **排除**:歌单详情首行是任意曲目,无特殊渲染分支 |
|
||||||
|
|
||||||
|
### 43.3 修复(app 侧,不动 SDK / 不动 meow_ui.a)
|
||||||
|
|
||||||
|
行高改为**按当前主题字体实测行高派生**,保证文本块装得下(卡高 == 文本块):
|
||||||
|
|
||||||
|
| 文件 | 改动 |
|
||||||
|
|---|---|
|
||||||
|
| `app/ncm_app_internal.h` | 新增 `NCM_ROW_SUB_GAP`(与 SDK 同口径 2)、`ncm_row_h_for_line_h(line_h)`(= `max(APP_ROW_H, 2*行高 + 2 + NCM_ROW_GAP)`,偶数)、`ncm_row_h()`(取 `meow_ui_text_height(meow_theme_get()->font_body)`)。`APP_ROW_H` 降级为**行高下限**(注释同步)。 |
|
||||||
|
| `app/ui/home.c` / `playlist.c` / `queue.c` / `cache.c` / `search.c` | 各页 `g_item_h = ncm_row_h()` 在 page create 取一次(字体档位切换走重建页面,避免同一页内行高漂移),`meow_listview_set_item_h` 与全部卡片几何/可见行推算/页脚「x-y / n 首」/补载视口换算统一用 `g_item_h`。 |
|
||||||
|
| `app/test/test_meow_row_text_fit.c`(新) | 纯头内联几何回归(不链 SDK):真机已装字库 20px 面实测行高矩阵 14/21/26/27/30 → 断言文本块 ≤ 行高、首行 ty ≥ 0、副标题不越行、卡高 ≥ 文本块、行高偶数且 ≥ 下限;并用旧 56 + 行高 30 断言 `ty<0`(bug 形态哨兵,防再写死)。 |
|
||||||
|
| `app/test/test_first_row_title_render.c`(新) | **渲染级回归**:宿主 5x7 回退行高是 14,照原样渲染复现不了。用 `-Wl,--wrap=meow_ui_text_height` 把行高替换成真机实测 30,驱动**真实 SDK listview 双行渲染**,读回 `MEOW_UI_DUMP` 的 PPM 数像素:旧行高 56 → 首行标题带 0 文字像素、副标题带 186;新行高 70 → 标题带 84、副标题带 100。 |
|
||||||
|
| `bench/plpage_sim.c` | 滚动换行高改用 `ncm_row_h()`;临时目录/出图改走 `$TMPDIR`(Termux 下 `/tmp` 不可写,原写死 `/tmp` 无法运行)。 |
|
||||||
|
| `build_ncm_host.sh` | **补 `meow_ui_time`**:M11 新增 `sdk/meow_ui_time.c` 后本脚本未同步,main 上宿主链接报 `undefined symbol: meow_ui_time_ms`(与 M12 无关的既有断裂,不补则本 mission 无法出图验证)。 |
|
||||||
|
|
||||||
|
行高取值(本机默认字体 source_han_serif,行高 30):`2*30 + 2 + 8 = 70` → 卡高 62 == 文本块 62,
|
||||||
|
`ty = +4`(首行标题正常落卡内)。其它字库:行高 27 → 64;26 → 62;21 → 52 但受下限钳到 56(与旧视觉一致)。
|
||||||
|
|
||||||
|
### 43.4 验证(宿主,0 真机写、0 真实 NCM 请求)
|
||||||
|
|
||||||
|
| 项 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| `app/test/test_meow_row_text_fit`(新) | **PASS:48 checks / 0 fail**(行高矩阵 5 档;含「旧 56 + 行高 30 → ty=-3」哨兵) |
|
||||||
|
| `app/test/test_first_row_title_render`(新,真实 SDK 渲染 + PPM 像素计数) | **PASS**:legacy `item=56 ty=-3 → title_ink=0 sub_ink=186`(复现用户症状);fixed `item=70 ty=4 → title_ink=84 sub_ink=100`(首行标题正常) |
|
||||||
|
| `app/test/test_meow_row_gap`(既有卡片几何) | **PASS**(row_gap=8 card_h=48 top2top=56 symmetric=1) |
|
||||||
|
| `bench/plpage_sim`(真实 playlist.c 全链路:懒加载分页/触底/回滚补物化) | **RESULT: PASS**(全用例 PASS) |
|
||||||
|
| `ncm_host` 出图(home/playlist/cache/search/searchpl/plsplay/queuetest) | 全部 rc=0;与**改动前基线逐字节相同**(宿主行高 14 → `ncm_row_h()` 仍为 56,几何零变化 → 证明重构本身无视觉回归) |
|
||||||
|
| `ncm_host` 页面自检(queuetest/modetest/barhit/searchtap/cachetest) | 全 PASS(covswitch 因测试自身写死 `/tmp/*.bmp`、Termux 下不可写而 `init FAIL`——与 M12 无关的既有环境限制) |
|
||||||
|
| 宿主告警 | 改动/新增 TU(5 个 ui + ncm_app_internal.h + 2 个 test + 1 个 sim)`-Wall -Wextra` **0 warning**;`build_ncm_host.sh` 全量链接通过 |
|
||||||
|
| 真机只读取证 | 读 `/proc/6693/environ`(字体档/多字体开关)+ 设备 `font.idx` 面头(ascent/descent);**未改设备** |
|
||||||
|
| mipsel / 真机 | **未跑**(本机无 zig 交叉链;交叉编译 + 热更由塔台做) |
|
||||||
|
|
||||||
|
### 43.5 诚实出入 / 遗留
|
||||||
|
|
||||||
|
- **修复形态是「行高变高」**:本机默认字库下行高 56 → 70(每屏可见行数略减)。这是必然代价——旧行高
|
||||||
|
本来就装不下 62px 的两行文本(文字一直溢出卡外 3~7px),只是首行标题被守卫整条吞掉才暴露。
|
||||||
|
若用户切到行高 21 的字库(source_han_sans 等),行高回到 56,视觉与旧版一致。
|
||||||
|
- **滚动时顶部半露行仍会隐藏标题**:SDK 的语义是「标题不完全在视口内就不画」(`ty >= vy0`),
|
||||||
|
这是滚动裁剪的正常表现,不属于本 bug;scroll=0 的首行标题现已正常。
|
||||||
|
- **根因在 SDK 通用渲染**:2 行档默认 `item_h=56` 假定字体行高 ≤27;任何 App 的两行列表在 20px
|
||||||
|
字库(行高 30)下都会丢首行标题。本 mission scope 只到 `patch_v2/ncm_app/**`,故 app 侧派生行高
|
||||||
|
规避;**SDK 侧仍应修**(2 行分支把 `ty` 钳到 `vy0` 或按文本块反推行高),已作为 finding 上报塔台。
|
||||||
|
- **`build_ncm_host.sh` 的 M11 漏编**:本脚本补了 `meow_ui_time`(否则宿主链接失败)。同类还写死
|
||||||
|
`/tmp` 的两处(`bench/plpage_sim.c` 已改 `$TMPDIR`;`app/test/test_ncm_app.c` covswitch 仍写死,
|
||||||
|
未改——与本 bug 无关)。
|
||||||
|
- **未部署/未 push**:设备只读访问,未改设备;所有验证在宿主完成。
|
||||||
|
|
||||||
|
### 43.6 真机验证要点(塔台热更后按此验收)
|
||||||
|
|
||||||
|
1. 首页「我的音乐」歌单列表:**第 1 个歌单名字可见**,副标题「N 首」仍在;第 2 行起视觉正常。
|
||||||
|
2. 点开任一歌单(含「我喜欢的音乐」):**第 1 首歌名可见**,副标题「歌手 / 专辑」仍在。
|
||||||
|
3. 同类页面顺带确认:播放队列(ACT_QUEUE)、已缓存列表(ACT_CACHE)、搜索结果单曲页签,首行标题都在。
|
||||||
|
4. 预期可见变化:每行变高(本机字库下 56 → 70px)、每屏可见行数略减;卡片内两行文字完整、不再溢出卡片。
|
||||||
|
5. 滚动列表:顶部半露行标题不可见属正常裁剪;回到顶部(scroll=0)后首行标题必须重新出现。
|
||||||
|
6. 若系统字体切到 source_han_sans 等小行高字库:行高回落 56,视觉与旧版一致且首行标题仍正常。
|
||||||
@@ -62,14 +62,14 @@ enum {
|
|||||||
#define APP_W 480
|
#define APP_W 480
|
||||||
#define APP_H 720
|
#define APP_H 720
|
||||||
#define APP_TOPBAR_H 56
|
#define APP_TOPBAR_H 56
|
||||||
#define APP_ROW_H 56 /* 列表行高 */
|
#define APP_ROW_H 56 /* 列表行高下限(旧固定值;实际行高按字体行高派生,见 ncm_row_h) */
|
||||||
#define APP_PAD 16
|
#define APP_PAD 16
|
||||||
#define APP_OP_W 48 /* 行右侧操作区触达宽 */
|
#define APP_OP_W 48 /* 行右侧操作区触达宽 */
|
||||||
#define APP_FOOTER_H 26
|
#define APP_FOOTER_H 26
|
||||||
|
|
||||||
/* 圆角卡列表项外边距(item margin/gap):相邻卡片间可见空隙 px。SDK listview 行距(=APP_ROW_H)
|
/* 圆角卡列表项外边距(item margin/gap):相邻卡片间可见空隙 px。SDK listview 行距(=ncm_row_h())
|
||||||
* 不变——卡片在其行高内上下对称内缩 NCM_ROW_GAP/2,故文本(SDK 按 APP_ROW_H 垂直居中)仍落卡中心;
|
* 不变——卡片在其行高内上下对称内缩 NCM_ROW_GAP/2,故文本(SDK 按行高垂直居中)仍落卡中心;
|
||||||
* 相邻卡顶间距恒=APP_ROW_H=卡高+NCM_ROW_GAP。要求偶数,保证上下对称。 */
|
* 相邻卡顶间距恒=行高=卡高+NCM_ROW_GAP。要求偶数,保证上下对称。 */
|
||||||
#define NCM_ROW_GAP 8
|
#define NCM_ROW_GAP 8
|
||||||
/* 第 idx 行(行高 item,滚动 off,列表顶 list_y)的圆角卡:顶 y 与高 h。 */
|
/* 第 idx 行(行高 item,滚动 off,列表顶 list_y)的圆角卡:顶 y 与高 h。 */
|
||||||
static inline int ncm_row_card_y(int item, int idx, int off, int list_y)
|
static inline int ncm_row_card_y(int item, int idx, int off, int list_y)
|
||||||
@@ -78,6 +78,32 @@ static inline int ncm_row_card_y(int item, int idx, int off, int list_y)
|
|||||||
}
|
}
|
||||||
static inline int ncm_row_card_h(int item) { return item - NCM_ROW_GAP; }
|
static inline int ncm_row_card_h(int item) { return item - NCM_ROW_GAP; }
|
||||||
|
|
||||||
|
/* ---- 列表行高(M12:首行标题不显示根因修复)----
|
||||||
|
* SDK meow_listview 的双行渲染(sdk/meow_ui_widget.c 的 2 行分支)把「标题+副标题」文本块
|
||||||
|
* block = 2 * meow_ui_text_height(font_body) + MEOW_LISTV_SUB_GAP(2)
|
||||||
|
* 在行内垂直居中(ty = 行顶 + (item - block) / 2),且标题只在 `ty >= 视口顶` 时才画
|
||||||
|
* (越界钳制,防泄漏)。行高 item 小于 block 时 ty = 行顶 - Δ,于是:
|
||||||
|
* - 第一行(行顶 == 视口顶)标题被钳掉、副标题(ty + thh + 2)仍在视口内 → 真机
|
||||||
|
* 「歌单列表/歌曲列表第 1 行只有副标题」,其余行标题画在自己行顶上方 Δ(不易察觉);
|
||||||
|
* - 文本块也比圆角卡高,两行文字溢出卡外。
|
||||||
|
* 真机字库实测(source_han_serif 20px:ascent 24 + descent 6 = 30)block = 62,旧固定行高
|
||||||
|
* 56 → Δ = 3。故行高必须由字体实测行高派生:block + NCM_ROW_GAP → 卡高 == 文本块,
|
||||||
|
* 文本完整落在卡内并仍居中。行高由 NCM_ROW_GAP 为偶数、2*行高 为偶数保证偶数(卡对称内缩)。 */
|
||||||
|
#define NCM_ROW_SUB_GAP 2 /* 与 SDK MEOW_LISTV_SUB_GAP 同口径(标题/副标题行间距) */
|
||||||
|
static inline int ncm_row_h_for_line_h(int line_h)
|
||||||
|
{
|
||||||
|
if (line_h < 1) line_h = 1; /* 字体未就绪的防御(0 高 → 退回下限) */
|
||||||
|
int h = 2 * line_h + NCM_ROW_SUB_GAP + NCM_ROW_GAP;
|
||||||
|
if (h < APP_ROW_H) h = APP_ROW_H;
|
||||||
|
if (h & 1) h++;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
/* 当前主题字体下的两行列表行高(页面 create 时取值并缓存;字体档位切换走重建页面)。 */
|
||||||
|
static inline int ncm_row_h(void)
|
||||||
|
{
|
||||||
|
return ncm_row_h_for_line_h(meow_ui_text_height(meow_theme_get()->font_body));
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- 播放页(§22 重做:封面页/歌词页双页,NCM_CACHE_DESIGN §6)----
|
/* ---- 播放页(§22 重做:封面页/歌词页双页,NCM_CACHE_DESIGN §6)----
|
||||||
* 沉浸底恒 NCM_PLAYER_BG(不随系统明暗反转——「不动沉浸底」);日夜间主题配色只映射到
|
* 沉浸底恒 NCM_PLAYER_BG(不随系统明暗反转——「不动沉浸底」);日夜间主题配色只映射到
|
||||||
* 半透明叠加层(歌词页 scrim)与 accent(红)。 */
|
* 半透明叠加层(歌词页 scrim)与 accent(红)。 */
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
/* app/test/test_first_row_title_render.c — 宿主渲染回归(M12):列表首行标题必须真正画出来。
|
||||||
|
*
|
||||||
|
* 目的:在宿主上用**真实 SDK listview 双行渲染**复现/验证真机根因,而不只是验几何公式。
|
||||||
|
* 真机字体(source_han_serif 20px,font.idx: ascent 24 + descent 6)行高 = 30,
|
||||||
|
* 双行文本块 = 2*30 + 2 = 62。旧固定行高 56 < 62 → SDK 的 2 行分支
|
||||||
|
* ty = 行顶 + (item - block)/2 = 行顶 - 3
|
||||||
|
* 而标题只在 `ty >= 视口顶` 时才画 → 第一行(行顶 == 视口顶)标题被整条跳过、副标题仍在。
|
||||||
|
*
|
||||||
|
* 宿主 5x7 字体的行高是 14(gfx 回退),照原样渲染永远复现不了。故用链接期
|
||||||
|
* `-Wl,--wrap=meow_ui_text_height` 把行高替换成真机实测的 30,驱动同一份 SDK 代码:
|
||||||
|
* A 旧行高 56 → 首行标题带无文字像素(bug 形态)
|
||||||
|
* B ncm_row_h_for_line_h(30) = 70 → 首行标题带/副标题带都有文字像素(修复形态)
|
||||||
|
* 断言直接读回 MEOW_UI_DUMP 写出的 PPM(按像素计数)。
|
||||||
|
*
|
||||||
|
* 编译/运行(先跑 build_ncm_host.sh 产出 out/host/sdk_*.o):
|
||||||
|
* gcc -O2 -Wall -Wextra -DMEOW_UI_HOST_TEST -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
|
||||||
|
* -Icore -Ivendor/jsmn -Ivendor/mbedtls/include -Ivendor/minimp3 -Ivendor/dr_flac \
|
||||||
|
* app/test/test_first_row_title_render.c out/host/sdk_*.o \
|
||||||
|
* -Wl,--wrap=meow_ui_text_height -lz -lpthread -lm -o test_first_row_title_render
|
||||||
|
* ./test_first_row_title_render # 产物落 $TMPDIR/first_row_*.ppm
|
||||||
|
*
|
||||||
|
* 断言:
|
||||||
|
* ① 行高替换生效(meow_ui_text_height(20) == 30);
|
||||||
|
* ② A:首行标题带零文字像素、副标题带有 → 复现「只有副标题」;
|
||||||
|
* ③ B:首行标题带与副标题带都有像素 → 修复后首行标题正常;
|
||||||
|
* ④ A/B 副标题带像素数都不为 0(副标题路径未受影响)。
|
||||||
|
*/
|
||||||
|
#include "../ncm_app_internal.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* --wrap 的替换实现:真机 source_han_serif 20px 实测行高(font.idx ascent+descent)。 */
|
||||||
|
int __wrap_meow_ui_text_height(int size)
|
||||||
|
{
|
||||||
|
(void)size;
|
||||||
|
return 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define LV_X 16
|
||||||
|
#define LV_Y 40
|
||||||
|
#define LV_W 448
|
||||||
|
#define LV_H 300
|
||||||
|
|
||||||
|
static int src2(int idx, meow_list_item *out, void *ctx)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
static char title[32], sub[32];
|
||||||
|
if (idx < 0 || idx >= 5) return 0;
|
||||||
|
snprintf(title, sizeof title, "T%d", idx + 1);
|
||||||
|
snprintf(sub, sizeof sub, "S%d", idx + 1);
|
||||||
|
memset(out, 0, sizeof *out);
|
||||||
|
out->icon_kind = MEOW_LIST_ICON_NONE;
|
||||||
|
out->title = title;
|
||||||
|
out->subtitle = sub;
|
||||||
|
out->right = MEOW_LIST_RIGHT_NONE;
|
||||||
|
out->enabled = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 极简 PPM(P6) 读取 + 区域文字像素计数 ---- */
|
||||||
|
static unsigned char *ppm_load(const char *path, int *w, int *h)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) { fprintf(stderr, "open %s failed\n", path); return NULL; }
|
||||||
|
char magic[3] = {0};
|
||||||
|
int maxv = 0;
|
||||||
|
if (fscanf(f, "%2s %d %d %d", magic, w, h, &maxv) != 4 ||
|
||||||
|
strcmp(magic, "P6") != 0 || *w <= 0 || *h <= 0) {
|
||||||
|
fclose(f); fprintf(stderr, "bad ppm %s\n", path); return NULL;
|
||||||
|
}
|
||||||
|
if (fgetc(f) < 0) { fclose(f); fprintf(stderr, "truncated ppm %s\n", path); return NULL; } /* 单个空白 */
|
||||||
|
size_t n = (size_t)*w * (size_t)*h * 3u;
|
||||||
|
unsigned char *buf = (unsigned char *)malloc(n);
|
||||||
|
if (!buf || fread(buf, 1, n, f) != n) { free(buf); fclose(f); return NULL; }
|
||||||
|
fclose(f);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 区域内「与页底色不同」的像素数。页底色取 (470,700)(远离列表控件)。 */
|
||||||
|
static int ink_count(const unsigned char *img, int w, int x0, int y0, int x1, int y1)
|
||||||
|
{
|
||||||
|
const unsigned char *bg = img + ((size_t)700 * (size_t)w + 470u) * 3u;
|
||||||
|
int n = 0;
|
||||||
|
for (int y = y0; y < y1; y++)
|
||||||
|
for (int x = x0; x < x1; x++) {
|
||||||
|
const unsigned char *p = img + ((size_t)y * (size_t)w + (size_t)x) * 3u;
|
||||||
|
int d = abs((int)p[0] - (int)bg[0]) + abs((int)p[1] - (int)bg[1]) +
|
||||||
|
abs((int)p[2] - (int)bg[2]);
|
||||||
|
if (d > 24) n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 造一个双行列表(容器=关、无图标/右尾件)并整帧绘制(paint_full 内部 flush → dump PPM)。 */
|
||||||
|
static void render_once(int item)
|
||||||
|
{
|
||||||
|
meow_widget *lv = meow_listview_create(LV_X, LV_Y, LV_W, LV_H, 5,
|
||||||
|
src2, NULL, MEOW_LIST_SEL_NONE);
|
||||||
|
meow_listview_set_container(lv, 0);
|
||||||
|
meow_listview_set_item_renderer(lv, 2, 0, 0);
|
||||||
|
meow_listview_set_item_h(lv, item);
|
||||||
|
meow_listview_set_count(lv, 5);
|
||||||
|
meow_ui_set_page_bg(meow_theme_get()->bg);
|
||||||
|
meow_ui_paint_full(); /* 画 + meow_ui_flush → MEOW_UI_DUMP 落 PPM */
|
||||||
|
meow_widget_free(lv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char g_dump[256];
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
const char *td = getenv("TMPDIR");
|
||||||
|
if (!td || !td[0]) td = "/tmp";
|
||||||
|
snprintf(g_dump, sizeof g_dump, "%s/first_row.ppm", td);
|
||||||
|
setenv("MEOW_UI_DUMP", g_dump, 1);
|
||||||
|
|
||||||
|
meow_ui_opts o = { APP_W, APP_H };
|
||||||
|
if (meow_ui_init(&o) != 0) { fprintf(stderr, "init failed\n"); return 1; }
|
||||||
|
meow_theme_set_mode(MEOW_THEME_DARK);
|
||||||
|
|
||||||
|
int fail = 0;
|
||||||
|
if (meow_ui_text_height(20) != 30) {
|
||||||
|
printf(" FAIL 行高替换未生效(expected 30,got %d)\n", meow_ui_text_height(20));
|
||||||
|
fail = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int lh = 30;
|
||||||
|
const int block = 2 * lh + NCM_ROW_SUB_GAP; /* 62 */
|
||||||
|
const int fixed = ncm_row_h_for_line_h(lh); /* 70 */
|
||||||
|
const int ty_legacy = (APP_ROW_H - block) / 2; /* -3 */
|
||||||
|
const int ty_fixed = (fixed - block) / 2; /* +4 */
|
||||||
|
|
||||||
|
int n_title_a = 0, n_sub_a = 0, n_title_b = 0, n_sub_b = 0;
|
||||||
|
|
||||||
|
/* A:旧固定行高 56(bug 形态)。标题带取「文本行顶 ∩ 控件视口」(ty<0 时从 0 起),
|
||||||
|
* 这样「被裁剪守卫整条跳过」与「只画出上半截」也能区分开。 */
|
||||||
|
render_once(APP_ROW_H);
|
||||||
|
{
|
||||||
|
int pw = 0, ph = 0;
|
||||||
|
unsigned char *img = ppm_load(g_dump, &pw, &ph);
|
||||||
|
if (!img) return 1;
|
||||||
|
int ty0 = (ty_legacy > 0) ? ty_legacy : 0;
|
||||||
|
n_title_a = ink_count(img, pw, LV_X + 16, LV_Y + ty0,
|
||||||
|
LV_X + 16 + 40, LV_Y + ty_legacy + lh);
|
||||||
|
n_sub_a = ink_count(img, pw, LV_X + 16, LV_Y + ty_legacy + lh + NCM_ROW_SUB_GAP,
|
||||||
|
LV_X + 16 + 40, LV_Y + ty_legacy + 2 * lh + NCM_ROW_SUB_GAP);
|
||||||
|
free(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* B:修复后的行高(ncm_row_h_for_line_h) */
|
||||||
|
render_once(fixed);
|
||||||
|
{
|
||||||
|
int pw = 0, ph = 0;
|
||||||
|
unsigned char *img = ppm_load(g_dump, &pw, &ph);
|
||||||
|
if (!img) return 1;
|
||||||
|
n_title_b = ink_count(img, pw, LV_X + 16, LV_Y + ty_fixed,
|
||||||
|
LV_X + 16 + 40, LV_Y + ty_fixed + lh);
|
||||||
|
n_sub_b = ink_count(img, pw, LV_X + 16, LV_Y + ty_fixed + lh + NCM_ROW_SUB_GAP,
|
||||||
|
LV_X + 16 + 40, LV_Y + ty_fixed + 2 * lh + NCM_ROW_SUB_GAP);
|
||||||
|
free(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("legacy item=%d ty=%d: title_ink=%d sub_ink=%d\n", APP_ROW_H, ty_legacy, n_title_a, n_sub_a);
|
||||||
|
printf("fixed item=%d ty=%d: title_ink=%d sub_ink=%d\n", fixed, ty_fixed, n_title_b, n_sub_b);
|
||||||
|
|
||||||
|
if (!(n_title_a == 0 && n_sub_a > 0)) fail = 1; /* ② 旧行高首行标题被跳过(bug 复现) */
|
||||||
|
if (!(n_title_b > 0 && n_sub_b > 0)) fail = 1; /* ③ 修复后首行标题/副标题都在 */
|
||||||
|
|
||||||
|
printf("%s: first-row title render (legacy skipped=%d, fixed drawn=%d)\n",
|
||||||
|
fail ? "FAIL" : "PASS", n_title_a == 0, n_title_b > 0);
|
||||||
|
return fail ? 1 : 0;
|
||||||
|
}
|
||||||
@@ -9,10 +9,12 @@
|
|||||||
* test_meow_row_gap.c -o test_meow_row_gap && ./test_meow_row_gap
|
* test_meow_row_gap.c -o test_meow_row_gap && ./test_meow_row_gap
|
||||||
*
|
*
|
||||||
* 断言集(对应「相邻 item 顶之间 = item 高 + gap」的可见形式):
|
* 断言集(对应「相邻 item 顶之间 = item 高 + gap」的可见形式):
|
||||||
* ① 相邻卡顶间距 = APP_ROW_H(= 卡高 + gap;SDK 行距不变);
|
* ① 相邻卡顶间距 = item 高(= 卡高 + gap;SDK 行距不变);
|
||||||
* ② 卡片间可见外距 = NCM_ROW_GAP;
|
* ② 卡片间可见外距 = NCM_ROW_GAP;
|
||||||
* ③ 卡在其行高内上下对称内缩 → SDK 文本(按 APP_ROW_H 垂直居中)落卡中心;
|
* ③ 卡在其行高内上下对称内缩 → SDK 文本(按行高垂直居中)落卡中心;
|
||||||
* ④ 滚动偏移只整体平移,不改变上述间距/对称。
|
* ④ 滚动偏移只整体平移,不改变上述间距/对称。
|
||||||
|
* 行高在这里固定用 APP_ROW_H(几何对任意 item 成立;M12 起真机行高由 ncm_row_h() 按字体
|
||||||
|
* 行高派生,文本块必须装得下,见 test_meow_row_text_fit.c)。
|
||||||
*/
|
*/
|
||||||
#include "../ncm_app_internal.h" /* APP_ROW_H / NCM_ROW_GAP / ncm_row_card_y / ncm_row_card_h */
|
#include "../ncm_app_internal.h" /* APP_ROW_H / NCM_ROW_GAP / ncm_row_card_y / ncm_row_card_h */
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -20,7 +22,7 @@
|
|||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
const int item = APP_ROW_H; /* 行距(SDK listview item_h,不变) */
|
const int item = APP_ROW_H; /* 几何取固定行高(对任意 item 成立) */
|
||||||
const int gap = NCM_ROW_GAP;
|
const int gap = NCM_ROW_GAP;
|
||||||
const int list_y = 100;
|
const int list_y = 100;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/* app/test/test_meow_row_text_fit.c — 宿主回归(M12):两行列表行高必须容纳行文本块。
|
||||||
|
*
|
||||||
|
* 背景(真机根因):SDK meow_listview 的双行渲染(sdk/meow_ui_widget.c 的 2 行分支)把
|
||||||
|
* block = 2 * meow_ui_text_height(font_body) + 2 (标题行 + 2px 行距 + 副标题行)
|
||||||
|
* 在行内垂直居中:ty = 行顶 + (item - block) / 2;且标题只在 `ty >= 视口顶` 时才画(越界钳制)。
|
||||||
|
* 行高 item < block 时 ty < 行顶 → **第一行(行顶 == 视口顶)标题被整条跳过**、副标题仍在,
|
||||||
|
* 即用户报的「歌单列表/歌曲列表第 1 行只有副标题,名字空白」。
|
||||||
|
* 真机字库实测(source_han_serif 20px:ascent 24 + descent 6 → 行高 30)block = 62,旧固定
|
||||||
|
* 行高 56 → ty = 行顶 - 3。
|
||||||
|
*
|
||||||
|
* 本测试只验 app 侧行高派生(ncm_row_h_for_line_h,纯整数、不链 SDK):
|
||||||
|
* 行高 = max(APP_ROW_H, block + NCM_ROW_GAP)(偶数)→ 卡高 == 文本块,文本完整落在卡内。
|
||||||
|
*
|
||||||
|
* 编译/运行(同 test_meow_row_gap.c,需仓库根 ROOT 下的 SDK 头):
|
||||||
|
* gcc -O2 -Wall -Wextra -I. -I../sdk -I../meow_launcherd -Icore \
|
||||||
|
* -Ivendor/mbedtls/include -Ivendor/jsmn -Ivendor/qrcodegen/c \
|
||||||
|
* -Ivendor/minimp3 -Ivendor/dr_flac \
|
||||||
|
* test_meow_row_text_fit.c -o test_meow_row_text_fit && ./test_meow_row_text_fit
|
||||||
|
*
|
||||||
|
* 断言矩阵 = 真机已装字库 20px 面实测行高(font.idx 的 ascent+descent):
|
||||||
|
* source_han_serif 30(当前生效)、misans/default 27、google_sans/meslo 26、
|
||||||
|
* source_han_sans 21;14 = 宿主 5x7 回退。逐项断言:
|
||||||
|
* ① block ≤ item(否则居中偏移为负)
|
||||||
|
* ② 首行 ty ≥ 行顶(绝对:ty_abs ≥ 视口顶)→ 标题不被裁剪守卫跳过
|
||||||
|
* ③ 副标题底边 sy + 行高 ≤ item(副标题不越行)
|
||||||
|
* ④ 卡高(item - NCM_ROW_GAP)≥ block(两行文本全落在圆角卡内)
|
||||||
|
* ⑤ item ≥ APP_ROW_H 下限且为偶数(卡上下对称内缩 gap/2)
|
||||||
|
* ⑥ 旧固定行高 56 在行高 30 下 ty < 0(bug 形态复现,防止再写死 56)
|
||||||
|
*/
|
||||||
|
#include "../ncm_app_internal.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static int g_checks, g_fail;
|
||||||
|
|
||||||
|
static void chk(int cond, int line_h, const char *what)
|
||||||
|
{
|
||||||
|
g_checks++;
|
||||||
|
if (!cond) {
|
||||||
|
g_fail++;
|
||||||
|
printf(" FAIL lh=%d: %s\n", line_h, what);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
static const int lh_all[] = { 14, 21, 26, 27, 30 };
|
||||||
|
const int n = (int)(sizeof lh_all / sizeof lh_all[0]);
|
||||||
|
const int list_y = 120; /* 任意列表顶(只验相对几何) */
|
||||||
|
|
||||||
|
for (int k = 0; k < n; k++) {
|
||||||
|
int lh = lh_all[k];
|
||||||
|
int item = ncm_row_h_for_line_h(lh);
|
||||||
|
int block = 2 * lh + NCM_ROW_SUB_GAP;
|
||||||
|
int ty = (item - block) / 2; /* SDK 双行文本块居中偏移 */
|
||||||
|
int sy = ty + lh + NCM_ROW_SUB_GAP; /* 副标题行顶 */
|
||||||
|
int card_h = ncm_row_card_h(item);
|
||||||
|
|
||||||
|
printf("lh=%2d item=%3d block=%3d ty=%d sy=%d card_h=%3d\n",
|
||||||
|
lh, item, block, ty, sy, card_h);
|
||||||
|
|
||||||
|
chk(block <= item, lh, "文本块必须 <= 行高(否则首行标题被视口顶裁剪)");
|
||||||
|
chk(ty >= 0, lh, "首行标题 y 不得高于行顶/视口顶");
|
||||||
|
chk(sy + lh <= item, lh, "副标题底边仍在行内");
|
||||||
|
chk(card_h >= block, lh, "两行文本块必须完整落在圆角卡内");
|
||||||
|
chk(item >= APP_ROW_H, lh, "行高不得小于 APP_ROW_H 下限");
|
||||||
|
chk((item & 1) == 0, lh, "行高必须偶数(卡上下对称内缩 gap/2)");
|
||||||
|
|
||||||
|
/* 首行(scroll=0 → 行顶 == 视口顶)的绝对坐标 */
|
||||||
|
int ty_abs = list_y + ty;
|
||||||
|
chk(ty_abs >= list_y, lh, "首行标题绝对 y 不得被视口顶裁掉");
|
||||||
|
chk(ty_abs + lh <= list_y + item, lh, "首行标题底边在行内");
|
||||||
|
|
||||||
|
/* 卡在行内对称 → 卡中心 == 行中心 */
|
||||||
|
int cy = ncm_row_card_y(item, 0, 0, list_y);
|
||||||
|
chk(cy + card_h / 2 == list_y + item / 2, lh, "卡中心 == 行中心");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 行高随字体行高单调不减 */
|
||||||
|
chk(ncm_row_h_for_line_h(30) >= ncm_row_h_for_line_h(26), 30, "行高随字体行高单调不减");
|
||||||
|
|
||||||
|
/* ⑥ 回归哨兵:旧固定行高 56 + 当前默认字体(行高 30)就是 bug 形态 */
|
||||||
|
{
|
||||||
|
const int lh = 30;
|
||||||
|
int block = 2 * lh + NCM_ROW_SUB_GAP; /* 62 */
|
||||||
|
int ty_legacy = (APP_ROW_H - block) / 2; /* -3 */
|
||||||
|
chk(ty_legacy < 0, lh, "旧行高 56 + 行高 30 → ty<0(首行标题被跳过,bug 复现)");
|
||||||
|
chk(ncm_row_h_for_line_h(lh) > APP_ROW_H, lh, "默认字体下行高必须大于旧值 56");
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%s: %d checks, %d fail (font line-height matrix: %d)\n",
|
||||||
|
g_fail ? "FAIL" : "PASS", g_checks, g_fail, n);
|
||||||
|
return g_fail ? 1 : 0;
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ static ncm_rect_t r_cont, r_list;
|
|||||||
static int g_bg_id, g_draw_id, g_empty_id, g_footer_id;
|
static int g_bg_id, g_draw_id, g_empty_id, g_footer_id;
|
||||||
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
|
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
|
||||||
static int g_last_count;
|
static int g_last_count;
|
||||||
|
static int g_item_h = APP_ROW_H; /* 两行列表行高(M12:按字体行高派生,见 ncm_row_h) */
|
||||||
|
|
||||||
/* 扫描 worker(磁盘 IO 不进 UI 线程;gen 计数做完成信号) */
|
/* 扫描 worker(磁盘 IO 不进 UI 线程;gen 计数做完成信号) */
|
||||||
static pthread_mutex_t g_cmu = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_mutex_t g_cmu = PTHREAD_MUTEX_INITIALIZER;
|
||||||
@@ -89,7 +90,7 @@ static void c_row_bg(void *ctx)
|
|||||||
if (cnt <= 0) return;
|
if (cnt <= 0) return;
|
||||||
const meow_theme *th = meow_theme_get();
|
const meow_theme *th = meow_theme_get();
|
||||||
const ncm_pal_t *pa = ncm_palette(g_dark);
|
const ncm_pal_t *pa = ncm_palette(g_dark);
|
||||||
int item = APP_ROW_H;
|
int item = g_item_h;
|
||||||
int off = meow_listview_scroll(g_list);
|
int off = meow_listview_scroll(g_list);
|
||||||
int first = off / item, last = (off + r_list.h) / item + 1;
|
int first = off / item, last = (off + r_list.h) / item + 1;
|
||||||
if (last > cnt) last = cnt;
|
if (last > cnt) last = cnt;
|
||||||
@@ -116,7 +117,7 @@ static void c_draw(void *ctx)
|
|||||||
const ncm_pal_t *pa = ncm_palette(g_dark);
|
const ncm_pal_t *pa = ncm_palette(g_dark);
|
||||||
int cnt = appstate_cache_count();
|
int cnt = appstate_cache_count();
|
||||||
if (cnt <= 0) return;
|
if (cnt <= 0) return;
|
||||||
int item = APP_ROW_H, isz = 24;
|
int item = g_item_h, isz = 24;
|
||||||
int off = meow_listview_scroll(g_list);
|
int off = meow_listview_scroll(g_list);
|
||||||
int first = off / item, last = (off + r_list.h) / item + 1;
|
int first = off / item, last = (off + r_list.h) / item + 1;
|
||||||
if (last > cnt) last = cnt;
|
if (last > cnt) last = cnt;
|
||||||
@@ -351,6 +352,7 @@ static void c_page_create(meow_view *v, void *ctx)
|
|||||||
(void)v;
|
(void)v;
|
||||||
((cache_ctrl *)ctx)->view = v;
|
((cache_ctrl *)ctx)->view = v;
|
||||||
r_cont = ncm_content_rect();
|
r_cont = ncm_content_rect();
|
||||||
|
g_item_h = ncm_row_h(); /* M12:行高按当前字体两行文本块派生(首行标题不被视口顶钳掉) */
|
||||||
r_list = NR(16, r_cont.y + 8, r_cont.w - 32,
|
r_list = NR(16, r_cont.y + 8, r_cont.w - 32,
|
||||||
r_cont.y + r_cont.h - APP_FOOTER_H - (r_cont.y + 8));
|
r_cont.y + r_cont.h - APP_FOOTER_H - (r_cont.y + 8));
|
||||||
if (r_list.h < 0) r_list.h = 0;
|
if (r_list.h < 0) r_list.h = 0;
|
||||||
@@ -358,7 +360,7 @@ static void c_page_create(meow_view *v, void *ctx)
|
|||||||
g_tb = ncm_topbar_create("已缓存", 1, "清空"); /* 文本动作(动作0)=清空缓存 */
|
g_tb = ncm_topbar_create("已缓存", 1, "清空"); /* 文本动作(动作0)=清空缓存 */
|
||||||
g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0,
|
g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0,
|
||||||
c_src2, NULL, MEOW_LIST_SEL_NONE);
|
c_src2, NULL, MEOW_LIST_SEL_NONE);
|
||||||
meow_listview_set_item_h(g_list, APP_ROW_H);
|
meow_listview_set_item_h(g_list, g_item_h);
|
||||||
meow_ui_activity_pop();
|
meow_ui_activity_pop();
|
||||||
meow_widget_set_z(g_list, 10);
|
meow_widget_set_z(g_list, 10);
|
||||||
g_bg_id = meow_draw_custom_register(5, r_list.x, r_list.y, r_list.w, r_list.h,
|
g_bg_id = meow_draw_custom_register(5, r_list.x, r_list.y, r_list.w, r_list.h,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ static ncm_rect_t r_cont, r_list, r_tabs, r_group;
|
|||||||
static int g_bg_id, g_draw_id, g_empty_id, g_footer_id;
|
static int g_bg_id, g_draw_id, g_empty_id, g_footer_id;
|
||||||
static volatile int g_load_err;
|
static volatile int g_load_err;
|
||||||
static int g_tab; /* 0=我创建的 1=我收藏的 */
|
static int g_tab; /* 0=我创建的 1=我收藏的 */
|
||||||
|
static int g_item_h = APP_ROW_H; /* 两行列表行高(M12:按字体行高派生,见 ncm_row_h) */
|
||||||
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
|
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
|
||||||
|
|
||||||
/* 歌单加载后台 worker(P1:从 UI 线程挪出,gen 代数做完成信号)。 */
|
/* 歌单加载后台 worker(P1:从 UI 线程挪出,gen 代数做完成信号)。 */
|
||||||
@@ -100,7 +101,7 @@ static void home_row_bg(void *ctx)
|
|||||||
if (meow_ui_activity_active() != ACT_HOME) return;
|
if (meow_ui_activity_active() != ACT_HOME) return;
|
||||||
if (g_load_err != 0 || g_st.playlist_count == 0) return;
|
if (g_load_err != 0 || g_st.playlist_count == 0) return;
|
||||||
const meow_theme *th = meow_theme_get();
|
const meow_theme *th = meow_theme_get();
|
||||||
int item = APP_ROW_H;
|
int item = g_item_h;
|
||||||
int off = meow_listview_scroll(g_list);
|
int off = meow_listview_scroll(g_list);
|
||||||
int first = off / item, last = (off + r_list.h) / item + 1;
|
int first = off / item, last = (off + r_list.h) / item + 1;
|
||||||
int n = tab_count(g_tab);
|
int n = tab_count(g_tab);
|
||||||
@@ -194,7 +195,7 @@ static void home_draw(void *ctx)
|
|||||||
|
|
||||||
/* 歌单行:红 mini-vinyl 缩略 + 按压红条(文本由 listview 画在 z=10)。 */
|
/* 歌单行:红 mini-vinyl 缩略 + 按压红条(文本由 listview 画在 z=10)。 */
|
||||||
if (g_load_err == 0 && g_st.playlist_count > 0) {
|
if (g_load_err == 0 && g_st.playlist_count > 0) {
|
||||||
int item = APP_ROW_H;
|
int item = g_item_h;
|
||||||
int off = meow_listview_scroll(g_list);
|
int off = meow_listview_scroll(g_list);
|
||||||
int isz = 24;
|
int isz = 24;
|
||||||
int first = off / item, last = (off + r_list.h) / item + 1;
|
int first = off / item, last = (off + r_list.h) / item + 1;
|
||||||
@@ -453,6 +454,7 @@ static void home_page_create(meow_view *v, void *ctx)
|
|||||||
((home_ctrl *)ctx)->view = v;
|
((home_ctrl *)ctx)->view = v;
|
||||||
home_retry_reset(); /* M10:离线重试节奏闸(首次进离线立即探测一次) */
|
home_retry_reset(); /* M10:离线重试节奏闸(首次进离线立即探测一次) */
|
||||||
r_cont = ncm_content_rect();
|
r_cont = ncm_content_rect();
|
||||||
|
g_item_h = ncm_row_h(); /* M12:行高按当前字体两行文本块派生(首行标题不被视口顶钳掉) */
|
||||||
ncm_rect_t pc = NR(16, r_cont.y + 8, r_cont.w - 32, 72);
|
ncm_rect_t pc = NR(16, r_cont.y + 8, r_cont.w - 32, 72);
|
||||||
r_tabs = NR(16, pc.y + pc.h + 8, r_cont.w - 32, 48);
|
r_tabs = NR(16, pc.y + pc.h + 8, r_cont.w - 32, 48);
|
||||||
r_group = NR(16, r_tabs.y + r_tabs.h + 4, r_cont.w - 32, 36);
|
r_group = NR(16, r_tabs.y + r_tabs.h + 4, r_cont.w - 32, 36);
|
||||||
@@ -481,7 +483,7 @@ static void home_page_create(meow_view *v, void *ctx)
|
|||||||
meow_tabbar_set_selected(g_tabs, 0);
|
meow_tabbar_set_selected(g_tabs, 0);
|
||||||
g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0,
|
g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0,
|
||||||
home_src2, NULL, MEOW_LIST_SEL_NONE);
|
home_src2, NULL, MEOW_LIST_SEL_NONE);
|
||||||
meow_listview_set_item_h(g_list, APP_ROW_H);
|
meow_listview_set_item_h(g_list, g_item_h);
|
||||||
meow_ui_activity_pop();
|
meow_ui_activity_pop();
|
||||||
/* 显式 z:行卡片(5) < listview(10) < tabbar(20) < 红态叠层(60) < topbar(999) */
|
/* 显式 z:行卡片(5) < listview(10) < tabbar(20) < 红态叠层(60) < topbar(999) */
|
||||||
meow_widget_set_z(g_list, 10);
|
meow_widget_set_z(g_list, 10);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ static int g_bg_id, g_draw_id, g_empty_id, g_footer_id;
|
|||||||
static char g_title[256];
|
static char g_title[256];
|
||||||
static volatile int g_load_err;
|
static volatile int g_load_err;
|
||||||
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
|
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
|
||||||
|
static int g_item_h = APP_ROW_H; /* 两行列表行高(M12:按字体行高派生,见 ncm_row_h) */
|
||||||
|
|
||||||
/* 歌单详情加载后台 worker(大歌单可达数十秒,不能占 UI 线程)。
|
/* 歌单详情加载后台 worker(大歌单可达数十秒,不能占 UI 线程)。
|
||||||
* §17 滑动窗口:worker 三种任务——①首屏(g_ens_first=1):重建 trackIds 索引 + 物化第 0 页
|
* §17 滑动窗口:worker 三种任务——①首屏(g_ens_first=1):重建 trackIds 索引 + 物化第 0 页
|
||||||
@@ -125,7 +126,7 @@ static void pl_row_bg(void *ctx)
|
|||||||
if (meow_ui_activity_active() != ACT_PLAYLIST) return;
|
if (meow_ui_activity_active() != ACT_PLAYLIST) return;
|
||||||
if (g_load_err != 0 || g_st.track_ids_count == 0) return;
|
if (g_load_err != 0 || g_st.track_ids_count == 0) return;
|
||||||
const meow_theme *th = meow_theme_get();
|
const meow_theme *th = meow_theme_get();
|
||||||
int item = APP_ROW_H;
|
int item = g_item_h;
|
||||||
int off = meow_listview_scroll(g_list);
|
int off = meow_listview_scroll(g_list);
|
||||||
int first = off / item, last = (off + r_list.h) / item + 1;
|
int first = off / item, last = (off + r_list.h) / item + 1;
|
||||||
if (last > g_st.track_ids_count) last = g_st.track_ids_count;
|
if (last > g_st.track_ids_count) last = g_st.track_ids_count;
|
||||||
@@ -195,7 +196,7 @@ static void pl_draw(void *ctx)
|
|||||||
|
|
||||||
/* 曲目行:红 mini-vinyl 缩略 + 按压红条(文本由 listview z=10 画) */
|
/* 曲目行:红 mini-vinyl 缩略 + 按压红条(文本由 listview z=10 画) */
|
||||||
if (g_load_err == 0 && g_st.track_ids_count > 0) {
|
if (g_load_err == 0 && g_st.track_ids_count > 0) {
|
||||||
int item = APP_ROW_H, isz = 24;
|
int item = g_item_h, isz = 24;
|
||||||
int off = meow_listview_scroll(g_list);
|
int off = meow_listview_scroll(g_list);
|
||||||
int first = off / item, last = (off + r_list.h) / item + 1;
|
int first = off / item, last = (off + r_list.h) / item + 1;
|
||||||
if (last > g_st.track_ids_count) last = g_st.track_ids_count;
|
if (last > g_st.track_ids_count) last = g_st.track_ids_count;
|
||||||
@@ -238,8 +239,8 @@ static void pl_draw_footer(void *ctx)
|
|||||||
int total = g_st.track_ids_count;
|
int total = g_st.track_ids_count;
|
||||||
if (total <= 0) return;
|
if (total <= 0) return;
|
||||||
/* 视口位置 / 总数(滑窗模型下「已物化数」无用户意义,视口区间更直观) */
|
/* 视口位置 / 总数(滑窗模型下「已物化数」无用户意义,视口区间更直观) */
|
||||||
int first = meow_listview_scroll(g_list) / APP_ROW_H + 1;
|
int first = meow_listview_scroll(g_list) / g_item_h + 1;
|
||||||
int last = first + r_list.h / APP_ROW_H;
|
int last = first + r_list.h / g_item_h;
|
||||||
if (last > total) last = total;
|
if (last > total) last = total;
|
||||||
char foot[96];
|
char foot[96];
|
||||||
snprintf(foot, sizeof foot, "%d-%d / %d 首", first, last, total);
|
snprintf(foot, sizeof foot, "%d-%d / %d 首", first, last, total);
|
||||||
@@ -361,8 +362,8 @@ static void pl_maybe_ensure(void)
|
|||||||
int total = g_st.track_ids_count;
|
int total = g_st.track_ids_count;
|
||||||
if (total <= 0) return;
|
if (total <= 0) return;
|
||||||
int scroll = meow_listview_scroll(g_list);
|
int scroll = meow_listview_scroll(g_list);
|
||||||
int first = scroll / APP_ROW_H;
|
int first = scroll / g_item_h;
|
||||||
int last = (scroll + r_list.h) / APP_ROW_H + 1;
|
int last = (scroll + r_list.h) / g_item_h + 1;
|
||||||
if (last > total) last = total;
|
if (last > total) last = total;
|
||||||
int paged = g_paged_hi;
|
int paged = g_paged_hi;
|
||||||
if (paged > total) paged = total; /* 防御:索引重建后高水位越界 */
|
if (paged > total) paged = total; /* 防御:索引重建后高水位越界 */
|
||||||
@@ -581,6 +582,7 @@ static void pl_page_create(meow_view *v, void *ctx)
|
|||||||
(void)v;
|
(void)v;
|
||||||
((playlist_ctrl *)ctx)->view = v;
|
((playlist_ctrl *)ctx)->view = v;
|
||||||
r_cont = ncm_content_rect();
|
r_cont = ncm_content_rect();
|
||||||
|
g_item_h = ncm_row_h(); /* M12:行高按当前字体两行文本块派生(首行标题不被视口顶钳掉) */
|
||||||
r_card = NR(16, r_cont.y + 8, r_cont.w - 32, 148);
|
r_card = NR(16, r_cont.y + 8, r_cont.w - 32, 148);
|
||||||
r_cov = NR(r_card.x + 16, r_card.y + 20, 80, 80);
|
r_cov = NR(r_card.x + 16, r_card.y + 20, 80, 80);
|
||||||
int by = r_card.y + r_card.h + 12;
|
int by = r_card.y + r_card.h + 12;
|
||||||
@@ -594,7 +596,7 @@ static void pl_page_create(meow_view *v, void *ctx)
|
|||||||
g_tb = ncm_topbar_create("歌单", 1, NULL);
|
g_tb = ncm_topbar_create("歌单", 1, NULL);
|
||||||
g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0,
|
g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0,
|
||||||
pl_src2, NULL, MEOW_LIST_SEL_NONE);
|
pl_src2, NULL, MEOW_LIST_SEL_NONE);
|
||||||
meow_listview_set_item_h(g_list, APP_ROW_H);
|
meow_listview_set_item_h(g_list, g_item_h);
|
||||||
meow_ui_activity_pop();
|
meow_ui_activity_pop();
|
||||||
meow_widget_set_z(g_list, 10);
|
meow_widget_set_z(g_list, 10);
|
||||||
g_bg_id = meow_draw_custom_register(5, r_list.x, r_list.y, r_list.w, r_list.h,
|
g_bg_id = meow_draw_custom_register(5, r_list.x, r_list.y, r_list.w, r_list.h,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ static ncm_rect_t r_cont, r_list;
|
|||||||
static int g_bg_id, g_draw_id, g_empty_id, g_footer_id;
|
static int g_bg_id, g_draw_id, g_empty_id, g_footer_id;
|
||||||
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
|
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
|
||||||
static int g_last_count, g_last_cur;
|
static int g_last_count, g_last_cur;
|
||||||
|
static int g_item_h = APP_ROW_H; /* 两行列表行高(M12:按字体行高派生,见 ncm_row_h) */
|
||||||
|
|
||||||
/* 队列补载(PLAYLIST)后台 worker 参数:目标区间 [lo,hi)(全局下标)。SEARCH 无需补载。 */
|
/* 队列补载(PLAYLIST)后台 worker 参数:目标区间 [lo,hi)(全局下标)。SEARCH 无需补载。 */
|
||||||
static pthread_mutex_t g_qmu = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_mutex_t g_qmu = PTHREAD_MUTEX_INITIALIZER;
|
||||||
@@ -92,7 +93,7 @@ static void q_row_bg(void *ctx)
|
|||||||
if (cnt <= 0) return;
|
if (cnt <= 0) return;
|
||||||
const meow_theme *th = meow_theme_get();
|
const meow_theme *th = meow_theme_get();
|
||||||
const ncm_pal_t *pa = ncm_palette(g_dark);
|
const ncm_pal_t *pa = ncm_palette(g_dark);
|
||||||
int item = APP_ROW_H;
|
int item = g_item_h;
|
||||||
int off = meow_listview_scroll(g_list);
|
int off = meow_listview_scroll(g_list);
|
||||||
int first = off / item, last = (off + r_list.h) / item + 1;
|
int first = off / item, last = (off + r_list.h) / item + 1;
|
||||||
if (last > cnt) last = cnt;
|
if (last > cnt) last = cnt;
|
||||||
@@ -120,7 +121,7 @@ static void q_draw(void *ctx)
|
|||||||
const ncm_pal_t *pa = ncm_palette(g_dark);
|
const ncm_pal_t *pa = ncm_palette(g_dark);
|
||||||
int cnt = appstate_queue_count();
|
int cnt = appstate_queue_count();
|
||||||
if (cnt <= 0) return;
|
if (cnt <= 0) return;
|
||||||
int item = APP_ROW_H, isz = 24;
|
int item = g_item_h, isz = 24;
|
||||||
int off = meow_listview_scroll(g_list);
|
int off = meow_listview_scroll(g_list);
|
||||||
int first = off / item, last = (off + r_list.h) / item + 1;
|
int first = off / item, last = (off + r_list.h) / item + 1;
|
||||||
if (last > cnt) last = cnt;
|
if (last > cnt) last = cnt;
|
||||||
@@ -156,8 +157,8 @@ static void q_draw_footer(void *ctx)
|
|||||||
if (meow_ui_activity_active() != ACT_QUEUE) return;
|
if (meow_ui_activity_active() != ACT_QUEUE) return;
|
||||||
int total = appstate_queue_count();
|
int total = appstate_queue_count();
|
||||||
if (total <= 0) return;
|
if (total <= 0) return;
|
||||||
int first = meow_listview_scroll(g_list) / APP_ROW_H + 1;
|
int first = meow_listview_scroll(g_list) / g_item_h + 1;
|
||||||
int last = first + r_list.h / APP_ROW_H;
|
int last = first + r_list.h / g_item_h;
|
||||||
if (last > total) last = total;
|
if (last > total) last = total;
|
||||||
char foot[96];
|
char foot[96];
|
||||||
snprintf(foot, sizeof foot, "%d-%d / %d 首", first, last, total);
|
snprintf(foot, sizeof foot, "%d-%d / %d 首", first, last, total);
|
||||||
@@ -203,8 +204,8 @@ static void q_maybe_ensure(void)
|
|||||||
int total = appstate_queue_count();
|
int total = appstate_queue_count();
|
||||||
if (total <= 0) return;
|
if (total <= 0) return;
|
||||||
int scroll = meow_listview_scroll(g_list);
|
int scroll = meow_listview_scroll(g_list);
|
||||||
int first = scroll / APP_ROW_H;
|
int first = scroll / g_item_h;
|
||||||
int last = (scroll + r_list.h) / APP_ROW_H + 1;
|
int last = (scroll + r_list.h) / g_item_h + 1;
|
||||||
int lo = first - 24, hi = last + 24;
|
int lo = first - 24, hi = last + 24;
|
||||||
if (lo < 0) lo = 0;
|
if (lo < 0) lo = 0;
|
||||||
if (hi > total) hi = total;
|
if (hi > total) hi = total;
|
||||||
@@ -325,6 +326,7 @@ static void q_page_create(meow_view *v, void *ctx)
|
|||||||
(void)v;
|
(void)v;
|
||||||
((queue_ctrl *)ctx)->view = v;
|
((queue_ctrl *)ctx)->view = v;
|
||||||
r_cont = ncm_content_rect();
|
r_cont = ncm_content_rect();
|
||||||
|
g_item_h = ncm_row_h(); /* M12:行高按当前字体两行文本块派生(首行标题不被视口顶钳掉) */
|
||||||
r_list = NR(16, r_cont.y + 8, r_cont.w - 32,
|
r_list = NR(16, r_cont.y + 8, r_cont.w - 32,
|
||||||
r_cont.y + r_cont.h - APP_FOOTER_H - (r_cont.y + 8));
|
r_cont.y + r_cont.h - APP_FOOTER_H - (r_cont.y + 8));
|
||||||
if (r_list.h < 0) r_list.h = 0;
|
if (r_list.h < 0) r_list.h = 0;
|
||||||
@@ -332,7 +334,7 @@ static void q_page_create(meow_view *v, void *ctx)
|
|||||||
g_tb = ncm_topbar_create("播放列表", 1, NULL);
|
g_tb = ncm_topbar_create("播放列表", 1, NULL);
|
||||||
g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0,
|
g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0,
|
||||||
q_src2, NULL, MEOW_LIST_SEL_NONE);
|
q_src2, NULL, MEOW_LIST_SEL_NONE);
|
||||||
meow_listview_set_item_h(g_list, APP_ROW_H);
|
meow_listview_set_item_h(g_list, g_item_h);
|
||||||
meow_ui_activity_pop();
|
meow_ui_activity_pop();
|
||||||
meow_widget_set_z(g_list, 10);
|
meow_widget_set_z(g_list, 10);
|
||||||
g_bg_id = meow_draw_custom_register(5, r_list.x, r_list.y, r_list.w, r_list.h,
|
g_bg_id = meow_draw_custom_register(5, r_list.x, r_list.y, r_list.w, r_list.h,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ static ncm_rect_t r_cont, r_list, r_btn, r_tabs[2];
|
|||||||
static int g_empty_id, g_footer_id, g_btn_id, g_tabbar_id;
|
static int g_empty_id, g_footer_id, g_btn_id, g_tabbar_id;
|
||||||
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
|
static int g_press_row, g_press_delta, g_dragging, g_drag_last_y;
|
||||||
static int g_scroll_song, g_scroll_pl; /* 两页签各自滚动位置(§23)*/
|
static int g_scroll_song, g_scroll_pl; /* 两页签各自滚动位置(§23)*/
|
||||||
|
static int g_item_h = APP_ROW_H; /* 两行列表行高(M12:按字体行高派生,见 ncm_row_h) */
|
||||||
|
|
||||||
static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||||
static pthread_t g_thr;
|
static pthread_t g_thr;
|
||||||
@@ -206,7 +207,7 @@ static void sr_maybe_load_more(void)
|
|||||||
int n = appstate_search_count(g_tab);
|
int n = appstate_search_count(g_tab);
|
||||||
int cap = (g_tab == 0) ? NCM_SEARCH_SONG_MAX : NCM_SEARCH_PL_MAX;
|
int cap = (g_tab == 0) ? NCM_SEARCH_SONG_MAX : NCM_SEARCH_PL_MAX;
|
||||||
if (n <= 0 || n >= cap || !appstate_search_has_more(g_tab)) return;
|
if (n <= 0 || n >= cap || !appstate_search_has_more(g_tab)) return;
|
||||||
int item = APP_ROW_H;
|
int item = g_item_h;
|
||||||
int scroll = meow_listview_scroll(g_list);
|
int scroll = meow_listview_scroll(g_list);
|
||||||
if (scroll + r_list.h < n * item - item * 2) return; /* 未近底 */
|
if (scroll + r_list.h < n * item - item * 2) return; /* 未近底 */
|
||||||
g_req_tab = g_tab; g_req_offset = n; g_req_replace = 0;
|
g_req_tab = g_tab; g_req_offset = n; g_req_replace = 0;
|
||||||
@@ -445,6 +446,7 @@ static void sr_page_create(meow_view *v, void *ctx)
|
|||||||
(void)v;
|
(void)v;
|
||||||
((search_ctrl *)ctx)->view = v;
|
((search_ctrl *)ctx)->view = v;
|
||||||
r_cont = ncm_content_rect();
|
r_cont = ncm_content_rect();
|
||||||
|
g_item_h = ncm_row_h(); /* M12:行高按当前字体两行文本块派生(首行标题不被视口顶钳掉) */
|
||||||
ncm_rect_t field = NR(16, r_cont.y + 12, r_cont.w - 32, 48);
|
ncm_rect_t field = NR(16, r_cont.y + 12, r_cont.w - 32, 48);
|
||||||
r_btn = NR(field.x, field.y + field.h + 12, field.w, 44);
|
r_btn = NR(field.x, field.y + field.h + 12, field.w, 44);
|
||||||
int by = r_btn.y + r_btn.h + 8;
|
int by = r_btn.y + r_btn.h + 8;
|
||||||
@@ -462,7 +464,7 @@ static void sr_page_create(meow_view *v, void *ctx)
|
|||||||
sizeof g_st.search_query, MEOW_IME_PINYIN);
|
sizeof g_st.search_query, MEOW_IME_PINYIN);
|
||||||
g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0,
|
g_list = meow_listview_create(r_list.x, r_list.y, r_list.w, r_list.h, 0,
|
||||||
sr_src2, NULL, MEOW_LIST_SEL_NONE);
|
sr_src2, NULL, MEOW_LIST_SEL_NONE);
|
||||||
meow_listview_set_item_h(g_list, APP_ROW_H);
|
meow_listview_set_item_h(g_list, g_item_h);
|
||||||
meow_ui_activity_pop();
|
meow_ui_activity_pop();
|
||||||
meow_widget_set_z(g_list, 10);
|
meow_widget_set_z(g_list, 10);
|
||||||
g_btn_id = meow_draw_custom_register(60, r_btn.x, r_btn.y, r_btn.w, r_btn.h,
|
g_btn_id = meow_draw_custom_register(60, r_btn.x, r_btn.y, r_btn.w, r_btn.h,
|
||||||
|
|||||||
@@ -161,14 +161,14 @@ static int cond_first_screen(void)
|
|||||||
return g_st.track_ids_count == SIM_TOTAL && g_song_calls >= 1;
|
return g_st.track_ids_count == SIM_TOTAL && g_song_calls >= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动到指定首行(行高 APP_ROW_H px;swipe 距离按当前行差换算) */
|
/* 滚动到指定首行(行高 ncm_row_h() px;swipe 距离按当前行差换算) */
|
||||||
static void scroll_to_row(int row)
|
static void scroll_to_row(int row)
|
||||||
{
|
{
|
||||||
static int cur;
|
static int cur;
|
||||||
int d = row - cur;
|
int d = row - cur;
|
||||||
cur = row;
|
cur = row;
|
||||||
playlist_swipe(d >= 0 ? MEOW_SWIPE_UP : MEOW_SWIPE_DOWN,
|
playlist_swipe(d >= 0 ? MEOW_SWIPE_UP : MEOW_SWIPE_DOWN,
|
||||||
(d >= 0 ? d : -d) * APP_ROW_H);
|
(d >= 0 ? d : -d) * ncm_row_h());
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
@@ -176,15 +176,19 @@ int main(void)
|
|||||||
printf("== plpage_sim: 懒加载分页 + 加载后列表刷新(%d 首,窗口 %d,批 %d,触底余量 20)==\n",
|
printf("== plpage_sim: 懒加载分页 + 加载后列表刷新(%d 首,窗口 %d,批 %d,触底余量 20)==\n",
|
||||||
SIM_TOTAL, NCM_SONG_WINDOW, NCM_SONG_BATCH);
|
SIM_TOTAL, NCM_SONG_WINDOW, NCM_SONG_BATCH);
|
||||||
|
|
||||||
/* 宿主 SDK 离屏初始化(与 ncm_host 同口径) */
|
/* 宿主 SDK 离屏初始化(与 ncm_host 同口径)。临时目录优先 $TMPDIR(Termux/容器里 /tmp 常不可写) */
|
||||||
setenv("MEOW_UI_DUMP", "/tmp/plpage_sim.ppm", 0);
|
const char *td = getenv("TMPDIR");
|
||||||
|
if (!td || !td[0]) td = "/tmp";
|
||||||
|
char dmp[256], cdt[256];
|
||||||
|
snprintf(dmp, sizeof dmp, "%s/plpage_sim.ppm", td);
|
||||||
|
setenv("MEOW_UI_DUMP", dmp, 0);
|
||||||
meow_ui_opts o = { APP_W, APP_H };
|
meow_ui_opts o = { APP_W, APP_H };
|
||||||
if (meow_ui_init(&o) != 0) { fprintf(stderr, "init failed\n"); return 1; }
|
if (meow_ui_init(&o) != 0) { fprintf(stderr, "init failed\n"); return 1; }
|
||||||
meow_theme_set_mode(MEOW_THEME_DARK);
|
meow_theme_set_mode(MEOW_THEME_DARK);
|
||||||
ncm_on_theme(MEOW_THEME_DARK);
|
ncm_on_theme(MEOW_THEME_DARK);
|
||||||
|
|
||||||
/* 歌单 mock:1 张 SIM_TOTAL 首歌单;songmeta 盘缓存用临时目录 */
|
/* 歌单 mock:1 张 SIM_TOTAL 首歌单;songmeta 盘缓存用临时目录 */
|
||||||
char cdt[] = "/tmp/plpage_cache_XXXXXX";
|
snprintf(cdt, sizeof cdt, "%s/plpage_cache_XXXXXX", td);
|
||||||
if (!mkdtemp(cdt)) { fprintf(stderr, "mkdtemp failed\n"); return 1; }
|
if (!mkdtemp(cdt)) { fprintf(stderr, "mkdtemp failed\n"); return 1; }
|
||||||
snprintf(g_st.cache_dir, sizeof g_st.cache_dir, "%s", cdt);
|
snprintf(g_st.cache_dir, sizeof g_st.cache_dir, "%s", cdt);
|
||||||
g_st.playlist_count = 1;
|
g_st.playlist_count = 1;
|
||||||
|
|||||||
Reference in new issue
Block a user