书架(shelf.c)从 M6.40 首版起就**没有**滚动输入通路,不是 M32/M33 的回归:
1) app_on_touch_down/move 只路由给阅读页 → 书架没有跟手拖动;
2) shelf_on_swipe 用 dy 去 scroll_by,而 SDK 固定按 on_swipe(dir, dist, 0)
分发(总位移在第 2 参 dx,dy 恒 0)→ 恒 scroll_by(0),死代码。
两条都在 shelf.c 自己身上,list/grid × format/folder 共用同一段,所以四种组合
全都「滑不动」(files/ncm 能滑是因为它们各自实现了 touch_down/move → scroll_by)。
修复:
- app.c:touch down/move 也路由给书架(阅读页优先级不变)。
- shelf.c:
* 新增 shelf_on_touch_down/move 跟手拖动:按下点在内容区才起手;纵向主导(≥4px)
才锁滚动,横向主导直接取消(不抢左缘返回);逐帧 scroll_by(-dy) 只重画内容区。
* shelf_on_swipe 改按 dist(=dx,兼容 dy 分支)+ 方向定符号;本次手势已跟手滚过
就不再叠加(防一划滚两倍)。
* 拖动后的抬手吞掉 TAP(<65px 的拖动 SDK 报 TAP)→ 滑动时不会误开书/误进目录。
* 控件重建(进/退目录、布局/分组切换、刷新)清滚动手势态,避免旧标记吃掉下一次滑动。
* 提示条抽成 shelf_draw_notice(),滚动局部重画时照常重贴。
- 新增宿主回归 test/shelf_scroll_smoke.c(真实 shelf.c + 真实 SDK 绘制 + back_buf
像素断言,27 条):format/folder × list/grid 都能滑;3 本书的目录滑不动;
拖动后不误进目录、干净点击仍能进目录;folder 进出目录仍正常。把 dist=dx 改回
dist=dy 后同一测试 6 条 FAIL(红绿对照证明它抓的就是这个 bug)。
- build_reader.sh 接入该测试;READER_SD_ROOT 加 #ifndef 以便宿主把扫描根指到临时目录。
- app.json 版本 0.1.7/code8 → 0.1.8/code9(同一版本号不发两个构建)。
宿主验证:shelf_scroll_smoke 27/27、shelf_group_smoke 99/99、shelf_index_smoke
170/170、其余 core/seg/chapter/loaders 全绿;reader 1718468B md5 259cb6ed5a60…
(meow_ui.a 用本仓 SDK 源码重编:79 成员全 MIPS32r2 o32)。真机验证待刷机
0.8.49 完成后部署(设备验证需用户手滑,面板无任意滑动注入端点)。
208 lines
9.5 KiB
C
208 lines
9.5 KiB
C
/* test/shelf_scroll_smoke.c — M41 书架滚动宿主回归(真实 shelf.c + 真实 SDK 绘制,离屏 480x720)。
|
||
*
|
||
* 背景(真机 bug):0.1.7 书架「列表和宫格都滑不动」。根因是书架**没有任何滚动输入通路**:
|
||
* ① app_on_touch_down/move 只路由给阅读页 → 没有跟手拖动;
|
||
* ② shelf_on_swipe 用 dy 去 scroll_by,而 SDK 固定 on_swipe(dir, dist, 0)(位移在第 2 参)→ 恒滚 0。
|
||
* 本测试直接打 App 层入口(与 app.c 的调用顺序一致),用 back_buf 像素证明「视口确实滚了」:
|
||
* - format/folder × list/grid 四种组合都能滚(swipe 的 dist 在第 2 参);
|
||
* - 跟手拖动(on_touch_down/move)能滚,往返回到原位;
|
||
* - 内容不足一屏(3 本书的目录)时滑动**不动**(根层 3 行不该滚);
|
||
* - 拖动后的抬手不当作点击(不会误进目录),干净的点击仍能进目录;
|
||
* - folder 导航(点目录进入 / back 回上一级)在滚动改动后仍工作。
|
||
*
|
||
* 编译/运行见 build_reader.sh(宿主 gcc + -DMEOW_UI_HOST_TEST + -DREADER_SD_ROOT="<临时目录>";
|
||
* reader_open 在本文件打桩,不链阅读页)。
|
||
*/
|
||
#include "reader_internal.h"
|
||
#include "meow_ui_internal.h" /* meow_ui_back_buf / pitch / bpp */
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/types.h>
|
||
#include <unistd.h>
|
||
|
||
static int g_checks, g_fails;
|
||
|
||
#define CHECK(cond, fmt, ...) do { \
|
||
g_checks++; \
|
||
if (cond) printf(" ok " fmt "\n", ##__VA_ARGS__); \
|
||
else { g_fails++; printf(" FAIL " fmt "\n", ##__VA_ARGS__); } \
|
||
} while (0)
|
||
|
||
/* 阅读页打桩:书架点书会调它;本测试只验滚动/导航,不打开书。 */
|
||
int reader_open(const char *path) { (void)path; return -1; }
|
||
|
||
/* ---- 造一棵假 Books/ 树 ---------------------------------------------------- */
|
||
static void mkdir_p(const char *p)
|
||
{
|
||
char tmp[600];
|
||
snprintf(tmp, sizeof tmp, "%s", p);
|
||
for (char *c = tmp + 1; *c; c++) {
|
||
if (*c != '/') continue;
|
||
*c = 0; mkdir(tmp, 0755); *c = '/';
|
||
}
|
||
mkdir(tmp, 0755);
|
||
}
|
||
|
||
static void mkbook(const char *root, const char *sub, int n, int idx)
|
||
{
|
||
char dir[600], path[640];
|
||
snprintf(dir, sizeof dir, "%s/Books/%s", root, sub);
|
||
mkdir_p(dir);
|
||
snprintf(path, sizeof path, "%s/book%03d.txt", dir, idx);
|
||
FILE *f = fopen(path, "wb");
|
||
if (!f) { printf(" FAIL 无法创建 %s\n", path); g_fails++; return; }
|
||
fprintf(f, "test book %d\n", idx);
|
||
fclose(f);
|
||
(void)n;
|
||
}
|
||
|
||
/* ---- 视口像素签名(内容区,步进采样;只比像素,不依赖内部控件状态) ---- */
|
||
static unsigned long content_hash(void)
|
||
{
|
||
unsigned char *bb = meow_ui_back_buf();
|
||
int pitch = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp();
|
||
int y0 = meow_ui_statusbar_height() + 56 + 22; /* 与 shelf_content_top 同口径(M41 宏) */
|
||
if (!bb || pitch <= 0) return 0;
|
||
unsigned long h = 5381;
|
||
for (int y = y0; y < 720; y += 2) {
|
||
for (int x = 0; x < 480; x += 2) {
|
||
uint32_t v;
|
||
if (bpp == 2) v = *(uint16_t *)(bb + (size_t)y * pitch + (size_t)x * 2);
|
||
else v = *(uint32_t *)(bb + (size_t)y * pitch + (size_t)x * 4);
|
||
h = h * 33u ^ v;
|
||
}
|
||
}
|
||
return h;
|
||
}
|
||
|
||
static int content_top(void) { return meow_ui_statusbar_height() + 56 + 22; }
|
||
|
||
int main(int argc, char **argv)
|
||
{
|
||
(void)argc; (void)argv;
|
||
/* 扫描根 = 编译期 READER_SD_ROOT(宿主编译时指向临时目录;见 build_reader.sh) */
|
||
const char *root = READER_SD_ROOT;
|
||
|
||
/* 树:Books 根 40 本 + Books/big 40 本(可滚);Books/small 3 本(不足一屏,不该滚)。 */
|
||
for (int i = 0; i < 40; i++) mkbook(root, "", 40, i);
|
||
for (int i = 0; i < 40; i++) mkbook(root, "big/", 40, i);
|
||
for (int i = 0; i < 3; i++) mkbook(root, "small/", 3, i);
|
||
if (g_fails) return 1;
|
||
|
||
meow_ui_opts o = { 480, 720 };
|
||
if (meow_ui_init(&o) != 0) { printf("meow_ui_init failed\n"); return 1; }
|
||
|
||
meow_view *shelf = shelf_view_create();
|
||
meow_view_page_push(ACT_SHELF); /* → shelf_on_enter:扫描 + 建控件 + 首绘 */
|
||
if (!shelf_is_active()) { printf("shelf 未激活\n"); return 1; }
|
||
|
||
int top = content_top();
|
||
|
||
/* ===== 1) format + list:swipe 的位移在第 2 参(这就是真机 bug) ===== */
|
||
printf("[1] format/list swipe\n");
|
||
unsigned long h0 = content_hash();
|
||
shelf_on_swipe(MEOW_SWIPE_DOWN, 120, 0); /* 已在顶:往下滑不该动 */
|
||
CHECK(content_hash() == h0, "顶部下滑不动");
|
||
shelf_on_swipe(MEOW_SWIPE_UP, 0, 0); /* 位移 0:不动(旧实现在这里恒滚 0) */
|
||
CHECK(content_hash() == h0, "dist=0 不滚");
|
||
shelf_on_swipe(MEOW_SWIPE_UP, 120, 0); /* SDK 形态:on_swipe(dir, dist, 0) */
|
||
unsigned long h1 = content_hash();
|
||
CHECK(h1 != h0, "swipe UP(dist 在 dx) 视口变化");
|
||
shelf_on_swipe(MEOW_SWIPE_UP, 120, 0);
|
||
unsigned long h2 = content_hash();
|
||
CHECK(h2 != h1, "连续 swipe UP 继续前进");
|
||
shelf_on_swipe(MEOW_SWIPE_DOWN, 100000, 0); /* 一次滑回顶(clamp) */
|
||
CHECK(content_hash() == h0, "swipe DOWN 越界 clamp 回顶部像素一致");
|
||
shelf_on_swipe(MEOW_SWIPE_UP, 60, 0); /* dy 兼容分支 */
|
||
unsigned long h3 = content_hash();
|
||
CHECK(h3 != h0, "swipe UP 亦认 dy 参数(兼容旧调用)");
|
||
shelf_on_swipe(MEOW_SWIPE_DOWN, 60, 0);
|
||
CHECK(content_hash() == h0, "dy 分支回来像素一致");
|
||
|
||
/* ===== 2) format + list:跟手拖动 ===== */
|
||
printf("[2] format/list 跟手拖动\n");
|
||
shelf_on_touch_down(240, top + 100);
|
||
shelf_on_touch_move(240, top + 40); /* 手指上移 60 → 内容上滚 */
|
||
unsigned long h4 = content_hash();
|
||
CHECK(h4 != h0, "拖动 60px 视口变化");
|
||
shelf_on_touch_move(240, top + 100); /* 拖回原位 */
|
||
CHECK(content_hash() == h0, "反向拖回像素复原");
|
||
shelf_on_touch_move(240, top + 300); /* 继续下滑(越过顶部 → clamp) */
|
||
CHECK(content_hash() == h0, "顶部继续下滑仍 clamp");
|
||
/* 拖动后的抬手不得当作点击(此时竖坐标恰好落在一本书上;打桩 reader_open 返回 -1,
|
||
* 这里只验状态机不误触发:拖动结束 → shelf_on_tap 返回 0 且不产生副作用) */
|
||
int rt = shelf_on_tap(240, top + 300);
|
||
CHECK(rt == 0, "拖动后的 TAP 被吞(返回 0)");
|
||
|
||
/* ===== 3) folder + list:导航 + 滚动 + 不足一屏不动 ===== */
|
||
printf("[3] folder/list 导航与滚动\n");
|
||
shelf_set_group(1);
|
||
CHECK(shelf_back() == 0, "folder 根层 shelf_back=0(未误进目录)");
|
||
/* 拖动 20px(< SHELF_DRAG_TAP_PX 的 SDK TAP 阈值内)后抬在目录行上:不得进目录 */
|
||
shelf_on_touch_down(240, top + 10);
|
||
shelf_on_touch_move(240, top + 30);
|
||
(void)shelf_on_tap(240, top + 10);
|
||
CHECK(shelf_back() == 0, "拖动后的抬手不误进目录(行 0=目录)");
|
||
/* 干净点击行 0(目录 big,40 本)→ 进入 */
|
||
shelf_on_touch_down(240, top + 10);
|
||
CHECK(shelf_on_tap(240, top + 10) == 0, "点击目录行返回 0");
|
||
/* 现在在 Books/big(40 本)→ 可滚 */
|
||
unsigned long f0 = content_hash();
|
||
shelf_on_swipe(MEOW_SWIPE_UP, 120, 0);
|
||
CHECK(content_hash() != f0, "folder 子目录内 swipe UP 生效");
|
||
shelf_on_touch_down(240, top + 200);
|
||
shelf_on_touch_move(240, top + 140);
|
||
CHECK(content_hash() != f0, "folder 子目录内拖动生效");
|
||
CHECK(shelf_back() == 1, "从 Books/big 回 Books 根");
|
||
CHECK(shelf_back() == 0, "Books 根再 back=0(调用方据此退出 App)");
|
||
/* 进入 small(3 本,不足一屏)→ 滑动不动 */
|
||
shelf_on_touch_down(240, top + 10 + 56); /* 行 1 = small(行 0=big) */
|
||
(void)shelf_on_tap(240, top + 10 + 56);
|
||
unsigned long s0 = content_hash();
|
||
shelf_on_swipe(MEOW_SWIPE_UP, 300, 0);
|
||
CHECK(content_hash() == s0, "3 本书的目录:滑动不动(不足一屏)");
|
||
shelf_on_touch_down(240, top + 100);
|
||
shelf_on_touch_move(240, top + 20);
|
||
CHECK(content_hash() == s0, "3 本书的目录:拖动不动");
|
||
CHECK(shelf_back() == 1, "从 small 返回 Books 根");
|
||
|
||
/* ===== 4) folder + grid ===== */
|
||
printf("[4] folder/grid\n");
|
||
shelf_set_view(1);
|
||
unsigned long g0 = content_hash();
|
||
shelf_on_swipe(MEOW_SWIPE_UP, 120, 0);
|
||
CHECK(content_hash() != g0, "grid swipe UP 生效");
|
||
shelf_on_swipe(MEOW_SWIPE_DOWN, 100000, 0);
|
||
CHECK(content_hash() == g0, "grid 回到顶部");
|
||
shelf_on_touch_down(240, top + 100);
|
||
shelf_on_touch_move(240, top + 20);
|
||
CHECK(content_hash() != g0, "grid 拖动生效");
|
||
|
||
/* ===== 5) format + grid ===== */
|
||
printf("[5] format/grid\n");
|
||
shelf_set_group(0);
|
||
shelf_set_view(0);
|
||
shelf_set_view(1);
|
||
unsigned long fg0 = content_hash();
|
||
shelf_on_swipe(MEOW_SWIPE_UP, 120, 0);
|
||
CHECK(content_hash() != fg0, "format/grid swipe UP 生效");
|
||
shelf_on_touch_down(240, top + 200);
|
||
shelf_on_touch_move(240, top + 140);
|
||
CHECK(content_hash() != fg0, "format/grid 拖动生效");
|
||
|
||
/* ===== 6) format + list 回归:format 行序列仍在(40 本 → 可滚) ===== */
|
||
printf("[6] format/list 收尾\n");
|
||
shelf_set_view(0);
|
||
unsigned long l0 = content_hash();
|
||
shelf_on_swipe(MEOW_SWIPE_UP, 120, 0);
|
||
CHECK(content_hash() != l0, "回到 format/list 仍可滑");
|
||
|
||
meow_view_free(shelf);
|
||
meow_ui_shutdown();
|
||
|
||
printf("\n%d checks, %d failed\n", g_checks, g_fails);
|
||
return g_fails ? 1 : 0;
|
||
}
|