Files
MeowOS-For-R3P2/patch_v2/files_app/ui/viewer.c
T
MeowOS c187b7bce3 feat(files): 新增外置系统文件管理器 os.meow.files (agent-183) — 目录浏览(虚拟化列表/空态)+文本预览(≤256KB截断)+删除/重命名/新建/属性(BottomSheet+IME);路径写操作限 TF 卡内(拒 .. 越界);多文件结构(app.c+ui/{browser,viewer}.c+ui/common/chrome.c+logic/fsx.c) 按批次②范本
files md5 6f6b0a6d; 0 warning; 宿主 test_fsx 全过(路径安全/格式化/排序/虚拟化/写操作/文本嗅探); qemu 离屏冒烟通过
SDK 缺口记录: listview 仅单行 label(本 App 用 custom 画布自绘行); common 档字库中文缺字(未声明 fonts:full)
真机待验证: 列表渲染/滚动手感/IME 重命名/弹窗交互
2026-08-31 04:03:56 +08:00

282 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.
/* ui/viewer.c — 文本预览页(ACT_VIEWER,子页)view + 控制器。
*
* 只读、可滚动、≤256KB 截断(APP_READ_MAX,超长只读前 256KB)。
* 行模型:把读入内容按 '\n' 切成逻辑行(<= VIEWER_MAX_LINES,超长截断并附加提示);
* 每逻辑行按可视列宽软换行成「屏幕行」;滚动按屏幕行像素仿真化(只画可见屏幕行)。
* 打开时 meow_view_page_push(ACT_VIEWER)(on_enter 置标题/清滚动/整帧重画);返回=左缘左滑/返回钮。
*/
#include "files_internal.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
typedef struct { meow_view *view; int active; } viewer_ctrl;
static viewer_ctrl g_vc;
#define VIEWER_MAX_LINES 8192 /* 逻辑行上限(超长折行会很多,但按行数界内存) */
static char *g_buf; /* malloc'd 读入内容(前 256KB,NUL 结尾) */
static size_t g_buf_len;
static char g_fname[FSX_MAX_NAME];
static int *g_line; /* g_nline 个:每逻辑行起始偏移 */
static int g_nline;
static int *g_rown; /* g_nline 个:每逻辑行对应的首「屏幕行」下标 */
static int g_nrows; /* 总屏幕行数(= 末行 rown + 其折行数) */
static int g_vscroll; /* 滚动偏移(px) */
static int g_vdrag, g_vdrag_last_y;
static meow_widget *g_tb;
static meow_widget *g_vcw; /* 内容区 custom widget(画文本行) */
static app_rect_t r_content;
/* 可视列宽(像素)。 */
static int viewer_colw(void) { return r_content.w - 2 * APP_PAD; }
/* 文本行区视口高(底部留页脚)。 */
static int viewer_vp_h(void) { return r_content.h - APP_FOOTER_H; }
/* 屏幕行高(px)。 */
static int viewer_rowh(const meow_theme *th) { return meow_ui_text_height(th->font_body) + 6; }
/* 折行:返回逻辑行 li 折成的屏幕行数;若 intra>=0,把该屏幕行文本写入 out(NUL 结尾)。 */
static int viewer_wrap_line(int li, int intra, char *out, int outsz)
{
if (!g_buf || li < 0 || li >= g_nline) return 0;
int start = g_line[li];
int end = (li + 1 < g_nline) ? g_line[li + 1] : (int)g_buf_len;
int len = end - start;
while (len > 0 && (g_buf[start + len - 1] == '\n' || g_buf[start + len - 1] == '\r')) len--;
int colw = viewer_colw();
int col = 0, row = 0, outlen = 0;
char ch[2]; ch[1] = '\0';
for (int i = 0; i < len; i++) {
ch[0] = g_buf[start + i];
if (ch[0] == '\r') continue;
int w = meow_ui_text_width(ch, 20);
if (col + w > colw && col > 0) { row++; col = 0; }
if (row == intra && out && outlen < outsz - 1) out[outlen++] = ch[0];
col += w;
}
if (out) out[outlen] = '\0';
return row + 1;
}
/* 读入文件并建行表;0=文本预览可读,-1=非文本/读失败。 */
static int viewer_build(const char *path)
{
free(g_buf); free(g_line); free(g_rown);
g_buf = NULL; g_line = NULL; g_rown = NULL;
g_nline = 0; g_nrows = 0; g_buf_len = 0;
g_fname[0] = '\0';
int fd = open(path, O_RDONLY);
if (fd < 0) return -1;
size_t cap = APP_READ_MAX + 1;
g_buf = (char *)malloc(cap);
if (!g_buf) { close(fd); return -1; }
ssize_t n = read(fd, g_buf, APP_READ_MAX);
close(fd);
if (n <= 0) { free(g_buf); g_buf = NULL; return -1; }
g_buf_len = (size_t)n;
g_buf[g_buf_len] = '\0';
/* 非文本嗅探:前 512 字节出现 NUL 视为二进制 */
int lim = (int)(g_buf_len < 512 ? g_buf_len : 512);
for (int i = 0; i < lim; i++) if (g_buf[i] == '\0') { free(g_buf); g_buf = NULL; return -1; }
/* 文件名 */
const char *bn = fsx_basename(path);
strncpy(g_fname, bn, FSX_MAX_NAME - 1);
g_fname[FSX_MAX_NAME - 1] = '\0';
/* 切逻辑行(上限 VIEWER_MAX_LINES;超出截断,末行吸收剩余 + 附截断提示) */
int cap_l = VIEWER_MAX_LINES;
g_line = (int *)malloc((size_t)cap_l * sizeof(int));
g_rown = (int *)malloc((size_t)cap_l * sizeof(int));
if (!g_line || !g_rown) { free(g_buf); free(g_line); free(g_rown); g_buf = NULL; g_line=NULL; g_rown=NULL; return -1; }
g_nline = 0;
g_line[g_nline++] = 0;
int truncated = 0;
for (size_t i = 0; i < g_buf_len; i++) {
if (g_buf[i] == '\n') {
if (g_nline < cap_l) g_line[g_nline++] = (int)i + 1;
else { truncated = 1; break; }
}
}
if (truncated) {
g_line[g_nline - 1] = (int)g_buf_len; /* 让最后一行吸收剩余 */
}
/* 折行累加 → 屏幕行下标 */
g_nrows = 0;
for (int li = 0; li < g_nline; li++) {
g_rown[li] = g_nrows;
g_nrows += viewer_wrap_line(li, -1, NULL, 0);
}
return 0;
}
/* 宿主测试/入口兼容:读入 buffer 并解析行。 */
void viewer_load(const char *path) { viewer_build(path); }
/* 内容区 render_cb:虚拟化画可见屏幕行。 */
static void viewer_render(meow_widget *w, const meow_theme *th, void *ctx)
{
(void)w; (void)ctx;
if (!g_buf || g_nrows <= 0) {
app_draw_empty(r_content, "NOT A TEXT FILE", g_fname[0] ? g_fname : "");
return;
}
int rowh = viewer_rowh(th);
if (rowh < 1) rowh = 1;
int vp = viewer_vp_h();
int top = g_vscroll / rowh;
int bot = (g_vscroll + vp) / rowh + 1;
if (top < 0) top = 0;
if (bot > g_nrows) bot = g_nrows;
char row[512];
for (int sr = top; sr < bot; sr++) {
/* 二分找逻辑行 li:g_rown[li] <= sr 的最大 li */
int lo = 0, hi = g_nline - 1, li = 0;
while (lo <= hi) {
int mid = (lo + hi) / 2;
if (g_rown[mid] <= sr) { li = mid; lo = mid + 1; }
else { hi = mid - 1; }
}
int intra = sr - g_rown[li];
row[0] = '\0';
viewer_wrap_line(li, intra, row, (int)sizeof row);
if (!row[0] && intra == 0) row[0] = ' ', row[1] = '\0';
int ry = r_content.y + (sr * rowh - g_vscroll);
if (ry < r_content.y) continue;
if (ry >= r_content.y + vp) break;
meow_ui_text(r_content.x + APP_PAD, ry, row, th->font_body, th->onSurface);
}
/* 页脚:行数 + 文件名(含截断提示) */
char foot[128];
if (g_buf_len >= APP_READ_MAX)
snprintf(foot, sizeof foot, ">256KB (truncated) %d row(s)", g_nrows);
else
snprintf(foot, sizeof foot, "%d row(s)", g_nrows);
app_draw_footer(r_content, foot);
}
static int viewer_hit(meow_widget *w, int x, int y, void *ctx)
{
(void)w; (void)ctx;
return x >= r_content.x && x < r_content.x + r_content.w &&
y >= r_content.y && y < r_content.y + r_content.h;
}
static void viewer_clamp_scroll(void)
{
const meow_theme *th = meow_theme_get();
int rowh = viewer_rowh(th);
int content_h = g_nrows * rowh;
int maxs = content_h - viewer_vp_h();
if (maxs < 0) maxs = 0;
if (g_vscroll < 0) g_vscroll = 0;
if (g_vscroll > maxs) g_vscroll = maxs;
}
int viewer_open_file(const char *path)
{
if (viewer_build(path) != 0) return -1;
meow_view_page_push(ACT_VIEWER);
return 0;
}
int viewer_is_active(void) { return g_vc.active; }
void viewer_swipe(int dir, int dist)
{
if (dir != MEOW_SWIPE_UP && dir != MEOW_SWIPE_DOWN) return;
int del = (dir == MEOW_SWIPE_UP) ? dist : -dist;
g_vscroll += del;
viewer_clamp_scroll();
meow_ui_invalidate(r_content.x, r_content.y, r_content.w, r_content.h);
}
void viewer_touch_down(int x, int y)
{
(void)x;
g_vdrag = 0; g_vdrag_last_y = 0;
if (!g_buf) return;
if (meow_widget_hit(g_vcw, x, y)) { g_vdrag = 1; g_vdrag_last_y = y; }
}
void viewer_touch_move(int x, int y)
{
(void)x;
if (!g_vdrag) return;
int dy = y - g_vdrag_last_y;
g_vdrag_last_y = y;
if (dy == 0) return;
g_vscroll -= dy;
viewer_clamp_scroll();
meow_ui_invalidate(r_content.x, r_content.y, r_content.w, r_content.h);
}
int viewer_on_tap(int x, int y)
{
if (g_tb) {
int ah = meow_topappbar_hit(g_tb, x, y);
if (ah == MEOW_TOPBAR_BACK) { meow_view_page_pop(); return 1; }
}
return 0;
}
/* ---- 页 view 生命周期 ---- */
static void viewer_page_create(meow_view *v, void *ctx)
{
(void)v;
((viewer_ctrl *)ctx)->view = v;
r_content = app_content_rect();
meow_ui_activity_push(ACT_VIEWER); /* 让下面控件 owner=ACT_VIEWER */
g_tb = app_topbar_create("Preview", 1, NULL);
g_vcw = meow_widget_custom_create(r_content.x, r_content.y, r_content.w, r_content.h,
viewer_render, viewer_hit, NULL);
meow_ui_activity_pop();
}
static void viewer_page_enter(meow_view *v, void *ctx)
{
(void)v;
((viewer_ctrl *)ctx)->active = 1;
g_vscroll = 0;
if (g_tb) meow_topappbar_set_title(g_tb, g_fname[0] ? g_fname : "Preview");
const meow_theme *th = meow_theme_get();
meow_ui_set_page_bg(th->bg);
meow_ui_paint_full();
}
static void viewer_page_leave(meow_view *v, void *ctx)
{
(void)v;
((viewer_ctrl *)ctx)->active = 0;
}
static void viewer_page_destroy(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
if (g_vcw) { meow_widget_free(g_vcw); g_vcw = NULL; }
if (g_tb) { meow_widget_free(g_tb); g_tb = NULL; }
free(g_buf); free(g_line); free(g_rown);
g_buf = NULL; g_line = NULL; g_rown = NULL;
}
meow_view *viewer_view_create(void)
{
static const meow_view_hooks h = {
.on_create = viewer_page_create,
.on_enter = viewer_page_enter,
.on_leave = viewer_page_leave,
.on_destroy = viewer_page_destroy,
};
return meow_view_create(ACT_VIEWER, MEOW_VIEW_ACTIVITY, &h, &g_vc);
}