feat: M6.10–M6.19 批量收编 + M6.19 推卡验收

- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘)
- OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复
- ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640
- 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫)
- 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
This commit is contained in:
AskaEth committed 2026-09-03 12:16:01 +08:00
1 parent c2ee7626a4
commit 3d70a3dd2c
176 files changed
+15507 -597

No files matched your search

+52 -15
View File
@@ -24,7 +24,8 @@ struct rd_book {
char path[400];
char font[400];
char base[256];
const char *encname;
const char *encname; /* TXT 有效编码可读名(含覆盖后);非 TXT = "" */
rd_enc_t enc; /* TXT 有效编码;非 TXT = RD_ENC_UTF8 */
rdft *ft;
rd_src *src;
rd_fmt_t fmt;
@@ -44,21 +45,22 @@ struct rd_book {
int seg_cache_idx;
};
static const char *fmt_encname(rd_fmt_t f, const char *path)
/* 编码枚举 → 可读名(R3:Big5 正确解码后名实相符)。 */
static const char *enc_to_name(rd_enc_t enc)
{
if (f == RD_FMT_TXT) {
rdtxt_info info;
if (rdtxt_probe(path, &info) == 0) {
switch (info.enc) {
case RD_ENC_UTF8: return "UTF-8";
case RD_ENC_UTF16LE: return "UTF-16LE";
case RD_ENC_UTF16BE: return "UTF-16BE";
case RD_ENC_GB18030: return "GB18030";
case RD_ENC_BIG5: return "Big5(按GB18030)";
case RD_ENC_ASCII: return "ASCII";
}
}
switch (enc) {
case RD_ENC_UTF8: return "UTF-8";
case RD_ENC_UTF16LE: return "UTF-16LE";
case RD_ENC_UTF16BE: return "UTF-16BE";
case RD_ENC_GB18030: return "GB18030";
case RD_ENC_BIG5: return "Big5";
case RD_ENC_ASCII: return "ASCII";
default: return "";
}
}
static const char *fmt_encname(rd_fmt_t f, rd_enc_t enc)
{
if (f == RD_FMT_TXT) return enc_to_name(enc);
return "";
}
@@ -169,7 +171,10 @@ int rd_book_open(rd_book **b, const char *path, const char *font_path,
if (rd_src_open(&x->src, path) != 0) goto close;
x->fmt = rd_src_fmt(x->src);
x->encname = fmt_encname(x->fmt, path);
x->enc = RD_ENC_UTF8;
rd_enc_t tmp_enc = RD_ENC_UTF8;
if (rd_src_txt_enc(x->src, &tmp_enc) == 0) x->enc = tmp_enc;
x->encname = fmt_encname(x->fmt, x->enc);
x->is_img = (x->fmt == RD_FMT_CBZ);
if (!x->is_img) {
@@ -210,6 +215,7 @@ int rd_book_font(rd_book *b) { return b ? b->fs : 0; }
int rd_book_ls(rd_book *b) { return b ? b->ls : 0; }
int rd_book_is_image(rd_book *b) { return b ? b->is_img : 0; }
const char *rd_book_encname(const rd_book *b) { return b ? (b->encname ? b->encname : "") : ""; }
rd_enc_t rd_book_enc_get(const rd_book *b) { return b ? b->enc : RD_ENC_UTF8; }
void rd_book_geom(rd_book *b, int *fs, int *ls, int *pw, int *ph, int *mx, int *my, int *top)
{
@@ -225,6 +231,28 @@ void rd_book_geom(rd_book *b, int *fs, int *ls, int *pw, int *ph, int *mx, int *
const char *rd_book_basename(const rd_book *b) { return b ? b->base : ""; }
const char *rd_book_path(const rd_book *b) { return b ? b->path : ""; }
/* R3:TXT 编码切换——重开 src(带覆盖编码)、重建页索引、回锚当前页。非 TXT/CBZ 返回 -1。
* 事务式:先开新 src 成功才换(失败保留旧 src,书仍可用,返回负值)。 */
int rd_book_set_enc(rd_book *b, rd_enc_t enc)
{
if (!b || b->is_img || b->fmt != RD_FMT_TXT) return -1;
if (enc == b->enc) return b->cur; /* 同编码,无需重建 */
int seg; int32_t ch;
rd_book_anchor(b, &seg, &ch);
rd_src *ns = NULL;
if (rd_src_open_with_enc(&ns, b->path, &enc) != 0) return -2; /* 旧 src 未动 */
rd_src_free(b->src); b->src = ns; /* 换新 */
b->enc = enc;
b->encname = fmt_encname(b->fmt, b->enc);
/* 重建索引(复用现值 fs/ls/content_*),再回锚 */
free(b->segs); free(b->pages); b->segs = NULL; b->pages = NULL;
b->nsegs = b->npages = 0;
if (build_index(b) != 0) return -3; /* 新 src 有效但索引空 → 渲染返回 -1,安全降级 */
return rd_book_jump_to_anchor(b, seg, ch, b->fs, b->ls);
}
void rd_book_anchor(rd_book *b, int *seg, int32_t *ch)
{
if (!b || b->npages <= 0) { if (seg) *seg = 0; if (ch) *ch = 0; return; }
@@ -243,6 +271,15 @@ int rd_book_turn(rd_book *b, int delta)
return np;
}
int rd_book_set_page(rd_book *b, int page)
{
if (!b || b->npages <= 0) return 0;
if (page < 0) page = 0;
if (page >= b->npages) page = b->npages - 1;
b->cur = page;
return page;
}
/* ---- 文本页渲染(FreeType 字形 → 全屏 565) ---- */
static int render_text_page(rd_book *b, int page_no, uint16_t *out, int pitch, uint16_t bg565, uint16_t fg565)
{
+6
View File
@@ -35,6 +35,7 @@ void rd_book_close(rd_book *b);
int rd_book_pages(rd_book *b);
int rd_book_current(rd_book *b); /* 当前页 */
int rd_book_turn(rd_book *b, int delta); /* +1/-1;clamp 到 [0,npages-1] */
int rd_book_set_page(rd_book *b, int page); /* 跳到绝对页;clamp;返回实际页(段刮擦用) */
int rd_book_is_image(rd_book *b); /* 1=CBZ 图片页书 */
/* 目录:0=无;i 项标题写到 title,目标段/字符偏移填 *seg 与 *ch。 */
@@ -56,8 +57,13 @@ int rd_book_render_text(rd_book *b, int page_no, rd_book_line_fn fn, void *ctx)
/* 书名/信息(章节名:TXT 无章节=文件名;enc 可读名)。调用方 strcpy 到自己的缓冲。 */
const char *rd_book_basename(const rd_book *b); /* 文件名(路径尾分量) */
const char *rd_book_encname(const rd_book *b);
rd_enc_t rd_book_enc_get(const rd_book *b); /* TXT 有效编码(非 TXT = RD_ENC_UTF8) */
const char *rd_book_path(const rd_book *b); /* 原始路径 */
/* R3:TXT 编码切换:重开 src(覆盖编码)+ 重建索引 + 回锚。返回目标页;非 TXT/CBZ 或失败负值。
* 用于「编码」菜单(Big5 繁体书手动选定)。 */
int rd_book_set_enc(rd_book *b, rd_enc_t enc);
/* 字体/几何 */
int rd_book_font(rd_book *b); /* 当前字号 */
int rd_book_ls(rd_book *b); /* 行距% */
+4 -2
View File
@@ -68,18 +68,19 @@ int rdprog_load(const char *book_path, rdprog *p)
FILE *f = fopen(path, "rb");
if (!f) return 0;
char line[256];
int seg = 0, fs = 0, ls = 0; int32_t ch = 0; uint64_t ts = 0; int v = 0, found = 0;
int seg = 0, fs = 0, ls = 0, enc = -1; int32_t ch = 0; uint64_t ts = 0; int v = 0, found = 0;
while (fgets(line, sizeof line, f)) {
if (sscanf(line, "v=%d", &v) == 1) { if (v == 1) found = 1; }
else if (sscanf(line, "seg=%d", &seg) == 1) { /* no-op */ }
else if (sscanf(line, "ch=%d", &ch) == 1) { /* no-op */ }
else if (sscanf(line, "fs=%d", &fs) == 1) { /* no-op */ }
else if (sscanf(line, "ls=%d", &ls) == 1) { /* no-op */ }
else if (sscanf(line, "enc=%d", &enc) == 1) { /* no-op(旧档无此键→enc=-1 自动) */ }
else if (sscanf(line, "ts=%llu", (unsigned long long *)&ts) == 1) { /* no-op */ }
}
fclose(f);
if (!found) return 0;
p->valid = 1; p->seg = seg; p->ch = ch; p->fs = fs; p->ls = ls; p->ts = ts;
p->valid = 1; p->seg = seg; p->ch = ch; p->fs = fs; p->ls = ls; p->enc = enc; p->ts = ts;
return 1;
}
@@ -96,6 +97,7 @@ int rdprog_save(const char *book_path, const rdprog *p)
fprintf(f, "ch=%d\n", (int)p->ch);
fprintf(f, "fs=%d\n", p->fs);
fprintf(f, "ls=%d\n", p->ls);
fprintf(f, "enc=%d\n", p->enc);
fprintf(f, "ts=%llu\n", (unsigned long long)p->ts);
fclose(f);
return 0;
+1
View File
@@ -21,6 +21,7 @@ typedef struct rdprog {
int32_t ch;
int fs;
int ls;
int enc; /* R3:显式文本编码(rd_enc_t);-1=自动(未手动选) */
uint64_t ts; /* 最近阅读 unix 秒 */
} rdprog;
+272 -27
View File
@@ -17,7 +17,7 @@
#include <time.h>
#define RD_MENU_H 64
#define RD_MENU_BTN 6
#define RD_MENU_BTN 7
static rd_book *g_book;
static int g_reader_active;
@@ -34,12 +34,47 @@ static int g_dir_top, g_dir_row_h; /* 目录列表几何(rdsrc 目录项
static char g_font_path[400];
static int g_notice_ms;
/* ---- R3 翻页动画(轻量水平滑入) ---- */
static meow_anim *g_anim; /* 活动翻页动画;NULL=无 */
static uint16_t *g_anim_old; /* 动画「滑出」页位图(切页前快照) */
static int g_anim_dir; /* 1=下一页(旧左移/新右入);-1=上一页(旧右移/新左入) */
/* ---- R3 段刮擦(右缘拖动快速定位 + 预览) ---- */
#define RD_SCRUB_W 26 /* 右缘刮擦条宽度 */
static int g_scrub; /* 1=刮擦中 */
static int g_scrub_page; /* 刮擦目标页(实时预览用) */
/* ---- R3 编码切换(TXT;Big5 繁体书手动选定) ---- */
static int g_enc_idx; /* g_enc_cycle[] 下标(当前有效编码) */
static uint32_t g_page_bg, g_page_fg; /* 正文页底/字色(24bit) */
static char g_chapter[128]; /* 顶栏章节名(TXT 无章节=文件名) */
#define RD_READER_DEFAULT_FS 20
#define RD_READER_DEFAULT_LS 130
/* R3:编码循环(显式编码;Auto 由打开时探测决定,不在此列)。
* -1 哨兵不用;TXT 初始 = 打开探测结果(GB18030 兜底)。 */
static const rd_enc_t g_enc_cycle[] = { RD_ENC_GB18030, RD_ENC_BIG5, RD_ENC_UTF8,
RD_ENC_UTF16LE, RD_ENC_UTF16BE };
#define RD_ENC_CYCLE_N ((int)(sizeof g_enc_cycle / sizeof g_enc_cycle[0]))
static const char *enc_cycle_name(int idx)
{
switch (g_enc_cycle[idx]) {
case RD_ENC_GB18030: return "GB18030";
case RD_ENC_BIG5: return "Big5";
case RD_ENC_UTF8: return "UTF-8";
case RD_ENC_UTF16LE: return "UTF-16LE";
case RD_ENC_UTF16BE: return "UTF-16BE";
default: return "?";
}
}
static int enc_to_cycle_idx(rd_enc_t e)
{
for (int i = 0; i < RD_ENC_CYCLE_N; i++) if (g_enc_cycle[i] == e) return i;
return -1;
}
/* 前向声明(reader_reflow* 依赖 reader_refill/redraw,定义在后) */
static void reader_redraw(void);
static int reader_refill_page(void);
@@ -96,6 +131,7 @@ static void chapter_name(void)
static void reader_reflow(void)
{
if (!g_book) return;
if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; } /* 重排/雷字号时终止翻页动画 */
int seg = 0; int32_t ch = 0;
rd_book_anchor(g_book, &seg, &ch); /* 记录当前锚,重排后回锚 */
rd_book_jump_to_anchor(g_book, seg, ch, g_fs, g_ls);
@@ -110,7 +146,9 @@ static void reader_reflow_persist(void)
int seg = 0; int32_t ch = 0;
rd_book_anchor(g_book, &seg, &ch);
rdprog p;
p.valid = 1; p.seg = seg; p.ch = ch; p.fs = g_fs; p.ls = g_ls; p.ts = (uint64_t)time(NULL);
p.valid = 1; p.seg = seg; p.ch = ch; p.fs = g_fs; p.ls = g_ls;
p.enc = (g_book && !rd_book_is_image(g_book)) ? (int)g_enc_cycle[g_enc_idx] : -1;
p.ts = (uint64_t)time(NULL);
rdprog_save(rd_book_path(g_book), &p);
}
@@ -121,6 +159,67 @@ static void reader_draw_line_atlas(int x, int y, const char *utf8, int len, void
meow_ui_text(x, y, utf8, (g_fs <= 20) ? 20 : 28, g_page_fg);
}
/* 正文内容区几何:y0=top(顶栏下沿),y1=ph-my(下边距)。 */
static int rect_y0(void) { return g_top; }
static int rect_y1(void) { return g_ph - g_my; }
/* 把一页位图的行区间 [y0,y1) 以水平偏移 dx 拷到 back_buf(裁剪到屏幕)。动画/局部重绘共用。 */
static void blit_rows(uint16_t *src, int dx, int y0, int y1)
{
unsigned char *bb = meow_ui_back_buf();
int pitch = meow_ui_back_buf_pitch();
int bpp = meow_ui_back_buf_bpp();
if (!bb || pitch <= 0 || bpp != 16) return;
int sx0 = dx < 0 ? -dx : 0;
int dx0 = dx > 0 ? dx : 0;
int w = g_pw - (dx < 0 ? -dx : dx);
if (w <= 0) return;
for (int y = y0; y < y1; y++) {
if (y < 0 || y >= g_ph) continue;
memcpy(bb + (size_t)y * pitch + (size_t)dx0 * 2,
src + (size_t)y * g_pw + sx0, (size_t)w * 2);
}
}
/* 子矩形直拷(同屏幕坐标):用于右缘刮擦条区恢复/局部重绘。 */
static void blit_sub(uint16_t *src, int sx, int sy, int w, int h)
{
unsigned char *bb = meow_ui_back_buf();
int pitch = meow_ui_back_buf_pitch();
int bpp = meow_ui_back_buf_bpp();
if (!bb || pitch <= 0 || bpp != 16) return;
for (int y = sy; y < sy + h; y++) {
if (y < 0 || y >= g_ph) continue;
memcpy(bb + (size_t)y * pitch + (size_t)sx * 2, src + (size_t)y * g_pw + sx, (size_t)w * 2);
}
}
/* R3 右缘刮擦指示条:轨道 + 拇指(当前/目标位置)+ 刮擦中预览框。render_cb 调用。 */
static void reader_draw_scrub(const meow_theme *th)
{
int np = g_book ? rd_book_pages(g_book) : 0;
if (np <= 0) return;
int y0 = rect_y0(), y1 = rect_y1(), h = y1 - y0;
if (h <= 0) return;
int page = g_scrub ? g_scrub_page : rd_book_current(g_book);
if (page < 0) page = 0;
if (page >= np) page = np - 1;
int x = g_pw - 10;
meow_ui_fill_rect(x - 1, y0, 3, h, th->dim); /* 轨道 */
int denom = np > 1 ? (np - 1) : 1;
int ty = y0 + (int)((long long)h * (np > 1 ? page : 0) / denom);
meow_ui_fill_rect(x - 3, ty - 4, 7, 8, th->primary); /* 拇指 */
if (g_scrub) {
char pv[24]; snprintf(pv, sizeof pv, "%d/%d", page + 1, np);
int bw = 58, bh = 22;
int px = x - bw - 6;
int py = ty - bh / 2;
if (py < y0) py = y0;
if (py + bh > y1) py = y1 - bh;
meow_ui_fill_rect(px, py, bw, bh, th->surfaceContainer);
meow_ui_text_center(px + bw / 2, py + (bh - 20) / 2, pv, 16, th->fg);
}
}
static void reader_canvas_render(meow_widget *w, const meow_theme *th, void *ctx)
{
(void)w; (void)ctx;
@@ -129,36 +228,39 @@ static void reader_canvas_render(meow_widget *w, const meow_theme *th, void *ctx
int page = rd_book_current(g_book);
if ((g_has_font || rd_book_is_image(g_book)) && g_page_buf[RD_PAGE_SLOT_CUR]) {
/* 翻页 blit:行拷贝当前页位图 → back_buf(局部脏区由 flush_region 上屏) */
unsigned char *bb = meow_ui_back_buf();
int pitch = meow_ui_back_buf_pitch();
int bpp = meow_ui_back_buf_bpp();
uint16_t *src = g_page_buf[RD_PAGE_SLOT_CUR];
if (bb && pitch > 0 && bpp == 16) {
for (int y = 0; y < g_ph; y++)
memcpy(bb + (size_t)y * pitch, src + (size_t)y * g_pw, (size_t)g_pw * 2);
}
blit_rows(g_page_buf[RD_PAGE_SLOT_CUR], 0, 0, g_ph);
} else {
rd_book_render_text(g_book, page, reader_draw_line_atlas, NULL);
}
}
/* 顶栏:进度% + 章节名(避开系统状态栏) */
/* 顶栏:章节名 + 页 n/N + 进度% + (TXT)编码 */
int sbh = meow_ui_statusbar_height();
int pages = rd_book_pages(g_book);
int page = rd_book_current(g_book);
char pct[16];
int pages = g_book ? rd_book_pages(g_book) : 0;
int page = g_book ? rd_book_current(g_book) : 0;
char right[40];
int p = pages > 0 ? (page * 100 / pages) : 0;
snprintf(pct, sizeof pct, "%d%%", p);
if (pages > 0) snprintf(right, sizeof right, "%d/%d %d%%", page + 1, pages, p);
else snprintf(right, sizeof right, "%d%%", p);
meow_ui_fill_rect(0, sbh, g_pw, (g_top - 4 - sbh), g_page_bg);
meow_ui_text(16, sbh + 4, g_chapter[0] ? g_chapter : "阅读", 20, th->fg);
meow_ui_text_right(g_pw - 16, sbh + 4, pct, 20, th->fg);
meow_ui_text_right(g_pw - 16, sbh + 4, right, 20, th->fg);
meow_ui_draw_rect(0, g_top - 5, g_pw, 1, th->outline);
/* R3 刮擦指示条(非目录覆盖时显示;菜单为底部条,与右缘条并存即可) */
if (g_book && !g_dir_open) reader_draw_scrub(th);
if (g_menu_open) {
int y = g_ph - RD_MENU_H;
meow_ui_fill_rect_alpha(0, y, g_pw, RD_MENU_H, 0x000000, 160);
/* 6 按钮:返回 | 目录 | 字− | 字+ | 距− | 距+ */
/* 当前编码提示(菜单上方淡条;仅文本类书有意义) */
if (g_book && !rd_book_is_image(g_book)) {
char en[64]; snprintf(en, sizeof en, "编码:%s", enc_cycle_name(g_enc_idx));
meow_ui_fill_rect_alpha(0, y - 26, g_pw, 24, 0x000000, 130);
meow_ui_text_right(g_pw - 16, y - 22, en, 16, th->dim);
}
/* 7 按钮:返回 | 目录 | 字− | 字+ | 距− | 距+ | 编码 */
int bw = g_pw / RD_MENU_BTN;
const char *labels[RD_MENU_BTN] = { "返回", "目录", "字-", "字+", "距-", "距+" };
const char *labels[RD_MENU_BTN] = { "返回", "目录", "字-", "字+", "距-", "距+", "编码" };
for (int i = 0; i < RD_MENU_BTN; i++) {
int bx = i * bw;
meow_ui_fill_rect(bx + 4, y + 12, bw - 8, RD_MENU_H - 24, th->surfaceContainer);
@@ -203,6 +305,7 @@ static void reader_redraw(void)
static void reader_free_page_bufs(void)
{
for (int i = 0; i < RD_PAGE_CACHE; i++) { free(g_page_buf[i]); g_page_buf[i] = NULL; }
if (g_anim_old) { free(g_anim_old); g_anim_old = NULL; }
}
/* 渲染当前页 + 前后各一页到 3 槽缓存(对齐设计「当前±前±后」;动画档为 无/滑动/淡入 R1 默认无)。 */
@@ -225,6 +328,104 @@ static int reader_refill_page(void)
return 0; /* 兜底路径:render_text 直绘 */
}
/* ---------- R3 翻页动画(重载页位图,轻量水平滑动,仅内容区分帧) ---------- */
static void reader_anim_update(meow_anim *a, float v, void *ctx)
{
(void)a; (void)ctx;
if (!g_book || !g_anim_old || !g_page_buf[RD_PAGE_SLOT_CUR]) return;
int off = (int)(v * g_pw);
int y0 = rect_y0(), y1 = rect_y1();
meow_ui_fill_rect(0, y0, g_pw, y1 - y0, g_page_bg); /* 内容区清底色 */
int old_x, new_x;
if (g_anim_dir == 1) { old_x = -off; new_x = g_pw - off; } /* 下一页:旧左移/新右入 */
else { old_x = off; new_x = off - g_pw; } /* 上一页:旧右移/新左入 */
blit_rows(g_anim_old, old_x, y0, y1);
blit_rows(g_page_buf[RD_PAGE_SLOT_CUR], new_x, y0, y1);
meow_ui_flush_region(0, y0, g_pw, y1 - y0);
}
static void reader_anim_done(meow_anim *a, int canceled, void *ctx)
{
(void)a; (void)ctx;
g_anim = NULL;
if (!canceled) { reader_redraw(); reader_reflow_persist(); } /* 顶栏 n/N+% 更新 + 进度保存 */
}
static void reader_turn_animated(int dir)
{
if (!g_book) return;
if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; } /* 取消进行中动画,重开 */
/* 无页位图(atlas 兜底):瞬切 */
if (!(g_has_font || rd_book_is_image(g_book)) || !g_page_buf[RD_PAGE_SLOT_CUR]) {
rd_book_turn(g_book, dir);
reader_refill_page(); reader_redraw(); reader_reflow_persist();
return;
}
if (!g_anim_old) g_anim_old = (uint16_t *)malloc((size_t)g_pw * g_ph * 2);
if (!g_anim_old) { rd_book_turn(g_book, dir); reader_refill_page(); reader_redraw(); return; }
memcpy(g_anim_old, g_page_buf[RD_PAGE_SLOT_CUR], (size_t)g_pw * g_ph * 2);
rd_book_turn(g_book, dir);
reader_refill_page();
g_anim_dir = dir;
meow_anim_desc d = { 0.0f, 1.0f, 140, MEOW_EASE_OUT, reader_anim_update, reader_anim_done, NULL };
g_anim = meow_anim_start(&d);
if (!g_anim) reader_redraw(); /* 池满等:瞬时落定 */
}
/* ---------- R3 段刮擦(右缘拖动快速定位 + 预览) ---------- */
static int scrub_page_for_y(int y)
{
int y0 = rect_y0(), y1 = rect_y1(), h = y1 - y0;
int np = g_book ? rd_book_pages(g_book) : 0;
if (h <= 0 || np <= 0) return g_book ? rd_book_current(g_book) : 0;
int cy = y - y0; if (cy < 0) cy = 0; if (cy > h) cy = h;
return (int)((long long)cy * (np - 1) / h);
}
static void reader_scrub_jump(int page)
{
if (!g_book) return;
int np = rd_book_pages(g_book);
if (np <= 0) return;
rd_book_set_page(g_book, page);
reader_refill_page();
reader_redraw(); /* 整页落定:正文 + 顶栏 + 刮擦条 */
reader_reflow_persist();
}
/* 拖动中局部重绘(不动正文页,仅顶栏 n/N + 右缘条/预览)。内容页保持(预览语义)。 */
static void reader_scrub_preview(int page)
{
g_scrub_page = page;
if (!g_book) return;
const meow_theme *th = meow_theme_get();
int sbh = meow_ui_statusbar_height();
int pages = rd_book_pages(g_book);
int y0 = rect_y0(), y1 = rect_y1();
char right[40];
int p = pages > 0 ? (g_scrub_page * 100 / pages) : 0;
if (pages > 0) snprintf(right, sizeof right, "%d/%d %d%%", g_scrub_page + 1, pages, p);
else snprintf(right, sizeof right, "%d%%", p);
/* 顶栏重画 */
meow_ui_fill_rect(0, sbh, g_pw, (g_top - 4 - sbh), g_page_bg);
meow_ui_text(16, sbh + 4, g_chapter[0] ? g_chapter : "阅读", 20, th->fg);
meow_ui_text_right(g_pw - 16, sbh + 4, right, 20, th->fg);
meow_ui_draw_rect(0, g_top - 5, g_pw, 1, th->outline);
/* 右缘条区清残留(恢复页面内容)再画条 + 预览框;无页位图(字库兜底)则跳过恢复 */
if (g_page_buf[RD_PAGE_SLOT_CUR]) blit_sub(g_page_buf[RD_PAGE_SLOT_CUR], g_pw - 120, y0, 120, y1 - y0);
reader_draw_scrub(th);
meow_ui_flush_region(0, sbh, g_pw, g_top - sbh);
meow_ui_flush_region(g_pw - 120, y0, 120, y1 - y0);
}
static void reader_scrub_begin(int x, int y)
{
(void)x;
if (!g_book || g_menu_open || g_dir_open) return;
if (g_pw - x > RD_SCRUB_W) return;
if (y < rect_y0() || y >= rect_y1()) return;
int np = rd_book_pages(g_book);
if (np <= 1) return;
g_scrub = 1;
if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; }
reader_scrub_preview(scrub_page_for_y(y));
}
/* ---------- 打开 ---------- */
void reader_open(const char *path)
{
@@ -263,8 +464,27 @@ void reader_open(const char *path)
for (int i = 0; i < RD_PAGE_CACHE; i++)
g_page_buf[i] = (uint16_t *)malloc((size_t)g_pw * (size_t)g_ph * 2);
if (!g_page_buf[RD_PAGE_SLOT_CUR]) { reader_free_page_bufs(); g_has_font = 0; }
else g_anim_old = (uint16_t *)malloc((size_t)g_pw * (size_t)g_ph * 2); /* 动画快照位图 */
}
/* 复位动画/刮擦状态(跨书) */
if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; }
g_scrub = 0; g_menu_open = 0; g_dir_open = 0;
/* 编码:初始 = 打开探测结果;进度里的显式编码(Big5 用户选定)优先重开 */
if (g_book && !rd_book_is_image(g_book)) {
int idx = enc_to_cycle_idx(rd_book_enc_get(g_book));
g_enc_idx = idx >= 0 ? idx : 0;
if (hasprog && prog.enc >= 0) {
int pi = enc_to_cycle_idx((rd_enc_t)prog.enc);
if (pi >= 0) {
if (g_enc_cycle[pi] != rd_book_enc_get(g_book)) {
if (rd_book_set_enc(g_book, g_enc_cycle[pi]) == 0) { g_enc_idx = pi; chapter_name(); }
} else g_enc_idx = pi;
}
}
} else g_enc_idx = 0;
/* 续读:进度锚(段,字符偏移)→ 跳页 */
if (hasprog && g_book) {
rd_book_jump_to_anchor(g_book, prog.seg, prog.ch, g_fs, g_ls);
@@ -272,7 +492,6 @@ void reader_open(const char *path)
reader_refill_page();
g_menu_open = 0; g_dir_open = 0;
if (g_reader_active) reader_redraw();
}
@@ -294,10 +513,11 @@ static void reader_on_leave(meow_view *v, void *ctx)
static void reader_on_destroy(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; }
if (g_book) rd_book_close(g_book);
g_book = NULL;
reader_free_page_bufs();
g_menu_open = 0; g_dir_open = 0; g_reader_active = 0;
g_menu_open = 0; g_dir_open = 0; g_scrub = 0; g_reader_active = 0;
}
meow_view *reader_view_create(void)
@@ -314,7 +534,13 @@ int reader_is_active(void) { return g_reader_active; }
void reader_on_tap(int x, int y)
{
(void)x;
if (!g_book) { return; }
if (g_scrub) { /* 刮擦抬手 → 跳到目标页收尾 */
g_scrub = 0;
reader_scrub_jump(g_scrub_page);
return;
}
if (g_dir_open) {
/* 目录覆盖层:点条目跳转,点空白关闭 */
int n = rd_book_toc_n(g_book);
@@ -359,6 +585,18 @@ void reader_on_tap(int x, int y)
case 3: if (g_fs < 40) { g_fs += 2; reader_reflow(); } break;
case 4: if (g_ls > 100) { g_ls -= 5; reader_reflow(); } break;
case 5: if (g_ls < 200) { g_ls += 5; reader_reflow(); } break;
case 6: /* 编码切换(TXT/Big5 繁体书) */
if (g_book && !rd_book_is_image(g_book)) {
int ni = (g_enc_idx + 1) % RD_ENC_CYCLE_N;
if (rd_book_set_enc(g_book, g_enc_cycle[ni]) == 0) {
g_enc_idx = ni;
chapter_name();
reader_refill_page();
reader_redraw();
reader_reflow_persist();
}
}
break;
}
if (y >= y0) return;
}
@@ -367,20 +605,27 @@ void reader_on_tap(int x, int y)
return;
}
/* 三栏:左 1/3 上一页、右 1/3 下一页、中央唤菜单 */
if (x < g_pw / 3) { rd_book_turn(g_book, -1); reader_refill_page(); reader_redraw(); }
else if (x > g_pw * 2 / 3) { rd_book_turn(g_book, 1); reader_refill_page(); reader_redraw(); }
else { g_menu_open = 1; reader_redraw(); }
if (x < g_pw / 3) { reader_turn_animated(-1); }
else if (x > g_pw * 2 / 3) { reader_turn_animated(1); }
else { if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; } g_menu_open = 1; reader_redraw(); }
}
void reader_on_swipe(int dir, int dx, int dy)
{
(void)dx; (void)dy;
if (!g_book) return;
if (g_scrub) g_scrub = 0; /* 刮擦中被滑走 → 退出刮擦 */
if (g_menu_open) { g_menu_open = 0; reader_redraw(); return; }
if (dir == MEOW_SWIPE_LEFT) { rd_book_turn(g_book, 1); }
else if (dir == MEOW_SWIPE_RIGHT) { rd_book_turn(g_book, -1); }
else return;
reader_refill_page(); reader_redraw();
if (dir == MEOW_SWIPE_LEFT) reader_turn_animated(1);
else if (dir == MEOW_SWIPE_RIGHT) reader_turn_animated(-1);
}
/* ---- R3 段刮擦触控(app.c on_touch_down/on_touch_move 路由) ---- */
void reader_on_touch_down(int x, int y) { reader_scrub_begin(x, y); }
void reader_on_touch_move(int x, int y)
{
(void)x;
if (g_scrub && g_book) reader_scrub_preview(scrub_page_for_y(y));
}
void reader_on_tick(uint64_t now)