feat(tuku): M7 条漫模式与续读——模式一键切换/每册记忆、视口±1 懒解码、滑块跳页、册间衔接
- 条漫:meow_scroll 跟手+fling,按滚动位置懒解码视口±1 页(驻留上限 4 页); 排版用 mr_book_meta 累加适宽高度(nav.c 纯逻辑,宿主可测) - 模式:阅读 chrome 图标一键切翻页/条漫;每册模式与 last page 经 meow_config 持久化 (reader.mode/page.<FNV1a64(册路径)>),重进该册恢复模式与页码 - 续读:离开/翻页/滚动停止节流落盘;书库扫描给含图册副标题追加「· 续读 Pn」 - 册间衔接:末页前进→同父目录下一含图册首页,首页后退→上一册末页;无相邻册轻提示不退出 - 跳页:chrome 内 meow_slider,目标页预解码完成后落位(2.5s 超时降级提示) - 解码:改走 M12 预算解码 meow_img_load_file_dim;超大 PNG/BMP 先 probe 预判, 超 40MiB 原始 RGBA 跳过并显示「图片无法显示」,12MiB 以上记 WARN(如实保留残余风险) - 测试:新增条漫排版/跳页状态/模式续读持久化/册间边界宿主测试,以及真实 SDK 源码树上的 阅读器集成测试(模式切换/跳页落位/续读/跨册),test/run_webtoon_tests.sh 一键运行
This commit is contained in:
1 parent
8cbb240931
commit
ff97f3cee7
14 files changed
+2539
-93
No files matched your search
@@ -7,8 +7,14 @@
|
||||
* 收进结果并自然排序——因此根视图不会列出后代条目,进入子目录时再重新扫描该层。
|
||||
*
|
||||
* 内存:条目数组倍增分配;路径/名称定长,随 mr_library_free() 释放。
|
||||
*
|
||||
* M7 追加(不改扫描语义):
|
||||
* - 续读角标:含图册的副标题后缀「· 续读 Pn」(读 logic/reader 的每册 last page);
|
||||
* - 册间衔接:mr_book_adjacent 给出同父目录下按书库自然序的相邻「含图册」,
|
||||
* 供阅读器末页前进/首页后退跨册;mr_library_dir_has_images 是共享的「是不是一本书」判据。
|
||||
*/
|
||||
#include "library.h"
|
||||
#include "logic/reader.h" /* mr_cont_badge:书库行续读角标(M7) */
|
||||
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
@@ -215,6 +221,17 @@ int mr_library_scan(mr_library *lib, const char *dir, int max_depth)
|
||||
else
|
||||
snprintf(e.subtitle, sizeof e.subtitle, "%d folder%s", e.subdirs,
|
||||
e.subdirs == 1 ? "" : "s");
|
||||
/* 续读角标(M7):有 last page 记录的册,在副标题后缀「· 续读 Pn」。
|
||||
* config 读失败/无记录时 mr_cont_badge 返回 0,副标题保持原样。 */
|
||||
if (e.has_images) {
|
||||
char badge[24];
|
||||
if (mr_cont_badge(e.path, badge, (int)sizeof badge)) {
|
||||
int used = (int)strlen(e.subtitle);
|
||||
if (used < (int)sizeof e.subtitle - 2)
|
||||
snprintf(e.subtitle + used, sizeof e.subtitle - (size_t)used,
|
||||
" · %s", badge);
|
||||
}
|
||||
}
|
||||
if (lib_add(lib, &e) != 0) {
|
||||
closedir(d);
|
||||
mr_library_free(lib);
|
||||
@@ -245,3 +262,101 @@ int mr_path_parent(const char *path, char *out, int outsz)
|
||||
out[n] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
/* 册间衔接(M7) */
|
||||
/* ============================================================ */
|
||||
int mr_library_dir_has_images(const char *dir)
|
||||
{
|
||||
if (!dir || !dir[0]) return 0;
|
||||
DIR *d = opendir(dir);
|
||||
if (!d) return 0;
|
||||
|
||||
int found = 0;
|
||||
struct dirent *de;
|
||||
while ((de = readdir(d)) != NULL) {
|
||||
const char *name = de->d_name;
|
||||
if (name[0] == '.' || !mr_path_is_image(name)) continue;
|
||||
char full[MR_PATH_MAX];
|
||||
if (join_path(full, (int)sizeof full, dir, name) != 0) continue;
|
||||
struct stat st;
|
||||
if (lstat(full, &st) != 0 || !S_ISREG(st.st_mode)) continue;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
closedir(d);
|
||||
return found;
|
||||
}
|
||||
|
||||
static int name_ptr_cmp(const void *pa, const void *pb)
|
||||
{
|
||||
const char *a = *(const char *const *)pa;
|
||||
const char *b = *(const char *const *)pb;
|
||||
return mr_natural_cmp(a, b);
|
||||
}
|
||||
|
||||
int mr_book_adjacent(const char *dir, int delta, char *out, int outsz)
|
||||
{
|
||||
if (!out || outsz <= 0) return -1;
|
||||
out[0] = '\0';
|
||||
if (!dir || !dir[0] || (delta != 1 && delta != -1)) return -1;
|
||||
|
||||
char norm[MR_PATH_MAX];
|
||||
size_t dl = strlen(dir);
|
||||
while (dl > 1 && dir[dl - 1] == '/') dl--;
|
||||
if (dl >= sizeof norm) return -1;
|
||||
memcpy(norm, dir, dl);
|
||||
norm[dl] = '\0';
|
||||
|
||||
const char *slash = strrchr(norm, '/');
|
||||
if (!slash || !slash[1]) return -1;
|
||||
const char *cur = slash + 1;
|
||||
|
||||
char parent[MR_PATH_MAX];
|
||||
if (mr_path_parent(norm, parent, (int)sizeof parent) != 0) return -1;
|
||||
|
||||
DIR *d = opendir(parent);
|
||||
if (!d) return 0;
|
||||
|
||||
char **names = NULL;
|
||||
int n = 0, cap = 0;
|
||||
struct dirent *de;
|
||||
while ((de = readdir(d)) != NULL) {
|
||||
const char *name = de->d_name;
|
||||
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
|
||||
if (ignored_dir_name(name)) continue;
|
||||
|
||||
char full[MR_PATH_MAX];
|
||||
if (join_path(full, (int)sizeof full, parent, name) != 0) continue;
|
||||
struct stat st;
|
||||
if (lstat(full, &st) != 0 || S_ISLNK(st.st_mode) || !S_ISDIR(st.st_mode)) continue;
|
||||
if (!mr_library_dir_has_images(full)) continue; /* 只算「一本书」,中间目录跳过 */
|
||||
|
||||
if (n >= cap) {
|
||||
int nc = cap ? cap * 2 : 16;
|
||||
char **nn = (char **)realloc(names, (size_t)nc * sizeof *nn);
|
||||
if (!nn) break;
|
||||
names = nn;
|
||||
cap = nc;
|
||||
}
|
||||
size_t len = strlen(name);
|
||||
names[n] = (char *)malloc(len + 1);
|
||||
if (!names[n]) break;
|
||||
memcpy(names[n], name, len + 1);
|
||||
n++;
|
||||
}
|
||||
closedir(d);
|
||||
|
||||
int rc = 0;
|
||||
if (n > 1) qsort(names, (size_t)n, sizeof *names, name_ptr_cmp);
|
||||
int idx = -1;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (strcmp(names[i], cur) == 0) { idx = i; break; }
|
||||
int target = idx + delta;
|
||||
if (idx >= 0 && target >= 0 && target < n) {
|
||||
if (join_path(out, outsz, parent, names[target]) == 0) rc = 1;
|
||||
}
|
||||
for (int i = 0; i < n; i++) free(names[i]);
|
||||
free(names);
|
||||
return rc;
|
||||
}
|
||||
Reference in new issue
Block a user