587 lines
18 KiB
C
587 lines
18 KiB
C
/* files.c — 窗口化文件资源管理器
|
|||
|
|
*
|
||
|
|
* 在桌面窗口里遍历 FAT32(含子目录与长文件名)。
|
||
|
|
* 单击选中,双击或 Enter 进入目录;文本文件交给内置编辑器。
|
||
|
|
* 左侧地点:此电脑 / 用户 / 桌面 / 系统。
|
||
|
|
*/
|
||
|
|
#include "desktop.h"
|
||
|
|
#include "zhfont.h"
|
||
|
|
|
||
|
|
#define FM_MAX 160
|
||
|
|
#define FM_NAME 64
|
||
|
|
#define FM_DEPTH 12
|
||
|
|
#define FM_TB 34
|
||
|
|
#define FM_HDR 22
|
||
|
|
#define FM_ST 22
|
||
|
|
#define FM_ROW 24
|
||
|
|
#define FM_SIDE 132
|
||
|
|
#define FM_READ_MAX 60000u
|
||
|
|
|
||
|
|
typedef struct {
|
||
|
|
char name[FM_NAME];
|
||
|
|
u32 clus;
|
||
|
|
u32 size;
|
||
|
|
u8 is_dir;
|
||
|
|
u8 is_up;
|
||
|
|
} fm_ent;
|
||
|
|
|
||
|
|
typedef struct {
|
||
|
|
fm_ent ents[FM_MAX];
|
||
|
|
int count;
|
||
|
|
int depth;
|
||
|
|
int sel;
|
||
|
|
int scroll;
|
||
|
|
int last_row;
|
||
|
|
u64 last_tsc;
|
||
|
|
u32 path_clus[FM_DEPTH];
|
||
|
|
char path_name[FM_DEPTH][FM_NAME];
|
||
|
|
char path_disp[160];
|
||
|
|
char status[96];
|
||
|
|
} fm_state;
|
||
|
|
|
||
|
|
static u8 g_fm_blob[FM_READ_MAX];
|
||
|
|
|
||
|
|
typedef struct {
|
||
|
|
u32 bg, side, bar, line, text, dim, sel, accent, folder;
|
||
|
|
} fm_pal;
|
||
|
|
|
||
|
|
static void fm_colors(fm_pal *p) {
|
||
|
|
if (g_fluent) {
|
||
|
|
p->bg = 0xFF1E1E1Eu;
|
||
|
|
p->side = 0xFF252526u;
|
||
|
|
p->bar = 0xFF2D2D2Du;
|
||
|
|
p->line = 0xFF3A3A3Au;
|
||
|
|
p->text = 0xFFF3F3F3u;
|
||
|
|
p->dim = 0xFF9A9A9Au;
|
||
|
|
p->sel = 0xFF094771u;
|
||
|
|
p->accent = 0xFF60CDFFu;
|
||
|
|
p->folder = 0xFFE8B84Au;
|
||
|
|
} else {
|
||
|
|
p->bg = KS_BG_PRIMARY;
|
||
|
|
p->side = KS_BG_SECONDARY;
|
||
|
|
p->bar = KS_BG_TERTIARY;
|
||
|
|
p->line = KS_BORDER;
|
||
|
|
p->text = KS_TEXT_PRIMARY;
|
||
|
|
p->dim = KS_TEXT_DIM;
|
||
|
|
p->sel = KS_ACCENT_DIM;
|
||
|
|
p->accent = KS_ACCENT;
|
||
|
|
p->folder = KS_ACCENT2;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fm_copy(char *dst, int cap, const char *src) {
|
||
|
|
int i = 0;
|
||
|
|
if (cap < 1) return;
|
||
|
|
while (src[i] && i < cap - 1) { dst[i] = src[i]; i++; }
|
||
|
|
dst[i] = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fm_set_status(fm_state *s, const char *msg) {
|
||
|
|
fm_copy(s->status, (int)sizeof(s->status), msg);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fm_fit(char *dst, int cap, const char *src, int max_px) {
|
||
|
|
fm_copy(dst, cap, src);
|
||
|
|
int n = kstrlen(dst);
|
||
|
|
while (n > 1 && zh_string_width(dst, 0) > max_px) {
|
||
|
|
dst[--n] = 0;
|
||
|
|
while (n > 0 && ((unsigned char)dst[n - 1] & 0xC0) == 0x80) dst[--n] = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static int fm_name_cmp(const char *a, const char *b) {
|
||
|
|
while (*a && *b) {
|
||
|
|
unsigned char ca = (unsigned char)*a, cb = (unsigned char)*b;
|
||
|
|
if (ca >= 'a' && ca <= 'z') ca = (unsigned char)(ca - 32);
|
||
|
|
if (cb >= 'a' && cb <= 'z') cb = (unsigned char)(cb - 32);
|
||
|
|
if (ca != cb) return ca < cb ? -1 : 1;
|
||
|
|
a++; b++;
|
||
|
|
}
|
||
|
|
if (*a) return 1;
|
||
|
|
if (*b) return -1;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fm_sort(fm_state *s) {
|
||
|
|
for (int i = 1; i < s->count; i++) {
|
||
|
|
fm_ent key = s->ents[i];
|
||
|
|
int j = i - 1;
|
||
|
|
while (j >= 0) {
|
||
|
|
int dir_first = (s->ents[j].is_dir != key.is_dir)
|
||
|
|
? (key.is_dir && !s->ents[j].is_dir)
|
||
|
|
: (fm_name_cmp(key.name, s->ents[j].name) < 0);
|
||
|
|
if (!dir_first) break;
|
||
|
|
s->ents[j + 1] = s->ents[j];
|
||
|
|
j--;
|
||
|
|
}
|
||
|
|
s->ents[j + 1] = key;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fm_build_path(fm_state *s) {
|
||
|
|
int p = 0;
|
||
|
|
s->path_disp[p++] = '/';
|
||
|
|
for (int d = 0; d < s->depth && p < (int)sizeof(s->path_disp) - 2; d++) {
|
||
|
|
const char *n = s->path_name[d];
|
||
|
|
while (*n && p < (int)sizeof(s->path_disp) - 2) s->path_disp[p++] = *n++;
|
||
|
|
s->path_disp[p++] = '/';
|
||
|
|
}
|
||
|
|
s->path_disp[p] = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fm_status_count(fm_state *s) {
|
||
|
|
int n = 0;
|
||
|
|
char num[12];
|
||
|
|
int p = 0;
|
||
|
|
for (int i = 0; i < s->count; i++) if (!s->ents[i].is_up) n++;
|
||
|
|
u32dec((u32)n, num);
|
||
|
|
while (num[p] && p < 80) { s->status[p] = num[p]; p++; }
|
||
|
|
{
|
||
|
|
const char *b = " 个项目";
|
||
|
|
while (*b && p < (int)sizeof(s->status) - 1) s->status[p++] = *b++;
|
||
|
|
}
|
||
|
|
s->status[p] = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int fm_collect(const f32_entry *e, void *ud) {
|
||
|
|
fm_state *s = (fm_state *)ud;
|
||
|
|
const char *nm;
|
||
|
|
fm_ent *f;
|
||
|
|
if (s->count >= FM_MAX) return 1;
|
||
|
|
nm = (e->has_lfn && e->long_name[0]) ? e->long_name : e->name;
|
||
|
|
if (!nm || !nm[0]) return 0;
|
||
|
|
f = &s->ents[s->count];
|
||
|
|
fm_copy(f->name, FM_NAME, nm);
|
||
|
|
f->clus = e->clus;
|
||
|
|
f->size = e->size;
|
||
|
|
f->is_dir = e->is_dir ? 1 : 0;
|
||
|
|
f->is_up = 0;
|
||
|
|
s->count++;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fm_insert_up(fm_state *s) {
|
||
|
|
if (s->depth <= 0) return;
|
||
|
|
if (s->count >= FM_MAX) s->count = FM_MAX - 1;
|
||
|
|
for (int i = s->count; i > 0; i--) s->ents[i] = s->ents[i - 1];
|
||
|
|
fm_copy(s->ents[0].name, FM_NAME, "..");
|
||
|
|
s->ents[0].clus = 0;
|
||
|
|
s->ents[0].size = 0;
|
||
|
|
s->ents[0].is_dir = 1;
|
||
|
|
s->ents[0].is_up = 1;
|
||
|
|
s->count++;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int fm_reload(fm_state *s) {
|
||
|
|
u32 clus;
|
||
|
|
s->count = 0;
|
||
|
|
s->sel = 0;
|
||
|
|
s->scroll = 0;
|
||
|
|
s->last_row = -1;
|
||
|
|
fm_build_path(s);
|
||
|
|
if (!g_block_read) {
|
||
|
|
fm_set_status(s, "无块设备");
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
clus = (s->depth == 0) ? 0 : s->path_clus[s->depth - 1];
|
||
|
|
if (f32_list_dir_w(clus, fm_collect, s) != 0) {
|
||
|
|
s->count = 0;
|
||
|
|
fm_set_status(s, "无法读取目录");
|
||
|
|
slog("fileman list failed");
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
fm_sort(s);
|
||
|
|
fm_insert_up(s);
|
||
|
|
fm_status_count(s);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fm_up(fm_state *s) {
|
||
|
|
if (s->depth <= 0) return;
|
||
|
|
s->depth--;
|
||
|
|
fm_reload(s);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int fm_enter_idx(fm_state *s, int idx) {
|
||
|
|
char name[FM_NAME];
|
||
|
|
u32 clus;
|
||
|
|
if (idx < 0 || idx >= s->count) return -1;
|
||
|
|
if (s->ents[idx].is_up) { fm_up(s); return 0; }
|
||
|
|
if (!s->ents[idx].is_dir) return -1;
|
||
|
|
if (s->depth >= FM_DEPTH) { fm_set_status(s, "目录太深"); return -1; }
|
||
|
|
clus = s->ents[idx].clus;
|
||
|
|
if (clus < 2) { fm_set_status(s, "无法进入目录"); return -1; }
|
||
|
|
fm_copy(name, FM_NAME, s->ents[idx].name);
|
||
|
|
s->path_clus[s->depth] = clus;
|
||
|
|
fm_copy(s->path_name[s->depth], FM_NAME, name);
|
||
|
|
s->depth++;
|
||
|
|
if (fm_reload(s) != 0) {
|
||
|
|
s->depth--;
|
||
|
|
fm_reload(s);
|
||
|
|
fm_set_status(s, "无法读取目录");
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
slog("fileman enter dir");
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int fm_enter_named(fm_state *s, const char *name) {
|
||
|
|
for (int i = 0; i < s->count; i++) {
|
||
|
|
if (s->ents[i].is_up || !s->ents[i].is_dir) continue;
|
||
|
|
if (fat32_lfn_streq_ci(s->ents[i].name, name))
|
||
|
|
return fm_enter_idx(s, i);
|
||
|
|
}
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fm_place(fm_state *s, int place) {
|
||
|
|
const char *a = 0;
|
||
|
|
const char *b = 0;
|
||
|
|
s->depth = 0;
|
||
|
|
if (fm_reload(s) != 0) return;
|
||
|
|
if (place == 1) a = "user";
|
||
|
|
else if (place == 2) { a = "user"; b = "desktop"; }
|
||
|
|
else if (place == 3) a = "system";
|
||
|
|
if (a && fm_enter_named(s, a) != 0) { fm_set_status(s, "路径不存在"); return; }
|
||
|
|
if (b && fm_enter_named(s, b) != 0) fm_set_status(s, "路径不存在");
|
||
|
|
}
|
||
|
|
|
||
|
|
static int fm_place_of(const fm_state *s) {
|
||
|
|
if (s->depth == 0) return 0;
|
||
|
|
if (s->depth == 1 && fat32_lfn_streq_ci(s->path_name[0], "user")) return 1;
|
||
|
|
if (s->depth == 1 && fat32_lfn_streq_ci(s->path_name[0], "system")) return 3;
|
||
|
|
if (s->depth == 2 && fat32_lfn_streq_ci(s->path_name[0], "user") &&
|
||
|
|
fat32_lfn_streq_ci(s->path_name[1], "desktop")) return 2;
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int fm_looks_text(const u8 *p, u32 n) {
|
||
|
|
u32 sample, bad = 0;
|
||
|
|
if (n == 0) return 1;
|
||
|
|
sample = n > 512 ? 512 : n;
|
||
|
|
for (u32 i = 0; i < sample; i++) {
|
||
|
|
u8 c = p[i];
|
||
|
|
if (c == 0) return 0;
|
||
|
|
if (c < 9 || (c > 13 && c < 32) || c == 127) bad++;
|
||
|
|
}
|
||
|
|
return bad * 10 < sample;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fm_open_file(fm_state *s, int idx) {
|
||
|
|
fm_ent *e;
|
||
|
|
u32 n;
|
||
|
|
if (idx < 0 || idx >= s->count) return;
|
||
|
|
e = &s->ents[idx];
|
||
|
|
if (e->size == 0) {
|
||
|
|
editor_open_text(e->name, (const u8 *)"", 0);
|
||
|
|
fm_set_status(s, "已在编辑器中打开");
|
||
|
|
slog("fileman open file");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (e->clus < 2) { fm_set_status(s, "读取失败"); return; }
|
||
|
|
n = e->size;
|
||
|
|
if (n > FM_READ_MAX) n = FM_READ_MAX;
|
||
|
|
for (u32 i = 0; i < n; i++) g_fm_blob[i] = 0;
|
||
|
|
if (f32_read_file_by_clus_w(e->clus, e->size, g_fm_blob, FM_READ_MAX) != 0) {
|
||
|
|
fm_set_status(s, "读取失败");
|
||
|
|
slog("fileman read failed");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (!fm_looks_text(g_fm_blob, n)) {
|
||
|
|
fm_set_status(s, "二进制文件,未打开");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
editor_open_text(e->name, g_fm_blob, n);
|
||
|
|
if (e->size > n) fm_set_status(s, "已打开(内容已截断)");
|
||
|
|
else fm_set_status(s, "已在编辑器中打开");
|
||
|
|
slog("fileman open file");
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fm_activate(fm_state *s, int idx) {
|
||
|
|
if (idx < 0 || idx >= s->count) return;
|
||
|
|
s->sel = idx;
|
||
|
|
if (s->ents[idx].is_dir || s->ents[idx].is_up) fm_enter_idx(s, idx);
|
||
|
|
else fm_open_file(s, idx);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fm_reveal(fm_state *s, int vis) {
|
||
|
|
if (vis < 1) vis = 1;
|
||
|
|
if (s->count <= 0) { s->sel = 0; s->scroll = 0; return; }
|
||
|
|
if (s->sel < 0) s->sel = 0;
|
||
|
|
if (s->sel >= s->count) s->sel = s->count - 1;
|
||
|
|
if (s->sel < s->scroll) s->scroll = s->sel;
|
||
|
|
if (s->sel >= s->scroll + vis) s->scroll = s->sel - vis + 1;
|
||
|
|
if (s->scroll < 0) s->scroll = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fm_geom(const app_ctx *ctx, int *side, int *lx, int *ly,
|
||
|
|
int *lw, int *lh, int *vis) {
|
||
|
|
int sidew = (ctx->client_w < 420) ? 0 : FM_SIDE;
|
||
|
|
int body = ctx->client_h - FM_TB - FM_ST;
|
||
|
|
if (body < FM_HDR + FM_ROW) body = FM_HDR + FM_ROW;
|
||
|
|
*side = sidew;
|
||
|
|
*lx = ctx->client_x + sidew;
|
||
|
|
*ly = ctx->client_y + FM_TB + FM_HDR;
|
||
|
|
*lw = ctx->client_w - sidew;
|
||
|
|
*lh = body - FM_HDR;
|
||
|
|
*vis = *lh / FM_ROW;
|
||
|
|
if (*vis < 1) *vis = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fm_size_str(char *out, u32 sz) {
|
||
|
|
char num[12];
|
||
|
|
const char *unit;
|
||
|
|
u32 v;
|
||
|
|
int j = 0;
|
||
|
|
if (sz < 1024u) { v = sz; unit = " B"; }
|
||
|
|
else if (sz < 1024u * 1024u) { v = sz / 1024u; unit = " K"; }
|
||
|
|
else { v = sz / (1024u * 1024u); unit = " M"; }
|
||
|
|
u32dec(v, num);
|
||
|
|
while (num[j] && j < 12) { out[j] = num[j]; j++; }
|
||
|
|
while (*unit && j < 15) out[j++] = *unit++;
|
||
|
|
out[j] = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static const char *fm_kind(const fm_ent *e) {
|
||
|
|
if (e->is_up) return "上级";
|
||
|
|
if (e->is_dir) return "文件夹";
|
||
|
|
if (fat32_lfn_ends_with_ci(e->name, ".txt") ||
|
||
|
|
fat32_lfn_ends_with_ci(e->name, ".md") ||
|
||
|
|
fat32_lfn_ends_with_ci(e->name, ".cnf") ||
|
||
|
|
fat32_lfn_ends_with_ci(e->name, ".json") ||
|
||
|
|
fat32_lfn_ends_with_ci(e->name, ".ini") ||
|
||
|
|
fat32_lfn_ends_with_ci(e->name, ".log") ||
|
||
|
|
fat32_lfn_ends_with_ci(e->name, ".h") ||
|
||
|
|
fat32_lfn_ends_with_ci(e->name, ".c"))
|
||
|
|
return "文本";
|
||
|
|
if (fat32_lfn_ends_with_ci(e->name, ".elf") ||
|
||
|
|
fat32_lfn_ends_with_ci(e->name, ".exe") ||
|
||
|
|
fat32_lfn_ends_with_ci(e->name, ".drv"))
|
||
|
|
return "程序";
|
||
|
|
if (fat32_lfn_ends_with_ci(e->name, ".png") ||
|
||
|
|
fat32_lfn_ends_with_ci(e->name, ".rgba"))
|
||
|
|
return "图片";
|
||
|
|
return "文件";
|
||
|
|
}
|
||
|
|
|
||
|
|
static int fm_hit_row(const app_ctx *ctx, int my, int scroll) {
|
||
|
|
int side, lx, ly, lw, lh, vis, row;
|
||
|
|
fm_geom(ctx, &side, &lx, &ly, &lw, &lh, &vis);
|
||
|
|
(void)side; (void)lx; (void)lw;
|
||
|
|
if (my < ly) return -1;
|
||
|
|
row = (my - ly) / FM_ROW;
|
||
|
|
if (row < 0 || row >= vis) return -1;
|
||
|
|
return scroll + row;
|
||
|
|
}
|
||
|
|
|
||
|
|
void *fileman_on_create(app_ctx *ctx) {
|
||
|
|
fm_state *s = (fm_state *)ADDR_FILEMAN;
|
||
|
|
const char *t = "Files";
|
||
|
|
u8 *raw = (u8 *)s;
|
||
|
|
for (u32 i = 0; i < (u32)sizeof(fm_state); i++) raw[i] = 0;
|
||
|
|
s->last_row = -1;
|
||
|
|
fm_reload(s);
|
||
|
|
for (int i = 0; i < 47; i++) ctx->win->title[i] = 0;
|
||
|
|
for (int i = 0; t[i] && i < 47; i++) ctx->win->title[i] = t[i];
|
||
|
|
slog("fileman window");
|
||
|
|
return s;
|
||
|
|
}
|
||
|
|
|
||
|
|
void fileman_on_destroy(void *state) { (void)state; }
|
||
|
|
|
||
|
|
void fileman_on_event(void *state, app_ctx *ctx, desktop_event *ev) {
|
||
|
|
fm_state *s = (fm_state *)state;
|
||
|
|
int side, lx, ly, lw, lh, vis;
|
||
|
|
int cx = ctx->client_x;
|
||
|
|
int cy = ctx->client_y;
|
||
|
|
fm_geom(ctx, &side, &lx, &ly, &lw, &lh, &vis);
|
||
|
|
|
||
|
|
if (ev->type == EV_KEY_DOWN) {
|
||
|
|
u8 sc = ev->scancode;
|
||
|
|
if (sc == 0x48) {
|
||
|
|
if (s->sel > 0) s->sel--;
|
||
|
|
} else if (sc == 0x50) {
|
||
|
|
if (s->sel < s->count - 1) s->sel++;
|
||
|
|
} else if (sc == 0x49) {
|
||
|
|
s->sel -= vis;
|
||
|
|
if (s->sel < 0) s->sel = 0;
|
||
|
|
} else if (sc == 0x51) {
|
||
|
|
s->sel += vis;
|
||
|
|
if (s->count > 0 && s->sel >= s->count) s->sel = s->count - 1;
|
||
|
|
} else if (sc == 0x47) {
|
||
|
|
s->sel = 0;
|
||
|
|
} else if (sc == 0x4F) {
|
||
|
|
s->sel = (s->count > 0) ? s->count - 1 : 0;
|
||
|
|
} else if (sc == 0x0E || sc == 0x4B) {
|
||
|
|
fm_up(s);
|
||
|
|
} else if (sc == 0x1C || sc == 0x4D) {
|
||
|
|
fm_activate(s, s->sel);
|
||
|
|
} else if (sc == 0x3F) {
|
||
|
|
int keep = s->sel;
|
||
|
|
fm_reload(s);
|
||
|
|
s->sel = keep;
|
||
|
|
if (s->count > 0 && s->sel >= s->count) s->sel = s->count - 1;
|
||
|
|
}
|
||
|
|
fm_reveal(s, vis);
|
||
|
|
ctx->win->dirty = 1;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ev->type != EV_MOUSE_DOWN || ev->button != 0) return;
|
||
|
|
|
||
|
|
/* 上级 */
|
||
|
|
{
|
||
|
|
int bw = zh_string_width("上级", 0) + 16;
|
||
|
|
if (ev->mx >= cx + 8 && ev->mx < cx + 8 + bw &&
|
||
|
|
ev->my >= cy + 6 && ev->my < cy + 6 + 22) {
|
||
|
|
fm_up(s);
|
||
|
|
ctx->win->dirty = 1;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 地点 */
|
||
|
|
if (side > 0 && ev->mx >= cx && ev->mx < cx + side &&
|
||
|
|
ev->my >= cy + FM_TB && ev->my < cy + ctx->client_h - FM_ST) {
|
||
|
|
int rel = ev->my - (cy + FM_TB + 8);
|
||
|
|
int slot = rel / 28;
|
||
|
|
if (rel >= 0 && slot >= 0 && slot < 4) fm_place(s, slot);
|
||
|
|
ctx->win->dirty = 1;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 滚动条 */
|
||
|
|
if (s->count > vis && ev->mx >= lx + lw - 10 && ev->mx < lx + lw &&
|
||
|
|
ev->my >= ly && ev->my < ly + lh) {
|
||
|
|
int max_scroll = s->count - vis;
|
||
|
|
int rel = ev->my - ly;
|
||
|
|
if (lh > 0 && max_scroll > 0)
|
||
|
|
s->scroll = rel * max_scroll / lh;
|
||
|
|
if (s->scroll < 0) s->scroll = 0;
|
||
|
|
if (s->scroll > max_scroll) s->scroll = max_scroll;
|
||
|
|
ctx->win->dirty = 1;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 列表行:单击选中,双击打开 */
|
||
|
|
if (ev->mx >= lx && ev->mx < lx + lw - 10) {
|
||
|
|
int idx = fm_hit_row(ctx, ev->my, s->scroll);
|
||
|
|
if (idx >= 0 && idx < s->count) {
|
||
|
|
int dbl = 0;
|
||
|
|
u64 now = rdtsc();
|
||
|
|
if (s->last_row == idx) {
|
||
|
|
if (!g_tsc_per_sec || (now - s->last_tsc) < g_tsc_per_sec / 2)
|
||
|
|
dbl = 1;
|
||
|
|
}
|
||
|
|
s->last_row = idx;
|
||
|
|
s->last_tsc = now;
|
||
|
|
s->sel = idx;
|
||
|
|
if (dbl) fm_activate(s, idx);
|
||
|
|
ctx->win->dirty = 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void fileman_on_draw(void *state, app_ctx *ctx) {
|
||
|
|
fm_state *s = (fm_state *)state;
|
||
|
|
fm_pal pal;
|
||
|
|
int side, lx, ly, lw, lh, vis;
|
||
|
|
int cx = ctx->client_x;
|
||
|
|
int cy = ctx->client_y;
|
||
|
|
int cw = ctx->client_w;
|
||
|
|
int ch = ctx->client_h;
|
||
|
|
static const char *places[4] = { "此电脑", "用户", "桌面", "系统" };
|
||
|
|
int place = fm_place_of(s);
|
||
|
|
int up_w;
|
||
|
|
char fitted[FM_NAME];
|
||
|
|
|
||
|
|
fm_colors(&pal);
|
||
|
|
fm_geom(ctx, &side, &lx, &ly, &lw, &lh, &vis);
|
||
|
|
if (s->scroll < 0) s->scroll = 0;
|
||
|
|
if (s->count > vis && s->scroll > s->count - vis) s->scroll = s->count - vis;
|
||
|
|
if (s->count <= vis) s->scroll = 0;
|
||
|
|
|
||
|
|
du_fill_rect(&g_fb, cx, cy, cw, ch, pal.bg);
|
||
|
|
|
||
|
|
/* 工具栏 */
|
||
|
|
du_fill_rect(&g_fb, cx, cy, cw, FM_TB, pal.bar);
|
||
|
|
du_divider_h(&g_fb, cx, cy + FM_TB, cw, pal.line);
|
||
|
|
up_w = zh_string_width("上级", 0) + 16;
|
||
|
|
du_fill_rounded_rect(&g_fb, cx + 8, cy + 6, up_w, 22,
|
||
|
|
(s->depth > 0) ? pal.side : pal.bg, 4);
|
||
|
|
zh_draw_string(&g_fb, "上级", cx + 16, cy + 8,
|
||
|
|
(s->depth > 0) ? pal.text : pal.dim, 0);
|
||
|
|
fm_fit(fitted, (int)sizeof(fitted), s->path_disp[0] ? s->path_disp : "/",
|
||
|
|
cw - (up_w + 28));
|
||
|
|
zh_draw_string(&g_fb, fitted, cx + 8 + up_w + 10, cy + 8, pal.accent, 0);
|
||
|
|
|
||
|
|
/* 地点 */
|
||
|
|
if (side > 0) {
|
||
|
|
du_fill_rect(&g_fb, cx, cy + FM_TB, side, ch - FM_TB - FM_ST, pal.side);
|
||
|
|
du_fill_rect(&g_fb, cx + side - 1, cy + FM_TB, 1, ch - FM_TB - FM_ST, pal.line);
|
||
|
|
for (int i = 0; i < 4; i++) {
|
||
|
|
int y = cy + FM_TB + 8 + i * 28;
|
||
|
|
if (i == place)
|
||
|
|
du_fill_rect(&g_fb, cx + 6, y, side - 14, 24, pal.sel);
|
||
|
|
zh_draw_string(&g_fb, places[i], cx + 14, y + 3,
|
||
|
|
(i == place) ? pal.text : pal.dim, 0);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 列头 */
|
||
|
|
du_fill_rect(&g_fb, lx, cy + FM_TB, lw, FM_HDR, pal.bar);
|
||
|
|
zh_draw_string(&g_fb, "名称", lx + 28, cy + FM_TB + 2, pal.dim, 0);
|
||
|
|
zh_draw_string(&g_fb, "大小", lx + lw - 150, cy + FM_TB + 2, pal.dim, 0);
|
||
|
|
zh_draw_string(&g_fb, "类型", lx + lw - 64, cy + FM_TB + 2, pal.dim, 0);
|
||
|
|
du_divider_h(&g_fb, lx, ly, lw, pal.line);
|
||
|
|
|
||
|
|
if (s->count == 0) {
|
||
|
|
const char *empty = s->status[0] ? s->status : "此文件夹为空";
|
||
|
|
zh_draw_string(&g_fb, empty, lx + 16, ly + 12, pal.dim, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
for (int i = 0; i < vis; i++) {
|
||
|
|
int idx = s->scroll + i;
|
||
|
|
int y, name_w;
|
||
|
|
char shown[FM_NAME];
|
||
|
|
char sz[16];
|
||
|
|
if (idx >= s->count) break;
|
||
|
|
y = ly + i * FM_ROW;
|
||
|
|
if (idx == s->sel)
|
||
|
|
du_fill_rect(&g_fb, lx, y, lw - 10, FM_ROW, pal.sel);
|
||
|
|
if (s->ents[idx].is_dir) {
|
||
|
|
du_fill_rounded_rect(&g_fb, lx + 8, y + 6, 14, 10, pal.folder, 2);
|
||
|
|
} else {
|
||
|
|
du_fill_rect(&g_fb, lx + 8, y + 5, 12, 14, pal.line);
|
||
|
|
du_rect_outline(&g_fb, lx + 8, y + 5, 12, 14, pal.dim, 1);
|
||
|
|
}
|
||
|
|
name_w = lw - 28 - 160;
|
||
|
|
if (name_w < 40) name_w = 40;
|
||
|
|
fm_fit(shown, FM_NAME, s->ents[idx].name, name_w);
|
||
|
|
zh_draw_string(&g_fb, shown, lx + 28, y + 3, pal.text, 0);
|
||
|
|
if (!s->ents[idx].is_dir && !s->ents[idx].is_up) {
|
||
|
|
fm_size_str(sz, s->ents[idx].size);
|
||
|
|
zh_draw_string(&g_fb, sz, lx + lw - 150, y + 3, pal.dim, 0);
|
||
|
|
}
|
||
|
|
zh_draw_string(&g_fb, fm_kind(&s->ents[idx]), lx + lw - 64, y + 3, pal.dim, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (s->count > vis && lh > 0) {
|
||
|
|
int max_scroll = s->count - vis;
|
||
|
|
int thumb_h = lh * vis / s->count;
|
||
|
|
int thumb_y;
|
||
|
|
if (thumb_h < 16) thumb_h = 16;
|
||
|
|
if (thumb_h > lh) thumb_h = lh;
|
||
|
|
thumb_y = ly;
|
||
|
|
if (max_scroll > 0 && lh > thumb_h)
|
||
|
|
thumb_y = ly + (lh - thumb_h) * s->scroll / max_scroll;
|
||
|
|
du_fill_rect(&g_fb, lx + lw - 8, ly, 6, lh, pal.line);
|
||
|
|
du_fill_rounded_rect(&g_fb, lx + lw - 8, thumb_y, 6, thumb_h, pal.dim, 2);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 状态栏 */
|
||
|
|
du_fill_rect(&g_fb, cx, cy + ch - FM_ST, cw, FM_ST, pal.bar);
|
||
|
|
du_divider_h(&g_fb, cx, cy + ch - FM_ST, cw, pal.line);
|
||
|
|
zh_draw_string(&g_fb, s->status, cx + 10, cy + ch - FM_ST + 2, pal.dim, 0);
|
||
|
|
}
|