Files
MeowOS-For-R3P2/patch_v2/reader_app/test/shelf_scroll_smoke.c
T
AskaEth 2de47154bf fix(reader): M41 书架补滚动输入通路 — 列表/宫格、format/folder 全都能滑
书架(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 完成后部署(设备验证需用户手滑,面板无任意滑动注入端点)。
2026-11-11 13:01:12 +08:00

208 lines
9.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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;
}