diff --git a/patch_v2/meow_launcherd/main_menu.c b/patch_v2/meow_launcherd/main_menu.c index c8b7e64..fd7fd36 100644 --- a/patch_v2/meow_launcherd/main_menu.c +++ b/patch_v2/meow_launcherd/main_menu.c @@ -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//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//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//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//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= 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). diff --git a/patch_v2/meow_launcherd/meow_launcherd b/patch_v2/meow_launcherd/meow_launcherd index 15c4d3d..00da89a 100755 Binary files a/patch_v2/meow_launcherd/meow_launcherd and b/patch_v2/meow_launcherd/meow_launcherd differ diff --git a/patch_v2/meow_launcherd/meow_settings.c b/patch_v2/meow_launcherd/meow_settings.c index 8ad4934..c9cbc43 100644 --- a/patch_v2/meow_launcherd/meow_settings.c +++ b/patch_v2/meow_launcherd/meow_settings.c @@ -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 }, diff --git a/patch_v2/meow_launcherd/settings_page.c b/patch_v2/meow_launcherd/settings_page.c index 5bd612b..1dddd57 100644 --- a/patch_v2/meow_launcherd/settings_page.c +++ b/patch_v2/meow_launcherd/settings_page.c @@ -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。原来散落的开关/列表/信息行收敛进子页面。 */