Files
AskaEth ddc8d35eb9 fix: M6.41批 — 启动失败原因透传 + 阅读器左缘返回/阅读页接入view栈 + 后台管理按钮点不动 + 阅读器瘦身(fonts common+页位图释放) + files txt换行
- launcher(453): app_launcher.c g_launch_fail_reason+launch_fail()统一出口(5失败点 fork OOM/exec/no such file/missing dep/execpipe)+main_menu.c launch_fail_toast(缺依赖/CANNOT START: name(reason))+toast256
- launcher(457): bg_page.c bgm_enumerate跳过term_at_ms(杀后当下消失)+不可杀行按钮置灰+row_tap/top_action返回1; subpage.c 消warning
- reader(454): reader_on_swipe LEFT/RIGHT→返回(先关overlay否则page_pop回书架)+app.c书架根LEFT→exit
- reader(458瘦身A+B): app.json fonts full→common(省13MB atlas)+reader.c RD_PAGE_CACHE 3→2+g_anim_old懒分配/anim_done释放+on_enter/on_leave页位图离开释放重建
- reader(459导航): reader_open返回int+shelf点书push ACT_READER(阅读页接入view栈,on_enter/on_leave驱动) — 使454左缘返回+458瘦身B真正生效
- files(455): viewer.c viewer_cp_len+按码位折行(txt中文不冲出右缘)

0warning;宿主测试全过;files二进制已热更
2026-09-04 19:43:28 +08:00

