feat(launcher): 主界面 MD3 化双布局 (agent-201, 用户定案) — 卡片宫格(2列223x112圆角16+按压态)/大图标列表双布局, ui.home_layout=card|list 设置切换; app.json icon 加载缓存+首字母占位; 图片背景 cover 居中裁剪+明暗叠加层(display.bg_overlay_alpha 默认128,预渲染快照不每帧缩放); 全 MD3 语义色零硬编码; 标题接 font.size.title

launcher 3c7c2ec7→4fe4103a; mipsel 0 warning; 宿主纯逻辑测试(几何/cover/首字母/截断) ALL PASS; 真机待验观感
This commit is contained in:
MeowOS committed 2026-08-31 06:05:33 +08:00
1 parent c96e912518
commit 955dbe1228
4 files changed
+476 -52

No files matched your search

+467 -52
View File
@@ -81,16 +81,20 @@ static void menu_after_app_exit(int app_ran)
* (draw_clock) never redraws or tears the list. draw_menu fills the whole
* background then calls draw_header_content. The menu is pre-drawn before
* fb_norm_buf0 so the initial pan shows a complete frame. */
static int menu_title_size(void); /* 前向声明:draw_header_content 用(定义在下方布局帮助块) */
static void draw_header_content(void)
{
draw_statusbar();
/* M2.6 标题真字体(28px;回退点阵3x) */
const meow_ui_theme_t *th = s_ui_theme();
int tsize = menu_title_size(); /* 标题字号接 font.size.title */
/* M2.6 标题真字体(字号接 font.size.title;回退点阵3x);MD3 on_surface 主文字色 */
if (mfont_ready) {
meow_font_drawtext((uint16_t *)fb, 24, 48, "MeowOS 应用", rgb565(0xFFE066), 28);
fill_rect(0, 48 + meow_font_line_height(28) + 2, fb_xres, 2, 0x3A3A5C);
meow_font_drawtext((uint16_t *)fb, 24, 48, "MeowOS 应用", rgb565(th->on_surface), tsize);
fill_rect(0, 48 + meow_font_line_height(tsize) + 2, fb_xres, 2, th->outline);
} else {
draw_text(24, 28, "MEOWAPPS", 3, 0xFFE066);
fill_rect(0, 56, fb_xres, 2, 0x3A3A5C);
draw_text(24, 28, "MEOWAPPS", 3, th->on_surface);
fill_rect(0, 56, fb_xres, 2, th->outline);
}
}
@@ -127,71 +131,450 @@ void s_draw_chevron_left(int x, int y, uint32_t color)
}
/* ================================================================== */
/* 主界面 MD3 双布局(卡片宫格 card / 列表 list)+ 应用图标 + 背景 */
/* 布局切换:设置键 ui.home_layout(card=0 默认 / list=1)。 */
/* 图标:读 app.json icon(相对应用目录/绝对),stb_image 解码一次进 */
/* 缓存(menu_icons_load);缺失/失败 → 主题色圆角块+首字母。 */
/* 背景:display.background(纯色名 或 图片路径 MeowAPPs/backgrounds/xx.png)*/
/* 图片 → cover 居中裁剪铺底 + 叠加层(深色叠黑/浅色叠白, */
/* 强度 display.bg_overlay_alpha 缺省 128)。 */
/* 观感:MD3 语义色全程(s_ui_theme()),无散落硬编码;标题字号接 */
/* font.size.title。状态栏由 SDK 全局绘制(不在此改动)。 */
/* ================================================================== */
/* 布局几何常量(480×720;app 区在标题栏之下、设置行之上) */
#define MENU_GRID_PAD 12 /* 宫格卡片外边距 */
#define MENU_GRID_GAP 10 /* 宫格卡片间隙 */
#define MENU_GRID_COLS 2
#define MENU_GRID_CARD_H 112 /* 宫格卡片高 */
#define MENU_GRID_RADIUS 16 /* 卡片圆角 */
#define MENU_GRID_ICON 44 /* 宫格图标 ink 高(方) */
#define MENU_LIST_ROW_H 60 /* 列表行高(大图标行) */
#define MENU_LIST_ICON 40 /* 列表图标 ink 高(方) */
#define MENU_LIST_ICON_X 24 /* 列表图标左缘 */
/* 应用图标缓存(进主界面加载一次,不每帧解码;index 对齐 apps[]) */
static meow_img *s_app_icons[MAX_APPS];
/* 当前按压命中的应用格(-1=无);触摸按下置位、抬起清除(按压态视觉) */
static int g_menu_pressed = -1;
/* 背景状态(display.background:纯色名 or 图片路径) */
static meow_img *s_bg_img = NULL; /* 解码后的背景图(缓存,NULL=纯色) */
static int s_bg_is_img = 0; /* 1=图片背景 */
static int s_bg_overlay = 128; /* 叠加层强度 0-255 */
static uint16_t *s_bg_cache = NULL; /* cover+overlay 后的全屏 RGB565 快照 */
static int s_bg_cache_w = 0, s_bg_cache_h = 0;
static unsigned s_bg_sig = 0; /* 背景设置签名(变化才重建缓存) */
static int s_bg_ready = 0; /* 背景是否已初始化(首次必然构建,防签名 0 碰撞) */
/* 当前主界面布局:0=card(默认) 1=list */
static int menu_layout(void)
{
int v = 0;
if (meow_settings_get_enum("ui.home_layout", &v) != 0) v = 0;
return v ? 1 : 0;
}
/* 标题字号(读 font.size.title,钳制到设置页取值域 16-40) */
static int menu_title_size(void)
{
int t = 28;
if (meow_settings_get_int("font.size.title", &t) != 0) t = 28;
if (t < 16) t = 16;
if (t > 40) t = 40;
return t;
}
/* 应用区顶缘:标题基线(y=48) + 标题行高 + 分隔线下方留白,且不小于 ROW_TOP。
* 大标题(字号>36)时下移,避免与首行卡片重叠。 */
static int menu_apps_top(void)
{
int t = ROW_TOP;
if (mfont_ready) {
int d = 48 + meow_font_line_height(menu_title_size()) + 2 + 10;
if (d > t) t = d;
}
return t;
}
/* 返回足够对比的前景色(YIQ 亮度阈值 140),用于主题色底上的文字/字母。 */
static uint32_t menu_contrast(uint32_t c)
{
int r = (c >> 16) & 0xFF, g = (c >> 8) & 0xFF, b = c & 0xFF;
long y = (r * 299 + g * 587 + b * 114) / 1000;
return y >= 140 ? 0x000000 : 0xFFFFFF;
}
/* 按 on_surface 亮度判定当前主题是否深色(浅色主题 on_surface 为深色小值)。
* 用于图片背景叠加层配色:深色叠黑 / 浅色叠白(用户定案,固定跟明暗主题)。 */
static int menu_is_dark(const meow_ui_theme_t *th)
{
return th ? (th->on_surface > 0x808080) : 1;
}
/* 背景设置签名:display.background 串 + 叠加层强度 + 明暗。变化才重建缓存。 */
static unsigned menu_bg_sig(const meow_ui_theme_t *th, int ov)
{
char bg[MEOW_SET_MAX_LEN];
if (meow_settings_get_str("display.background", bg, sizeof bg) != 0) bg[0] = 0;
unsigned h = 2166136261u;
for (const char *p = bg; *p; p++) { h ^= (unsigned char)*p; h *= 16777619u; }
h = h * 31u + (unsigned)ov;
h = h * 31u + (unsigned)menu_is_dark(th);
return h;
}
/* 解码后背景图路径解析:绝对/相对卡根(MeowOS 约定图片放 MeowOS/opt 或卡根,
* 计划定案为 MeowAPPs/backgrounds/xx.png → 卡根相对)。 */
static meow_img *menu_bg_load_img(const char *bg)
{
char path[320];
if (bg[0] == '/') snprintf(path, sizeof path, "%s", bg);
else snprintf(path, sizeof path, "/usr/data/mnt/sd_0/%s", bg);
return meow_img_load_file(path);
}
/* 重建背景缓存:铺基色 → cover 居中裁剪图片 → 叠加层 → 快照 480×720 到 s_bg_cache。 */
static void menu_bg_cache_build(const meow_ui_theme_t *th)
{
int W = fb_xres, H = fb_yres;
fill_rect(0, 0, W, H, menu_bg);
if (!s_bg_is_img || !s_bg_img) { free(s_bg_cache); s_bg_cache = NULL; return; }
int iw = meow_img_w(s_bg_img), ih = meow_img_h(s_bg_img);
if (iw <= 0 || ih <= 0) { free(s_bg_cache); s_bg_cache = NULL; return; }
/* cover:整数定点求 scale=max(W/iw,H/ih),居中裁剪;dw/dh 向上取整保证铺满(不露边) */
long long sw = (long long)W * 65536 / iw;
long long sh = (long long)H * 65536 / ih;
long long s = sw > sh ? sw : sh;
int dw = (int)(((long long)iw * s + 65535) / 65536);
int dh = (int)(((long long)ih * s + 65535) / 65536);
int ox = (dw - W) / 2, oy = (dh - H) / 2;
if (dw > 0 && dh > 0) meow_ui_img_scaled(-ox, -oy, dw, dh, s_bg_img);
/* 叠加层(深色叠黑/浅色叠白,强度 s_bg_overlay) */
int ov = s_bg_overlay;
if (ov < 0) ov = 0; if (ov > 255) ov = 255;
if (ov > 0) meow_ui_fill_rect_alpha(0, 0, W, H, menu_is_dark(th) ? 0x000000 : 0xFFFFFF, ov);
/* 快照全屏 565 到缓存(仅支持 16bpp;32bpp 回退纯色) */
int pitch = meow_ui_back_buf_pitch();
if (meow_ui_back_buf_bpp() != 16) { free(s_bg_cache); s_bg_cache = NULL; return; }
if (s_bg_cache && (s_bg_cache_w != W || s_bg_cache_h != H)) { free(s_bg_cache); s_bg_cache = NULL; }
if (!s_bg_cache) {
s_bg_cache = (uint16_t *)malloc((size_t)W * H * 2);
if (!s_bg_cache) return;
s_bg_cache_w = W; s_bg_cache_h = H;
}
const unsigned char *bb = meow_ui_back_buf();
for (int y = 0; y < H; y++)
memcpy(s_bg_cache + (size_t)y * W, bb + (size_t)y * pitch, (size_t)W * 2);
}
/* 背景准备:读设置、检测变化、重载图片/重建缓存。draw_menu 每次调用(变化才重建)。 */
static void menu_bg_prepare(const meow_ui_theme_t *th)
{
int ov = 128;
if (meow_settings_get_int("display.bg_overlay_alpha", &ov) != 0) ov = 128;
if (ov < 0) ov = 0; if (ov > 255) ov = 255;
unsigned sig = menu_bg_sig(th, ov);
if (s_bg_ready && sig == s_bg_sig) { if (ov != s_bg_overlay) s_bg_overlay = ov; return; }
s_bg_sig = sig;
s_bg_overlay = ov;
s_bg_ready = 1;
char bg[MEOW_SET_MAX_LEN];
if (meow_settings_get_str("display.background", bg, sizeof bg) != 0) bg[0] = 0;
if (s_bg_img) { meow_img_free(s_bg_img); s_bg_img = NULL; }
s_bg_is_img = 0;
menu_bg = 0x000000;
if (strchr(bg, '/')) { /* 含路径分隔符 → 图片路径 */
/* 基色用主题 surface:图片透明区/加载失败时回退,避免生硬黑底 */
menu_bg = th->surface;
s_bg_img = menu_bg_load_img(bg);
if (s_bg_img) s_bg_is_img = 1;
} else { /* 纯色名(沿用既有 black/blue/green 语义) */
if (!strcmp(bg, "blue")) menu_bg = 0x0A1428; /* 深蓝 */
else if (!strcmp(bg, "green")) menu_bg = 0x0A2814; /* 深绿 */
else menu_bg = 0x000000; /* black(默认) */
}
menu_bg_cache_build(th);
}
/* 背景绘制:图片缓存 blit(快)或纯色填充。 */
static void menu_bg_paint(void)
{
int W = fb_xres, H = fb_yres;
if (s_bg_is_img && s_bg_img && s_bg_cache) {
int pitch = meow_ui_back_buf_pitch();
for (int y = 0; y < H; y++)
memcpy(fb + (size_t)y * pitch, s_bg_cache + (size_t)y * W, (size_t)W * 2);
} else {
fill_rect(0, 0, W, H, menu_bg);
}
}
/* 应用图标路径:app.json icon 相对应用目录(dirname(exec))/ 绝对;失败回退卡根相对。 */
static void menu_icon_path(const struct app *a, char *out, int olen)
{
if (a->icon[0] == '/') { snprintf(out, olen, "%s", a->icon); return; }
const char *sl = strrchr(a->exec, '/');
if (sl && a->icon[0]) {
int n = (int)(sl - a->exec);
char dir[180];
if (n >= (int)sizeof dir) n = (int)sizeof dir - 1;
memcpy(dir, a->exec, (size_t)n); dir[n] = 0;
snprintf(out, olen, "%s/%s", dir, a->icon);
} else {
snprintf(out, olen, "/usr/data/mnt/sd_0/%s", a->icon);
}
}
/* 加载单个应用图标(解码一次进缓存);失败返回 NULL(调用方回落首字母占位)。 */
static meow_img *menu_app_icon_load(const struct app *a)
{
if (!a->icon[0]) return NULL;
char p[220];
menu_icon_path(a, p, sizeof p);
meow_img *img = meow_img_load_file(p);
if (!img && a->icon[0] != '/') { /* 应用目录相对失败 → 回退卡根相对 */
snprintf(p, sizeof p, "/usr/data/mnt/sd_0/%s", a->icon);
img = meow_img_load_file(p);
}
return img;
}
/* 进主界面/热重载:批量加载应用图标(一次,不每帧解码;先释放旧图防泄漏)。 */
static void menu_icons_load(struct app *apps, int napps)
{
for (int i = 0; i < MAX_APPS; i++)
if (s_app_icons[i]) { meow_img_free(s_app_icons[i]); s_app_icons[i] = NULL; }
for (int i = 0; i < napps; i++) s_app_icons[i] = menu_app_icon_load(&apps[i]);
}
/* 主界面退出:释放图标缓存。 */
static void menu_icons_free(void)
{
for (int i = 0; i < MAX_APPS; i++)
if (s_app_icons[i]) { meow_img_free(s_app_icons[i]); s_app_icons[i] = NULL; }
g_menu_pressed = -1;
}
/* 首字符(UTF-8 整码,中文取第一字) */
static void menu_first_glyph(const char *nm, char out[4])
{
out[0] = out[1] = out[2] = out[3] = 0;
if (!nm || !nm[0]) { out[0] = '?'; return; }
char c0 = nm[0];
out[0] = c0;
if ((unsigned char)c0 >= 0x80 && nm[1]) {
out[1] = nm[1];
if ((unsigned char)c0 >= 0xE0 && nm[2]) out[2] = nm[2];
}
}
/* 名称截断(按可用像素宽;超出加 '…')。 */
static void menu_name_fit(const char *name, int maxw, int size, char *out, int olen)
{
char buf[64];
snprintf(buf, sizeof buf, "%s", name);
if (ui_width(buf, size) <= maxw) { snprintf(out, olen, "%s", buf); return; }
int n = (int)strlen(buf);
while (n > 0 && ui_width(buf, size) > maxw) {
/* 回退一个 UTF-8 字符边界 */
do { buf[--n] = 0; } while (n > 0 && ((unsigned char)buf[n] & 0xC0) == 0x80);
}
snprintf(out, olen, "%s...", buf);
}
/* 图标绘制:有图 meow_ui_img_cover(居中裁方);无图 → 主题色圆角块 + 首字母。 */
static void menu_draw_icon(const struct app *a, meow_img *icon, int x, int y, int sz,
const meow_ui_theme_t *th)
{
if (icon) { meow_ui_img_cover(x, y, sz, icon); return; }
meow_gfx_fill_round(x, y, sz, sz, sz * 3 / 10, th->primary);
char g[4];
menu_first_glyph(a->name, g);
uint32_t fg = menu_contrast(th->primary);
int cw = ui_width(g, 20);
ui_draw(x + sz / 2 - cw / 2, y + sz / 2 - meow_font_line_height(20) / 2, g, fg, 20);
}
/* 应用格几何(grid/list 统一入口;供 draw 与 touch 命中共用)。 */
static void menu_cell_rect(int i, int layout, int apps_top,
int *cx, int *cy, int *cw, int *ch)
{
if (layout == 0) { /* card grid */
int row = i / MENU_GRID_COLS, col = i % MENU_GRID_COLS;
*cw = (fb_xres - 2 * MENU_GRID_PAD - (MENU_GRID_COLS - 1) * MENU_GRID_GAP) / MENU_GRID_COLS;
*ch = MENU_GRID_CARD_H;
*cx = MENU_GRID_PAD + col * (*cw + MENU_GRID_GAP);
*cy = apps_top + row * (*ch + MENU_GRID_GAP);
} else { /* list */
*cw = fb_xres - 16;
*ch = MENU_LIST_ROW_H;
*cx = 8;
*cy = apps_top + i * MENU_LIST_ROW_H;
}
}
/* 触摸命中应用格;未命中返回 -1。 */
static int menu_hit_app(int x, int y, struct app *apps, int napps, int state,
int layout, int apps_top)
{
if (state != 0 || napps <= 0) return -1;
for (int i = 0; i < napps; i++) {
int cx, cy, cw, ch;
menu_cell_rect(i, layout, apps_top, &cx, &cy, &cw, &ch);
if (x >= cx && x < cx + cw && y >= cy && y < cy + ch) return i;
}
return -1;
}
/* 宫格卡片:surface_container 底 + 圆角 + 选中/按压态 tonal 高亮 + 图标 + 名称。 */
static void menu_draw_grid_cell(const struct app *a, meow_img *icon, int x, int y,
int w, int h, int sel, int pressed,
const meow_ui_theme_t *th)
{
meow_gfx_fill_round(x, y, w, h, MENU_GRID_RADIUS, th->surface_container);
if (sel || pressed) {
int alpha = pressed ? 72 : 40; /* MD3 状态层:按压更浓 */
meow_gfx_fill_round_alpha(x, y, w, h, MENU_GRID_RADIUS, th->primary, alpha);
}
int sz = (h > MENU_GRID_ICON + 40) ? MENU_GRID_ICON : h - 40;
int icx = x + (w - sz) / 2, icy = y + 12;
menu_draw_icon(a, icon, icx, icy, sz, th);
char nm[40];
menu_name_fit(a->name, w - 14, 18, nm, sizeof nm);
int name_y = y + 12 + sz + 10;
int nw = ui_width(nm, 18);
ui_draw(x + (w - nw) / 2, name_y, nm, a->ok ? th->on_surface : th->error, 18);
if (!a->ok) {
int ew = ui_width("[缺失]", 14);
ui_draw(x + (w - ew) / 2, name_y + meow_font_line_height(18) + 2, "[缺失]", th->error, 14);
}
}
/* 列表行:图标 + 名称 + 副标题(package/id)+ 选中/按压 tonal 高亮。 */
static void menu_draw_list_cell(const struct app *a, meow_img *icon, int x, int y,
int w, int h, int sel, int pressed,
const meow_ui_theme_t *th)
{
meow_gfx_fill_round(x, y, w, h, 12, th->surface_container);
if (sel || pressed) {
int alpha = pressed ? 72 : 40;
meow_gfx_fill_round_alpha(x, y, w, h, 12, th->primary, alpha);
}
int sz = MENU_LIST_ICON;
int icy = y + (h - sz) / 2;
menu_draw_icon(a, icon, x + MENU_LIST_ICON_X, icy, sz, th);
int tx = x + MENU_LIST_ICON_X + sz + 14;
char nm[40];
menu_name_fit(a->name, w - (tx - x) - 12, 20, nm, sizeof nm);
int name_y, sub_y;
if (mfont_ready) {
int lh20 = meow_font_line_height(20), lh14 = meow_font_line_height(14);
name_y = y + (h - (lh20 + 4 + lh14)) / 2;
sub_y = name_y + lh20 + 4;
} else {
name_y = y + 8;
sub_y = y + 30;
}
ui_draw(tx, name_y, nm, a->ok ? th->on_surface : th->error, 20);
/* 副标题:package(无则 id),on_surface_variant */
char sub[40];
snprintf(sub, sizeof sub, "%s", a->package[0] ? a->package : (a->id[0] ? a->id : ""));
if (sub[0]) {
char subf[44];
menu_name_fit(sub, w - (tx - x) - 12, 14, subf, sizeof subf);
ui_draw(tx, sub_y, subf, th->on_surface_variant, 14);
}
}
/* 应用格绘制入口:按布局分派。 */
static void menu_draw_app_cell(const struct app *a, meow_img *icon, int x, int y,
int w, int h, int sel, int pressed, int layout,
const meow_ui_theme_t *th)
{
if (layout == 0) menu_draw_grid_cell(a, icon, x, y, w, h, sel, pressed, th);
else menu_draw_list_cell(a, icon, x, y, w, h, sel, pressed, th);
}
static void draw_menu(struct app apps[MAX_APPS], int napps, int state,
int cursor, int toast_ms, const char *toast)
{
fill_rect(0, 0, fb_xres, fb_yres, menu_bg);
const meow_ui_theme_t *th = s_ui_theme();
int layout = menu_layout();
int apps_top = menu_apps_top();
int bsize = 20;
meow_settings_get_int("font.size.body", &bsize);
if (bsize < 12) bsize = 12;
if (bsize > 28) bsize = 28;
/* 背景(纯色 or 图片 cover+overlay;缓存变化才重建,不每帧解码) */
menu_bg_prepare(th);
menu_bg_paint();
draw_header_content();
char line[64];
int y = ROW_TOP;
if (state == 1) {
if (mfont_ready) { ui_draw(30, 200, "没有存储卡", 0xFFFFFF, 20); ui_draw(30, 234, "请插入TF卡后重开菜单", 0x9FB8D9, 20); }
else { draw_text(30, 200, "NO STORAGE CARD", 2, 0xFFFFFF); draw_text(30, 240, "INSERT TF CARD", 2, 0x9FB8D9); draw_text(30, 272, "THEN RE-OPEN MENU", 2, 0x9FB8D9); }
if (mfont_ready) {
ui_draw(30, 200, "没有存储卡", th->on_surface, bsize);
ui_draw(30, 234, "请插入TF卡后重开菜单", th->on_surface_variant, bsize);
} else {
draw_text(30, 200, "NO STORAGE CARD", 2, th->on_surface);
draw_text(30, 240, "INSERT TF CARD", 2, th->on_surface_variant);
draw_text(30, 272, "THEN RE-OPEN MENU", 2, th->on_surface_variant);
}
} else if (state == 2 || napps == 0) {
if (mfont_ready) { ui_draw(30, 200, "没有找到应用", 0xFFFFFF, 20); ui_draw(30, 234, "请放入应用到", 0x9FB8D9, 20); ui_draw(30, 268, "MeowAPPs/<id>/app.json", 0x9FB8D9, 20); }
else { draw_text(30, 200, "NO APPS FOUND", 2, 0xFFFFFF); draw_text(30, 240, "PUT APP IN", 2, 0x9FB8D9); draw_text(30, 272, "MeowAPPs/<id>/app.json", 2, 0x9FB8D9); }
if (mfont_ready) {
ui_draw(30, 200, "没有找到应用", th->on_surface, bsize);
ui_draw(30, 234, "请放入应用到", th->on_surface_variant, bsize);
ui_draw(30, 268, "MeowAPPs/<id>/app.json", th->on_surface_variant, bsize);
} else {
draw_text(30, 200, "NO APPS FOUND", 2, th->on_surface);
draw_text(30, 240, "PUT APP IN", 2, th->on_surface_variant);
draw_text(30, 272, "MeowAPPs/<id>/app.json", 2, th->on_surface_variant);
}
} else {
for (int i = 0; i < napps; i++) {
int sel = (i == cursor);
if (sel) {
fill_rect(8, y - 4, fb_xres - 16, ROW_H - 8, 0x2A3A6A);
snprintf(line, sizeof line, "%d %s", i + 1, apps[i].name);
ui_draw(24, y, line, 0xFFFFFF, 20);
if (!apps[i].ok) ui_draw(24, y + ROW_H - 24, "[缺失]", 0xFF6B6B, 20);
} else {
snprintf(line, sizeof line, "%d %s", i + 1, apps[i].name);
ui_draw(24, y, line, 0xC8D0E8, 20);
if (!apps[i].ok) ui_draw(24, y + ROW_H - 24, "[缺失]", 0x8A4A4A, 20);
}
y += ROW_H;
int cx, cy, cw, ch;
menu_cell_rect(i, layout, apps_top, &cx, &cy, &cw, &ch);
menu_draw_app_cell(&apps[i], (i < MAX_APPS) ? s_app_icons[i] : NULL,
cx, cy, cw, ch, i == cursor, g_menu_pressed == i,
layout, th);
}
}
/* M2.36 B2b:系统设置 builtin 行(过渡入口)。位于应用项之后、Exit 行之前;
* 不依赖 .mpk(图标/文案就地绘制)。音量导航可把光标移到此处(cursor==napps),
* 触摸点击该行进入设置页(run_menu 分发到 run_settings_page)。 */
* 音量导航可把光标移到此处(cursor==napps),触摸点击该行进入设置页。 */
{
int sy = EXIT_ROW - ROW_H; /* 固定在 Exit 行上方 588 */
int selset = (cursor == napps); /* 光标已移到设置行 */
fill_rect(8, sy - 4, fb_xres - 16, ROW_H - 8, selset ? 0x2A3A6A : 0x12142A);
if (mfont_ready) ui_draw(24, sy, "系统设置", selset ? 0xFFFFFF : 0xC8D0E8, 20);
else draw_text(24, sy, "SETTINGS", 2, selset ? 0xFFFFFF : 0xC8D0E8);
s_draw_chevron(fb_xres - 22, sy + ROW_H / 2, 0x9FB8D9); /* 右向进入提示 */
meow_gfx_fill_round(8, sy - 4, fb_xres - 16, ROW_H - 8, 12, th->surface_container);
if (selset) meow_gfx_fill_round_alpha(8, sy - 4, fb_xres - 16, ROW_H - 8, 12, th->primary, 40);
if (mfont_ready) ui_draw(24, sy, "系统设置", th->on_surface, bsize);
else draw_text(24, sy, "SETTINGS", 2, th->on_surface);
s_draw_chevron(fb_xres - 22, sy + ROW_H / 2, th->on_surface_variant);
}
/* exit row */
fill_rect(8, EXIT_ROW - 4, fb_xres - 16, ROW_H - 8, 0x1A1A3A);
meow_gfx_fill_round(8, EXIT_ROW - 4, fb_xres - 16, ROW_H - 8, 12, th->surface_container);
/* M2.2/M2.6:MeowOS 常驻主界面(播放器已死)时改「重启返回播放器」。 */
if (mfont_ready) ui_draw(24, EXIT_ROW, meow_mode ? "重启返回播放器" : "返回播放器", 0x8FBFD9, 20);
else draw_text(24, EXIT_ROW, meow_mode ? "REBOOT TO PLAYER" : "BACK TO PLAYER", 2, 0x8FBFD9);
if (mfont_ready) ui_draw(24, EXIT_ROW, meow_mode ? "重启返回播放器" : "返回播放器", th->on_surface, bsize);
else draw_text(24, EXIT_ROW, meow_mode ? "REBOOT TO PLAYER" : "BACK TO PLAYER", 2, th->on_surface);
/* M2.18 Bug 3: 底部对齐,弃用硬编码 y=700。屏幕高 fb_yres=720,字号 20 真实行高
* meow_font_line_height(20)=27(ascent 21 + descent 6);原 y=700 使字底 = 700+27 = 727
* > 720,溢出 7px ≈ 1/5 字符高被裁。改为 y = fb_yres - 行高 - 底部留白。非 mfont 回退
* 分支(5x7 点阵,字高=7*scale)同样底部对齐,两分支都处理。 */
/* M2.18 Bug 3: 底部对齐,弃用硬编码 y=700。字号 20 真实行高 meow_font_line_height(20)。
* 非 mfont 回退分支(5x7 点阵,字高=7*scale)同样底部对齐,两分支都处理。 */
#define FOOTY_PAD 8
/* dev version, bottom-right: single source MEOWOS_VERSION. */
{
char ver[32];
snprintf(ver, sizeof ver, "MeowOS v%s dev", g_meowos_version);
int foot_y = fb_yres - meow_font_line_height(20) - FOOTY_PAD;
ui_draw(fb_xres - 8 - ui_width(ver, 20), foot_y, ver, 0x666688, 20);
int foot_y = fb_yres - meow_font_line_height(bsize) - FOOTY_PAD;
ui_draw(fb_xres - 8 - ui_width(ver, bsize), foot_y, ver, th->on_surface_variant, bsize);
}
if (toast_ms > 0 && toast && toast[0]) {
fill_rect(20, TOAST_Y - 6, fb_xres - 40, 40, 0x2A0A0A);
ui_draw(30, TOAST_Y, toast, 0xFF8080, 20);
fill_rect(20, TOAST_Y - 6, fb_xres - 40, 40, th->surface_variant);
ui_draw(30, TOAST_Y, toast, th->error, bsize);
}
}
@@ -318,6 +701,7 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
unsigned long long apps_sig = 0; /* M2.77:应用目录签名(0=未初始化,首圈建立基线) */
load_config(); /* meow.conf on the card: bg_color etc. */
menu_icons_load(apps, napps); /* MD3:应用图标解码一次进缓存(不每帧解码) */
/* A-1/A-2:launcher 用 SDK 渲染栈 —— 初始化 SDK(自建 back_buf/读几何/载字库),
* 并把刷新率档位/恒满速开关交给 SDK present 帧率门控(去中心化:launcher 不再自带一套)。
@@ -519,6 +903,7 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
if (fstate == 0 && fn > 0) {
for (int i = 0; i < fn; i++) apps[i] = fresh[i];
napps = fn; state = 0;
menu_icons_load(apps, napps); /* MD3:图标缓存随重扫刷新 */
logmsg("hotplug: card ready -> %d apps loaded", napps);
dirty = 1;
} else if (fstate != state) {
@@ -541,6 +926,7 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
memcpy(apps, fresh, sizeof fresh);
napps = fn; state = 0;
if (cursor >= napps) cursor = napps - 1;
menu_icons_load(apps, napps); /* MD3:图标缓存随热重载刷新 */
dirty = 1;
logmsg("hot-reload: MeowAPPs changed -> %d apps", napps);
} else if (fstate != state) {
@@ -738,6 +1124,16 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
* 杜绝把上一手势残留(-1/旧坐标)当本次手势起点。 */
if (t_down && !t_down_valid && t_last_x >= 0 && t_last_y >= 0) {
t_down_x = t_last_x; t_down_y = t_last_y; t_down_valid = 1;
/* MD3 按压态:锁定原点后立即判定命中的应用格(按下即显示高亮) */
g_menu_pressed = menu_hit_app(t_last_x, t_last_y, apps, napps, state,
menu_layout(), menu_apps_top());
if (g_menu_pressed >= 0) {
int cx, cy, cw, ch;
menu_cell_rect(g_menu_pressed, menu_layout(), menu_apps_top(),
&cx, &cy, &cw, &ch);
meow_dirty_add(cx, cy, cw, ch);
need_menu_paint = 1;
}
}
/* ⑨ 顶部热区「跟手下拉」打开通知中心(拖动中即进,真跟手):
* 原点锁定 + 起点在顶部热区(y<SB_BAR_H) + 下拉达 NCP_DRAG_ENTER_SLOP →
@@ -815,6 +1211,15 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
int dx = t_last_x - tx, dy = t_last_y - ty;
int adx = dx < 0 ? -dx : dx, ady = dy < 0 ? -dy : dy;
t_down_valid = 0;
/* MD3 按压态:抬手清除按压高亮(无论 tap/滑动,先回退到常态) */
if (g_menu_pressed >= 0) {
int cx, cy, cw, ch;
menu_cell_rect(g_menu_pressed, menu_layout(), menu_apps_top(),
&cx, &cy, &cw, &ch);
meow_dirty_add(cx, cy, cw, ch);
g_menu_pressed = -1;
need_menu_paint = 1;
}
logmsg("menu tap at %d,%d (up %d,%d, d=(%d,%d))", tx, ty,
t_last_x, t_last_y, dx, dy);
/* 阶段B:顶部热区下滑打开通知中心。判定在「滑动」分支内先于左缘返回——
@@ -902,10 +1307,12 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
* 应用行动作非互斥。应用很多(napps*ROW_H 上探到 EXIT 带)时,
* 一个 tap 会同时命中退出行(<~684)与应用行带,触发确认重启又启动
* 应用(或直接 break 走人)→ 概率乱跳。退出行优先,命中即不再
* 落到应用行。普通小列表两区间不重叠,行为不变。 */
else if (ty >= ROW_TOP && ty < ROW_TOP + napps * ROW_H && napps > 0 && state == 0) {
int idx = (ty - ROW_TOP) / ROW_H;
if (idx < napps) {
* 落到应用行。普通小列表两区间不重叠,行为不变。
* MD3 双布局:应用格用 menu_hit_app(按 ui.home_layout 几何命中)。 */
else {
int idx = menu_hit_app(tx, ty, apps, napps, state,
menu_layout(), menu_apps_top());
if (idx >= 0) {
logmsg("menu: touch row %d", idx);
int ret = launch_app(&apps[idx]);
/* app exited: return to menu (v6.3).
@@ -987,15 +1394,22 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
* Stage-One 状态栏收敛:音量渐变/时钟只经 paint_sb_right/draw_clock → sb_sdk_refresh()
* 把权威状态喂进 SDK 全局状态栏 + 标脏顶条(0,0,xres,28),由 SDK present() 顶条重绘。 */
if (cursor != last_cursor) {
/* M2.36 B2b:光标可能在应用行(0..napps-1)或「系统设置」行(napps,位于 Exit 行上方)。
* 分别标脏对应行 rect(设置行固定 y=EXIT_ROW-ROW_H,与 napps 无关)。 */
/* M2.36 B2b:光标可能在应用格(0..napps-1)或「系统设置」行(napps,位于 Exit 行上方)。
* 应用格用 menu_cell_rect(按 ui.home_layout 几何)标脏;设置行固定 y=EXIT_ROW-ROW_H。 */
if (cursor == napps || last_cursor == napps)
meow_dirty_add(8, EXIT_ROW - ROW_H - 4, fb_xres - 16, ROW_H - 8);
if (state == 0 && napps > 0) {
if (last_cursor >= 0 && last_cursor < napps)
meow_dirty_add(8, ROW_TOP + last_cursor * ROW_H - 4, fb_xres - 16, ROW_H - 8);
if (cursor >= 0 && cursor < napps)
meow_dirty_add(8, ROW_TOP + cursor * ROW_H - 4, fb_xres - 16, ROW_H - 8);
int layout = menu_layout(), apps_top = menu_apps_top();
if (last_cursor >= 0 && last_cursor < napps) {
int cx, cy, cw, ch;
menu_cell_rect(last_cursor, layout, apps_top, &cx, &cy, &cw, &ch);
meow_dirty_add(cx, cy - 2, cw, ch + 4);
}
if (cursor >= 0 && cursor < napps) {
int cx, cy, cw, ch;
menu_cell_rect(cursor, layout, apps_top, &cx, &cy, &cw, &ch);
meow_dirty_add(cx, cy - 2, cw, ch + 4);
}
}
last_cursor = cursor;
need_menu_paint = 1;
@@ -1036,6 +1450,7 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
usleep(POLL_MS * 1000);
}
menu_icons_free(); /* MD3:主界面退出释放应用图标缓存 */
if (touch_fd >= 0) close(touch_fd);
/* v6.6: restore the player's frame + pan back BEFORE releasing fb (the
* player is event-driven and won't redraw on its own — see §18.15).
Binary file not shown.
+5
View File
@@ -77,6 +77,11 @@ static const meow_setting_def g_defs[] = {
{ "display.theme_dark_start", SET_INT, "0", 0, 1439, NULL, NULL },
{ "display.theme_dark_end", SET_INT, "0", 0, 1439, NULL, NULL },
{ "display.background", SET_STR, "black",0, 0, NULL, NULL },
/* M2.79 主界面背景图片叠加层强度(int 0-255,缺省 128):图片背景上叠 黑(深色)/白(浅色)
* 半透明遮罩,保证控件/文字可读。仅图片背景(display.background=路径)时生效。 */
{ "display.bg_overlay_alpha", SET_INT, "128", 0, 255, NULL, NULL },
/* M2.79 主界面布局卡片宫格/列表(card=0 默认 / list=1),设置页切换写 settings.conf。 */
{ "ui.home_layout", SET_ENUM, "card", 0, 0, "card,list", "卡片宫格,列表" },
/* M2.5x 多字体:1=按字符类别逐字符路由(配合下方 6 类 font.class_*);
* 0=单字体(只走 font.file,零额外内存/性能开销,等价现状)。 */
{ "display.multi_font", SET_INT, "0", 0, 1, NULL, NULL },
+4
View File
@@ -56,6 +56,10 @@ static const srow_t SMENU[] = {
{ "深色时段开始", "display.theme_dark_start", SC_SLIDER, 0,1439,15, NULL,NULL,0,0 },
{ "深色时段结束", "display.theme_dark_end", SC_SLIDER, 0,1439,15, NULL,NULL,0,0 },
{ "背景", "display.background", SC_PRESET, 0,0,0, bg_presets, bg_preset_labels, BG_PRESETS_N,0 },
/* M2.79 主界面背景图片叠加层强度(图片背景上叠 黑/白 半透明遮罩,防文字/控件被图片淹没) */
{ "背景叠加强度", "display.bg_overlay_alpha", SC_SLIDER, 0,255,1, NULL,NULL,0,0 },
/* M2.79 主界面布局:卡片宫格 / 列表(写 ui.home_layout,回主界面即生效) */
{ "主界面布局", "ui.home_layout", SC_ENUM, 0,0,0, NULL,NULL,0,0 },
/* 网络:M2.76 WiFi/蓝牙升级为独立子页面(安卓风)。入口行为「列表行」+右侧 chevron,
* 摘要显示当前状态(s_net_get);点开进入对应子页(顶栏+返回+分组列表),详见
* wifi_page.c / bt_page.c。原来散落的开关/列表/信息行收敛进子页面。 */