905 lines
37 KiB
C
Raw Permalink 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.
/* app/reader.c — 阅读页:W_CUSTOM 画布 + 三栏交互 + 中央菜单占位 + 顶栏进度/章节名 + 进度持久化。
*
* 渲染:W_CUSTOM 画布覆盖全屏;render_cb 画帧 = 填底色 → 翻页 blit(FreeType 页位图缓存 /
* 无字体则 SDK 位图字库兜底直绘)→ 顶栏 → 菜单覆盖层。翻页 = 局部脏区(flush_region)。
* 交互:点击左/右 1/3 翻页;中央唤菜单(字号±/行距±/返回);左滑=下一页、右滑=上一页。
* 进度:打开自动续读;换字号重排回锚(段,字符偏移);离开/翻页时保存。
*/
#include "../reader_internal.h"
#include "meow_ui_internal.h" /* back_buf/pitch/bpp 访问器(决策见报告) */
#include "book.h"
#include "progress.h"
#include "reader_conf.h"
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#define RD_MENU_H 64
#define RD_MENU_BTN 4 /* 返回 | 目录 | 界面 | 设置(MVP 单条) */
#define RD_HEADER_H 30 /* 页头条高(书名+章节名) */
#define RD_FOOT_H 18 /* 页底条高(章节+页码) */
#define RD_UI_H 268 /* 「界面」排版面板高 */
static rd_book *g_book;
static int g_reader_active;
static meow_widget *g_canvas;
static int g_pw, g_ph, g_top, g_mx, g_my;
static int g_fs, g_ls;
#define RD_PAGE_CACHE 2
#define RD_PAGE_SLOT_CUR 0
static uint16_t *g_page_buf[RD_PAGE_CACHE]; /* [0]=cur [1]=next(FreeType 页位图;B.1 降 3→2 槽,省 1 页 0.69MB) */
static int g_has_font; /* 1=FreeType 路径;0=SDK 字库兜底 */
static int g_menu_open;
static int g_ui_open; /* 「界面」排版面板(B) */
static int g_dir_open; /* 目录覆盖层 */
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 无章节=文件名) */
static int g_show_header, g_show_footer; /* 页头条/底条显隐(rdconf.rd.header/footer) */
static volatile int g_volpage_pending; /* 系统翻页信号待消费(IPC 线程置位,UI tick 消费,E) */
#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);
static void reader_reflow_persist(void);
/* ---------- 工具 ---------- */
static uint16_t rgb565(uint32_t c)
{
int r = (c >> 16) & 0xFF, g = (c >> 8) & 0xFF, b = c & 0xFF;
return (uint16_t)(((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3));
}
/* 按 rdconf.bg/theme 解析阅读页底/字色(默认纯黑,B 定案)。 */
static void reader_resolve_bg(uint32_t *bg, uint32_t *fg)
{
const rdconf *c = rdconf_get();
if (!c) { *bg = 0x000000; *fg = 0x9AA4C8; return; }
if (strcmp(c->bg, "black") == 0) { *bg = 0x000000; *fg = 0x9AA4C8; return; }
if (strcmp(c->bg, "white") == 0) { *bg = 0xFFFFFF; *fg = 0x1A1A2A; return; }
if (strcmp(c->bg, "sepia") == 0) { *bg = 0xF5EFDC; *fg = 0x3B3A32; return; }
/* auto:跟随主题(rd.theme=light/dark 时强制该明暗的 surface/onSurface) */
meow_theme_mode m = MEOW_THEME_AUTO;
if (strcmp(c->theme, "light") == 0) m = MEOW_THEME_LIGHT;
else if (strcmp(c->theme, "dark") == 0) m = MEOW_THEME_DARK;
if (m == MEOW_THEME_AUTO) {
const meow_theme *th = meow_theme_get();
*bg = th->surface; *fg = th->onSurface;
} else {
meow_theme t = meow_theme_defaults(m);
*bg = t.surface; *fg = t.onSurface;
}
}
/* 「界面」面板改背景(写 rdconf.bg + 重解析颜色 + 重排版页缓冲)。 */
static void reader_set_bg(const char *bg)
{
rdconf *c = rdconf_get();
if (!c) return;
strncpy(c->bg, bg, sizeof c->bg - 1); c->bg[sizeof c->bg - 1] = 0;
if (strcmp(c->bg, "black") && strcmp(c->bg, "white") &&
strcmp(c->bg, "sepia") && strcmp(c->bg, "auto")) strcpy(c->bg, "black");
rdconf_persist();
reader_resolve_bg(&g_page_bg, &g_page_fg);
reader_refill_page();
reader_redraw();
}
/* 递归(深度≤3)扫 MeowOS/fonts/ 找第一个 .otf/.ttf。0=找到。 */
static int find_font_rec(const char *dir, int depth)
{
if (depth > 3) return 0;
DIR *dp = opendir(dir);
if (!dp) return 0;
struct dirent *de;
int found = 0;
while ((de = readdir(dp)) && !found) {
if (de->d_name[0] == '.') continue;
char full[480];
if (snprintf(full, sizeof full, "%s/%s", dir, de->d_name) >= (int)sizeof full) continue;
struct stat st;
if (stat(full, &st) != 0) continue;
if (S_ISDIR(st.st_mode)) { found = find_font_rec(full, depth + 1); }
else {
const char *d = strrchr(de->d_name, '.');
if (d && (strcasecmp(d, ".otf") == 0 || strcasecmp(d, ".ttf") == 0)) {
if (strlen(full) < sizeof g_font_path) {
strcpy(g_font_path, full);
found = 1;
}
}
}
}
closedir(dp);
return found;
}
static const char *resolve_font(void)
{
g_font_path[0] = 0;
if (find_font_rec(READER_SD_ROOT "/MeowOS/fonts", 0)) return g_font_path;
return NULL;
}
static void chapter_name(void)
{
/* TXT 无章节 → 文件名;R1 固定。 */
snprintf(g_chapter, sizeof g_chapter, "%s", rd_book_basename(g_book));
}
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);
reader_refill_page();
reader_redraw();
reader_reflow_persist();
}
static void reader_reflow_persist(void)
{
if (!g_book) return;
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.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);
}
/* ---------- 渲染(W_CUSTOM render_cb) ---------- */
static void reader_draw_line_atlas(int x, int y, const char *utf8, int len, void *ctx)
{
(void)ctx; (void)len;
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);
}
}
/* ----------------「界面」排版面板(B:字号/行距/背景/顶底栏/编码) ----------------
* 自绘 + 自制命中(README §5.2 建议:W_CUSTOM 画布 + 自制 hit,零 SDK widget 协同)。
* 几何 draw/hit 共用(常量)。 */
#define UI_LABEL_X 20
#define UI_CTRL_X 130
#define UI_CTRL_W (480 - UI_CTRL_X - 16)
#define UI_ROW_H 34
static int ui_panel_y(void) { return g_ph - RD_UI_H; }
/* 单个「值按钮」:[-]/[+] 或文本钮。cx=控件区起点;返回该按钮中点 x。 */
static void ui_draw_step(int x, int w, int y, const char *tag, int active, const meow_theme *th)
{
if (active) meow_ui_fill_rect(x, y, w, 28, th->primaryContainer);
else meow_ui_fill_rect(x, y, w, 28, th->surfaceContainerHighest);
uint32_t tc = active ? th->onPrimaryContainer : th->fg;
meow_ui_text_center(x + w / 2, y + 4, tag, 20, tc);
}
static void ui_draw_chips(const char *const *labels, int n, int cur, int x, int y, int w, const meow_theme *th)
{
int gap = 4;
int cw = (w - (n - 1) * gap) / n;
for (int i = 0; i < n; i++) {
int cx = x + i * (cw + gap);
if (i == cur) meow_gfx_fill_round(cx, y, cw, 28, 14, th->primaryContainer);
else meow_gfx_fill_round(cx, y, cw, 28, 14, th->surfaceContainerHighest);
uint32_t tc = (i == cur) ? th->onPrimaryContainer : th->fg;
meow_ui_text_center(cx + cw / 2, y + 4, labels[i], 20, tc);
}
}
static void reader_draw_ui_panel(const meow_theme *th)
{
int y0 = ui_panel_y();
int top = y0 + 30;
int ry;
const rdconf *c = rdconf_get();
/* 面板底 + scrim */
meow_ui_fill_rect_alpha(0, g_top, g_pw, g_ph - g_top, 0x000000, 150);
meow_ui_fill_rect(0, y0, g_pw, RD_UI_H, th->surfaceContainer);
meow_ui_draw_rect(0, y0, g_pw, 1, th->outlineVariant);
meow_ui_text_center(240, y0 + 5, "界面 · 排版", 20, th->fg);
/* 0 字号 */
ry = top + 0 * UI_ROW_H;
meow_ui_text(UI_LABEL_X, ry + 6, "字号", 20, th->fg);
char v[16]; snprintf(v, sizeof v, "%d", g_fs);
ui_draw_step(UI_CTRL_X, 40, ry + 3, "-", 0, th);
meow_ui_text_center(UI_CTRL_X + 40 + (UI_CTRL_W - 80) / 2, ry + 6, v, 20, th->fg);
ui_draw_step(UI_CTRL_X + UI_CTRL_W - 40, 40, ry + 3, "+", 0, th);
/* 1 行距 */
ry = top + 1 * UI_ROW_H;
meow_ui_text(UI_LABEL_X, ry + 6, "行距", 20, th->fg);
snprintf(v, sizeof v, "%d", g_ls);
ui_draw_step(UI_CTRL_X, 40, ry + 3, "-", 0, th);
meow_ui_text_center(UI_CTRL_X + 40 + (UI_CTRL_W - 80) / 2, ry + 6, v, 20, th->fg);
ui_draw_step(UI_CTRL_X + UI_CTRL_W - 40, 40, ry + 3, "+", 0, th);
/* 2 背景(4 档:黑/白/米黄/自动;当前=标亮) */
ry = top + 2 * UI_ROW_H;
meow_ui_text(UI_LABEL_X, ry + 6, "背景", 20, th->fg);
static const char *bg_lbl[4] = { "黑", "白", "米黄", "自动" };
int bgcur = 0;
if (c) { if (!strcmp(c->bg, "white")) bgcur = 1; else if (!strcmp(c->bg, "sepia")) bgcur = 2;
else if (!strcmp(c->bg, "auto")) bgcur = 3; }
ui_draw_chips(bg_lbl, 4, bgcur, UI_CTRL_X, ry + 3, UI_CTRL_W, th);
/* 3 顶栏显隐(开/关) */
ry = top + 3 * UI_ROW_H;
meow_ui_text(UI_LABEL_X, ry + 6, "顶栏", 20, th->fg);
static const char *yn[2] = { "开", "关" };
ui_draw_chips(yn, 2, g_show_header ? 0 : 1, UI_CTRL_X, ry + 3, UI_CTRL_W, th);
/* 4 底栏显隐 */
ry = top + 4 * UI_ROW_H;
meow_ui_text(UI_LABEL_X, ry + 6, "底栏", 20, th->fg);
ui_draw_chips(yn, 2, g_show_footer ? 0 : 1, UI_CTRL_X, ry + 3, UI_CTRL_W, th);
/* 5 编码(TXT;显示当前,点循环) */
ry = top + 5 * UI_ROW_H;
meow_ui_text(UI_LABEL_X, ry + 6, "编码", 20, th->fg);
if (g_book && !rd_book_is_image(g_book)) {
ui_draw_step(UI_CTRL_X + UI_CTRL_W - 120, 120, ry + 3, enc_cycle_name(g_enc_idx), 0, th);
} else {
meow_ui_text(UI_CTRL_X, ry + 6, "—", 20, th->dim);
}
/* 6 返回阅读 */
ry = top + 6 * UI_ROW_H;
ui_draw_step(UI_CTRL_X, UI_CTRL_W, ry + 2, "返回阅读", 1, th);
}
/* 「界面」面板命中;返回动作码(见下方 enum)。0=无。 */
enum {
UI_NONE = 0, UI_FS_DEC, UI_FS_INC, UI_LS_DEC, UI_LS_INC,
UI_BG_0, UI_BG_1, UI_BG_2, UI_BG_3, UI_HDR_TOGGLE, UI_FTR_TOGGLE, UI_ENC, UI_CLOSE
};
static int reader_ui_hit(int x, int y)
{
int y0 = ui_panel_y();
if (y < y0) return UI_NONE;
int top = y0 + 30;
int ry;
int cx = UI_CTRL_X, cw = UI_CTRL_W;
/* 0 字号 */
ry = top + 0 * UI_ROW_H;
if (y >= ry && y < ry + UI_ROW_H) {
if (x >= cx && x < cx + 40) return UI_FS_DEC;
if (x >= cx + cw - 40 && x < cx + cw) return UI_FS_INC;
return UI_NONE;
}
/* 1 行距 */
ry = top + 1 * UI_ROW_H;
if (y >= ry && y < ry + UI_ROW_H) {
if (x >= cx && x < cx + 40) return UI_LS_DEC;
if (x >= cx + cw - 40 && x < cx + cw) return UI_LS_INC;
return UI_NONE;
}
/* 2 背景 4 chips */
ry = top + 2 * UI_ROW_H;
if (y >= ry && y < ry + UI_ROW_H && x >= cx && x < cx + cw) {
int gap = 4, cwp = (cw - 3 * gap) / 4;
int k = (x - cx) / (cwp + gap);
if (k < 0) k = 0;
if (k > 3) k = 3;
return UI_BG_0 + k;
}
/* 3 顶栏 */
ry = top + 3 * UI_ROW_H;
if (y >= ry && y < ry + UI_ROW_H && x >= cx && x < cx + cw)
return UI_HDR_TOGGLE;
/* 4 底栏 */
ry = top + 4 * UI_ROW_H;
if (y >= ry && y < ry + UI_ROW_H && x >= cx && x < cx + cw)
return UI_FTR_TOGGLE;
/* 5 编码 */
ry = top + 5 * UI_ROW_H;
if (y >= ry && y < ry + UI_ROW_H && x >= cx && x < cx + cw)
return UI_ENC;
/* 6 返回阅读 */
ry = top + 6 * UI_ROW_H;
if (y >= ry && y < ry + UI_ROW_H && x >= cx && x < cx + cw)
return UI_CLOSE;
return UI_NONE;
}
static void reader_canvas_render(meow_widget *w, const meow_theme *th, void *ctx)
{
(void)w; (void)ctx;
meow_ui_clear(g_page_bg);
if (g_book) {
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 上屏) */
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);
}
}
/* 页头条(书名 + 章节名;可选,rd.header) */
int sbh = meow_ui_statusbar_height();
int pages = g_book ? rd_book_pages(g_book) : 0;
int page = g_book ? rd_book_current(g_book) : 0;
const char *bk = g_book ? rd_book_basename(g_book) : "阅读";
if (g_show_header) {
meow_ui_fill_rect(0, sbh, g_pw, RD_HEADER_H, g_page_bg);
meow_ui_text(16, sbh + 6, bk, 16, g_page_fg);
meow_ui_text_right(g_pw - 16, sbh + 6, g_chapter[0] ? g_chapter : "阅读", 16, g_page_fg);
meow_ui_draw_rect(0, sbh + RD_HEADER_H - 1, g_pw, 1, th->outlineVariant);
}
/* 页底条(章节 + 页码;可选,rd.footer) */
if (g_show_footer) {
char ft[48];
if (pages > 0) snprintf(ft, sizeof ft, "%d/%d", page + 1, pages);
else snprintf(ft, sizeof ft, "0/0");
int fy = g_ph - RD_FOOT_H;
meow_ui_fill_rect(0, fy, g_pw, RD_FOOT_H, g_page_bg);
meow_ui_text(16, fy + 1, g_chapter[0] ? g_chapter : bk, 16, g_page_fg);
meow_ui_text_right(g_pw - 16, fy + 1, ft, 16, g_page_fg);
}
/* R3 刮擦指示条(非目录/面板覆盖时显示;菜单为底部条,与右缘条并存即可) */
if (g_book && !g_dir_open && !g_ui_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);
/* 4 按钮(MVP 单条):返回 | 目录 | 界面 | 设置 */
int bw = g_pw / 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);
meow_ui_text_center(bx + bw / 2, y + (RD_MENU_H - 20) / 2, labels[i], 20, th->fg);
}
}
if (g_ui_open && g_book) reader_draw_ui_panel(th);
if (g_dir_open && g_book) {
/* 目录覆盖层:半透明底 + 条目列表(当前行高亮) */
int n = rd_book_toc_n(g_book);
if (n > 0) {
int top = g_top + 8;
int row = 34;
int maxshow = (g_ph - top - 70) / row;
if (maxshow < 1) maxshow = 1;
if (maxshow > n) maxshow = n;
g_dir_top = top; g_dir_row_h = row;
meow_ui_fill_rect_alpha(0, g_top, g_pw, g_ph - g_top, 0x000000, 150);
char t[96]; int sg; int32_t cch;
int page = rd_book_current(g_book);
for (int i = 0; i < maxshow; i++) {
if (rd_book_toc(g_book, i, t, sizeof t, &sg, &cch) != 0) continue;
int y0 = top + i * row;
if (page == sg) meow_ui_fill_rect(0, y0, g_pw, row - 4, th->surfaceContainer);
meow_ui_text(24, y0, t, 20, th->fg);
meow_ui_draw_rect(12, y0 + row - 6, g_pw - 24, 1, th->outline);
}
meow_ui_text(20, g_ph - 34, "点条目跳转 · 点空白关闭", 16, th->dim);
} else {
g_dir_open = 0;
meow_ui_text(20, g_top + 40, "本书无目录", 20, th->dim);
}
}
}
static void reader_redraw(void)
{
if (!g_canvas) return;
meow_widget_draw(g_canvas); /* → render_cb 画帧 */
meow_ui_flush_region(0, 0, g_pw, g_ph);
}
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; }
}
/* 分配 FreeType 页位图槽(cur+next 两槽)。已备当前槽则幂等返回(开书/重入共用)。
* 动画快照 g_anim_old 不在此常驻:翻页时懒分配、动画完成即释放(见 turn_animated/anim_done)。
* 分配失败 → g_has_font=0,走字库兜底路径。 */
static void reader_alloc_page_bufs(void)
{
if (!(g_has_font || rd_book_is_image(g_book))) return;
if (g_page_buf[RD_PAGE_SLOT_CUR]) return; /* 已备当前槽(重入)→ 幂等 */
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; }
}
/* 渲染当前页 + 下一预取页到 2 槽缓存(B.1:降 3→2,去 prev 槽;动画档 无/滑动/淡入 R1 默认无)。 */
static int reader_refill_page(void)
{
if (!g_book) return -1;
if ((g_has_font || rd_book_is_image(g_book)) && g_page_buf[RD_PAGE_SLOT_CUR]) {
uint16_t bg = rgb565(g_page_bg), fg = rgb565(g_page_fg);
int cur = rd_book_current(g_book);
int np = rd_book_pages(g_book);
int slots[RD_PAGE_CACHE] = { cur, cur + 1 };
for (int i = 0; i < RD_PAGE_CACHE; i++) {
int p = slots[i];
if (p >= 0 && p < np) rd_book_render(g_book, p, g_page_buf[i], g_pw * 2, bg, fg);
else for (int y = 0; y < g_ph; y++) /* 空白槽填背景 */
for (int x = 0; x < g_pw; x++) g_page_buf[i][(size_t)y * g_pw + x] = bg;
}
return 0;
}
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;
/* 动画完成/取消即释放旧页快照位图,不驻留;下一翻页懒分配(见 reader_turn_animated)。 */
if (g_anim_old) { free(g_anim_old); g_anim_old = 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();
}
/* 拖动中局部重绘(不动正文页,仅页底页码 + 右缘条/预览)。内容页保持(预览语义)。 */
static void reader_scrub_preview(int page)
{
g_scrub_page = page;
if (!g_book) return;
const meow_theme *th = meow_theme_get();
int pages = rd_book_pages(g_book);
int y0 = rect_y0(), y1 = rect_y1();
char ft[48];
if (pages > 0) snprintf(ft, sizeof ft, "%d/%d", g_scrub_page + 1, pages);
else snprintf(ft, sizeof ft, "0/0");
/* 页底条页码重画(页码已移到底条) */
if (g_show_footer) {
int fy = g_ph - RD_FOOT_H;
meow_ui_fill_rect(0, fy, g_pw, RD_FOOT_H, g_page_bg);
meow_ui_text(16, fy + 1, g_chapter[0] ? g_chapter : "阅读", 16, g_page_fg);
meow_ui_text_right(g_pw - 16, fy + 1, ft, 16, g_page_fg);
}
/* 右缘条区清残留(恢复页面内容)再画条 + 预览框;无页位图(字库兜底)则跳过恢复 */
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);
if (g_show_footer) meow_ui_flush_region(0, g_ph - RD_FOOT_H, g_pw, RD_FOOT_H);
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));
}
/* ---------- 打开 ---------- */
int reader_open(const char *path)
{
if (!path || !path[0]) return -1;
if (g_book) { rd_book_close(g_book); g_book = NULL; }
reader_free_page_bufs();
const char *fp = resolve_font();
g_has_font = (fp != NULL);
/* 全局默认字号/行距(reader.conf;进度里的每书偏好随后覆盖) */
const rdconf *rdc = rdconf_get();
g_fs = (rdc && rdc->fs >= 14 && rdc->fs <= 40) ? rdc->fs : RD_READER_DEFAULT_FS;
g_ls = (rdc && rdc->ls >= 100 && rdc->ls <= 200) ? rdc->ls : RD_READER_DEFAULT_LS;
g_show_header = rdc ? (rdc->header ? 1 : 0) : 1;
g_show_footer = rdc ? (rdc->footer ? 1 : 0) : 1;
/* 进度(含字号/行距偏好) */
rdprog prog;
int hasprog = (rdprog_load(path, &prog) == 1) && prog.valid;
if (hasprog) { if (prog.fs >= 14 && prog.fs <= 40) g_fs = prog.fs; if (prog.ls >= 100 && prog.ls <= 200) g_ls = prog.ls; }
int sbh = meow_ui_statusbar_height();
g_pw = meow_ui_width(); g_ph = meow_ui_height();
g_top = sbh + RD_HEADER_H; g_mx = 16; g_my = 20;
/* 阅读页底/字色:rd.bg(默认纯黑,B)→ 覆盖 th->surface */
reader_resolve_bg(&g_page_bg, &g_page_fg);
/* 开书(FreeType 路径带齐几何;无字体则 g_has_font=0 → atlas 兜底) */
int rc = rd_book_open(&g_book, path, fp, g_fs, g_ls, g_pw, g_ph, g_mx, g_my, g_top);
if (rc != 0) {
meow_log(MEOW_LOG_WARN, "book open failed rc=%d path=%s", rc, path);
g_book = NULL; g_has_font = 0;
g_notice_ms = 3000;
return -1;
}
chapter_name();
if (g_has_font || rd_book_is_image(g_book)) {
reader_free_page_bufs(); /* 丢弃上一本书的页位图(重开/换书) */
reader_alloc_page_bufs(); /* 重建 cur+next 两槽;g_anim_old 翻页时懒分配 */
}
/* 复位动画/刮擦状态(跨书) */
if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; }
g_scrub = 0; g_menu_open = 0; g_dir_open = 0; g_ui_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);
}
reader_refill_page();
if (g_reader_active) reader_redraw();
return 0;
}
/* ---------- 视图生命周期 ---------- */
static void reader_on_create(meow_view *v, void *ctx) { (void)v; (void)ctx; }
static void reader_on_enter(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
g_reader_active = 1;
if (g_book) {
reader_alloc_page_bufs(); /* 离开已释放页位图 → 重建;未释放则幂等返回 */
reader_refill_page();
chapter_name();
}
reader_redraw();
}
static void reader_on_leave(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
reader_reflow_persist(); /* 离开保存进度 */
reader_free_page_bufs(); /* 离开阅读页释放页位图(cur+next 两槽+动画快照)→ on_enter 重建 */
g_reader_active = 0;
}
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_ui_open = 0; g_scrub = 0; g_reader_active = 0;
}
meow_view *reader_view_create(void)
{
static meow_view_hooks hooks = { reader_on_create, reader_on_enter, reader_on_leave,
reader_on_destroy, NULL, NULL, NULL };
meow_view *v = meow_view_create(ACT_READER, MEOW_VIEW_ACTIVITY, &hooks, NULL);
g_canvas = meow_widget_custom_create(0, 0, 480, 720, reader_canvas_render, NULL, NULL);
meow_view_add_widget(v, g_canvas);
return v;
}
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);
if (n > 0 && x >= 0 && y >= g_dir_top) {
int row = g_dir_row_h > 0 ? g_dir_row_h : 34;
int idx = (y - g_dir_top) / row;
if (idx < n && idx >= 0) {
char t[96]; int sg; int32_t cch;
if (rd_book_toc(g_book, idx, t, sizeof t, &sg, &cch) == 0) {
if (g_book && sg >= 0) {
rd_book_jump_to_anchor(g_book, sg, cch, g_fs, g_ls);
g_dir_open = 0; g_menu_open = 0;
snprintf(g_chapter, sizeof g_chapter, "%s", t);
reader_refill_page();
reader_redraw();
reader_reflow_persist();
return;
}
}
}
}
g_dir_open = 0; reader_redraw();
return;
}
if (g_ui_open) {
int a = reader_ui_hit(x, y);
switch (a) {
case UI_FS_DEC: if (g_fs > 14) { g_fs -= 2; reader_reflow(); } break;
case UI_FS_INC: if (g_fs < 40) { g_fs += 2; reader_reflow(); } break;
case UI_LS_DEC: if (g_ls > 100) { g_ls -= 5; reader_reflow(); } break;
case UI_LS_INC: if (g_ls < 200) { g_ls += 5; reader_reflow(); } break;
case UI_BG_0: reader_set_bg("black"); break;
case UI_BG_1: reader_set_bg("white"); break;
case UI_BG_2: reader_set_bg("sepia"); break;
case UI_BG_3: reader_set_bg("auto"); break;
case UI_HDR_TOGGLE:
g_show_header = !g_show_header;
if (rdconf_get()) { rdconf_get()->header = g_show_header; rdconf_persist(); }
reader_redraw();
break;
case UI_FTR_TOGGLE:
g_show_footer = !g_show_footer;
if (rdconf_get()) { rdconf_get()->footer = g_show_footer; rdconf_persist(); }
reader_redraw();
break;
case UI_ENC: /* 编码切换(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;
case UI_CLOSE: g_ui_open = 0; reader_redraw(); break;
default: if (a == UI_NONE) { g_ui_open = 0; reader_redraw(); } break;
}
return;
}
if (g_menu_open) {
/* 菜单按钮命中 */
int y0 = g_ph - RD_MENU_H;
if (y >= y0) {
int bw = g_pw / RD_MENU_BTN;
int idx = x / bw;
switch (idx) {
case 0: /* 返回书架 */
g_menu_open = 0;
reader_reflow_persist();
meow_view_page_pop();
return;
case 1: /* 目录 */
if (rd_book_toc_n(g_book) > 0) { g_dir_open = 1; g_menu_open = 0; reader_redraw(); }
else { g_menu_open = 0; reader_redraw(); }
return;
case 2: /* 界面(阅读排版设置) */
g_ui_open = 1; g_menu_open = 0; reader_redraw();
return;
case 3: /* 设置 → App 设置页 */
reader_reflow_persist();
meow_view_page_push(ACT_SETTINGS);
return;
}
if (y >= y0) return;
}
g_menu_open = 0; /* 点菜单外关闭 */
reader_redraw();
return;
}
/* 三栏:左 1/3 上一页、右 1/3 下一页、中央唤菜单 */
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; /* 刮擦中被滑走 → 退出刮擦 */
/* 水平滑 = 返回语义(镜像菜单「返回书架」,见上文 g_menu_open 分支)。SDK 把「左缘右滑
* (返回手势)」判为 MEOW_SWIPE_LEFT 下发;旧语义拿去翻下一页,会与 SDK 自绘的左缘返回
* 指示条错位(M6.40 真机 bug:有指示条却翻页/不返回)。故水平滑统一「先关一层 overlay,
* 否则回书架」。翻页不再交给水平滑——保留三区点按(左 1/3 上页 / 右 1/3 下页)与右缘
* 刮擦(reader_scrub_begin,由 touch_down/move 驱动)两条既有路径。 */
if (g_ui_open) { g_ui_open = 0; reader_redraw(); return; }
if (g_dir_open) { g_dir_open = 0; reader_redraw(); return; }
if (g_menu_open) { g_menu_open = 0; reader_redraw(); return; }
if (dir == MEOW_SWIPE_LEFT || dir == MEOW_SWIPE_RIGHT) {
reader_reflow_persist();
meow_view_page_pop();
}
}
/* ---- 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));
}
/* 系统翻页信号(E):reader_ipc 线程收到 MSG_KEY_PAGEUP/PAGEDOWN 后置 pending 计数;
* 真正翻页在 UI 线程 reader_on_tick 消费(避免跨线程直接改页面状态)。 */
void reader_page_key(int dir)
{
int p = g_volpage_pending + dir;
if (p > 2) p = 2;
if (p < -2) p = -2;
g_volpage_pending = p;
}
void reader_on_tick(uint64_t now)
{
(void)now;
if (g_volpage_pending) {
int dir = g_volpage_pending > 0 ? 1 : -1;
g_volpage_pending = 0;
if (g_book && !g_menu_open && !g_dir_open && !g_ui_open)
reader_turn_animated(dir);
}
if (g_notice_ms > 0) { g_notice_ms--; if (g_notice_ms == 0) reader_redraw(); }
}