/* * meow_ui_gfx.c — meow_ui 渲染后端(B1.0+B1.1)。 * * 独立实现:自建 fb 上下文(自 mmap /dev/fb0、自建 back_buf、自实现脏区+present), * 不复用 launcher 的全局 g_dirty/fb(launcher 与 app 是不同进程)。机制参考 * MEOW_APP_SDK.md §1/§2 与 launcher 的 M2.16 无闪烁地基,代码全部独立书写。 * * 抽象:所有绘制目标 = 离屏 back_buf(g_fb);meow_ui_flush() 时单次原子 * vsync+blit 到 fb0(真实设备),或写出 PPM 截图(宿主/qemu 离屏,MEOW_UI_DUMP)。 */ #include "meow_ui.h" #include "meow_ui_internal.h" #include "meow_ui_sys.h" #include "../meow_launcherd/meow_font.h" #include #include #include #include #include #include #include #include #include /* 本设备实测 ioctl 号(勿与通用头交换) */ #define FBIOGET_VSCREENINFO 0x4600 #define FBIOGET_FSCREENINFO 0x4602 #define FBIO_WAITFORVSYNC 0x4620 #define FB_DEV "/dev/fb0" #define MEOW_FONT_PNG_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/regular/font.png" #define MEOW_FONT_IDX_CARD "/usr/data/mnt/sd_0/MeowOS/fonts/default/regular/font.idx" #define MEOW_FONT_PNG_SYS "/usr/share/meowos/font/font.png" #define MEOW_FONT_IDX_SYS "/usr/share/meowos/font/font.idx" /* 默认字库(卡 MeowOS/fonts/default/regular,M2.6 字重结构)与固件内置之外,launcher 会把 * 「当前生效字体」经环境变量下发给 app(见 meow_launcherd.c launch_app / meow_font_effective_paths): * MEOW_FONT_PNG / MEOW_FONT_IDX = 绝对路径;MEOW_FONT_APP = 或 "default"。 * SDK load_font 优先读 env(匹配系统 font.file),无则回退卡默认 → 固件。 */ #define MUI_DEFAULT_W 480 #define MUI_DEFAULT_H 720 /* 脏矩形上限(超出合并为全屏) */ #define MUI_DIRTY_MAX 12 struct mu_rect { int x, y, w, h; }; /* ---- fb 上下文 ---- */ static unsigned char *g_back_buf = NULL; /* 离屏(所有绘制目标) */ static unsigned char *g_fb_mem = NULL; /* 真实 mmap 帧缓冲(present 落点;NULL=离屏) */ static unsigned char *g_fb = NULL; /* 绘制指针 = g_back_buf */ static int g_fb_fd = -1; static int g_xres = MUI_DEFAULT_W, g_yres = MUI_DEFAULT_H; static int g_bpp = 16; static int g_line = 0; /* 每行字节数 */ static int g_init_ok = 0; static int g_mfont_ready = 0; static int g_dirty[/*MUI_DIRTY_MAX*/12][4]; /* [x,y,w,h] */ static int g_ndirty = 0; static int g_dirty_full = 1; static char *g_dump_path = NULL; /* ---- 状态栏「上收出屏只留提示线」(collapsed,基座)---- * g_sbar_collapsed = 目标态(1=上收);g_sbar_collapse_h = 当前实际上收偏移 * (0..MUI_SBAR_H:0=完全展开,MUI_SBAR_H=内容全部上移出屏、仅留提示线)。 * 基座让 meow_ui_statusbar_set_collapsed(on) 立即生效(true→只画提示线/内容上移); * 过渡(每帧插值)留待后续代理或经 meow_ui_statusbar_animate(ms) 启用的简易线性逼近。 * 「收合=只留提示线」占高 = MUI_SBAR_COLLAPSE_H(=MEOW_SBAR_HOTZONE_FS=4,meow_ui_sys.h), * 与 inset(meow_ui_statusbar_height())/顶部下滑热区/收合渲染同口径——replaced MUI_SBAR_HINT_H。 */ static int g_sbar_collapsed = 0; /* 目标:1=上收 */ static int g_sbar_collapse_h = 0; /* 当前上收偏移(0..MUI_SBAR_H) */ static int g_sbar_collapse_anim_ms = 0; /* 过渡时长(0=瞬切);meow_ui_statusbar_animate 设置 */ static int g_sbar_collapse_from_h = 0; /* 本次过渡起点偏移 */ static unsigned long long g_sbar_collapse_start_ms = 0; /* 本次过渡起始(monotonic ms) */ /* ---- 字体档位(MEOW_FONT_TIER,基座存档 + 公共 get/set)---- * launcher 按 app.json "fonts":<档> 注入 MEOW_FONT_TIER=<0|1|2>;SDK 读入并以 * meow_font_set_tier/tier 暴露。按档装载大字库(GBK/Full)由 meow_font_reload_tier() * 在 meow_font.c 实现;meow_font_set_tier() 在档位变化时经 env/settings 路由到该钩子真重载。 */ static int g_font_tier = MEOW_FONT_TIER_COMMON; /* ---- 帧率调度(A-1/A-2:从 launcher M2.16 迁入,刷新率调度归口 SDK)---- * meow_ui_flush()/present() 的 fps 档位与自适应降档 governor。launcher 不再自带 * 一套帧率门控;launcher 与 app 统一走这里 → 全局观感/刷新率一致。 * 设置接口:meow_ui_set_refresh_rate()/meow_ui_set_refresh_always_max()。 */ static int g_refresh_rate_cfg = 60; /* 配置目标档 60/30/15(上限) */ static int g_eff_rate = 60; /* 当前有效帧率(governor 可降档) */ static int g_refresh_always_max = 0; /* 1=恒 max 不降档 */ static unsigned long long g_last_present_ms = 0; static unsigned long long g_frames_presented = 0; static unsigned long long g_present_cost_sum = 0; static int g_present_cost_n = 0; static int g_gov_low = 0; /* 每档上限(refresh_always_max=1 时恒 max;0 时 governor 可在 [15,30,60] 内降/升档)。 */ static int rate_bump(int r) { return (r == 15) ? 30 : (r == 30) ? 60 : 60; } static int rate_drop(int r) { return (r == 60) ? 30 : (r == 30) ? 15 : 15; } /* 自适应降档 governor:统计每帧 present 耗时;连续 12 帧平均超预算 → 降一档; * 连续 4 批(≈48 帧)平均远低于预算 → 升一档。refresh_always_max=1 时恒 max 不降。 */ static void refresh_governor(unsigned long long cost_ms, int period) { if (g_refresh_always_max || g_eff_rate <= 0) return; g_present_cost_sum += cost_ms; g_present_cost_n++; if (g_present_cost_n < 12) return; unsigned long long avg = g_present_cost_sum / (unsigned int)g_present_cost_n; if (avg > (unsigned long long)period) { if (g_eff_rate > 15) g_eff_rate = rate_drop(g_eff_rate); g_gov_low = 0; } else if (avg * 2 < (unsigned long long)period) { if (++g_gov_low >= 4) { int nr = rate_bump(g_eff_rate); if (nr <= g_refresh_rate_cfg) g_eff_rate = nr; g_gov_low = 0; } } else g_gov_low = 0; g_present_cost_sum = 0; g_present_cost_n = 0; } /* ---- 工具 ---- */ static uint64_t now_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u; } uint64_t meow_ui_time_ms(void) { return now_ms(); } static uint16_t rgb565(uint32_t c) { return (uint16_t)(((c >> 8) & 0xF800) | ((c >> 5) & 0x07E0) | ((c >> 3) & 0x001F)); } static uint8_t ex5(uint16_t v) { return (uint8_t)(((v) << 3) | ((v) >> 2)); } /* 5→8 */ static uint8_t ex6(uint16_t v) { return (uint8_t)(((v) << 2) | ((v) >> 4)); } /* 6→8 */ static int rect_hit(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh) { return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by; } /* ================= 脏区追踪 ================= */ static void dirty_full(void) { g_dirty_full = 1; g_ndirty = 0; } static void dirty_reset(void) { g_ndirty = 0; g_dirty_full = 0; } static void dirty_add(int x, int y, int w, int h) { if (g_dirty_full) return; if (w <= 0 || h <= 0) return; if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (x >= g_xres || y >= g_yres) return; if (x + w > g_xres) w = g_xres - x; if (y + h > g_yres) h = g_yres - y; if (w <= 0 || h <= 0) return; for (int i = 0; i < g_ndirty; i++) { if (rect_hit(g_dirty[i][0], g_dirty[i][1], g_dirty[i][2], g_dirty[i][3], x, y, w, h)) { int nx1 = g_dirty[i][0] < x ? g_dirty[i][0] : x; int ny1 = g_dirty[i][1] < y ? g_dirty[i][1] : y; int nx2 = (g_dirty[i][0] + g_dirty[i][2]) > (x + w) ? (g_dirty[i][0] + g_dirty[i][2]) : (x + w); int ny2 = (g_dirty[i][1] + g_dirty[i][3]) > (y + h) ? (g_dirty[i][1] + g_dirty[i][3]) : (y + h); g_dirty[i][0] = nx1; g_dirty[i][1] = ny1; g_dirty[i][2] = nx2 - nx1; g_dirty[i][3] = ny2 - ny1; return; } } if (g_ndirty >= MUI_DIRTY_MAX) { dirty_full(); return; } g_dirty[g_ndirty][0] = x; g_dirty[g_ndirty][1] = y; g_dirty[g_ndirty][2] = w; g_dirty[g_ndirty][3] = h; g_ndirty++; } void meow_gfx_mark_dirty(int x, int y, int w, int h) { dirty_add(x, y, w, h); } void meow_gfx_mark_full(void) { dirty_full(); } /* launcher(系统组件)与 SDK 共用同一 back_buf:launcher 的 fb 绘制指针指向它, * 从而 launcher 与 app 统一渲染栈(同一离屏 + 同一 present)。 */ unsigned char *meow_ui_back_buf(void) { return g_back_buf; } /* ================= 像素写入 ================= */ static void px_write_rgb565(int x, int y, uint16_t c16) { *(uint16_t *)(g_fb + (size_t)y * g_line + (size_t)x * 2) = c16; } static uint16_t px_read_rgb565(int x, int y) { return *(uint16_t *)(g_fb + (size_t)y * g_line + (size_t)x * 2); } static void put_px(int x, int y, uint32_t color) { if (x < 0 || x >= g_xres || y < 0 || y >= g_yres) return; if (g_bpp == 16) { px_write_rgb565(x, y, rgb565(color)); } else { unsigned char *p = g_fb + (size_t)y * g_line + (size_t)x * 4; *(uint32_t *)p = (color << 8) | 0xFF; } } static void put_px_blend(int x, int y, uint32_t color, int alpha) { if (x < 0 || x >= g_xres || y < 0 || y >= g_yres) return; uint16_t fg = rgb565(color); if (g_bpp == 16) { uint16_t bg = px_read_rgb565(x, y); int ia = 255 - alpha; unsigned r = ((((fg >> 11) & 0x1F) * alpha + ((bg >> 11) & 0x1F) * ia) / 255); unsigned gg = ((((fg >> 5) & 0x3F) * alpha + ((bg >> 5) & 0x3F) * ia) / 255); unsigned b = (((fg & 0x1F) * alpha + (bg & 0x1F) * ia) / 255); px_write_rgb565(x, y, (uint16_t)((r << 11) | (gg << 5) | b)); } else { unsigned char *p = g_fb + (size_t)y * g_line + (size_t)x * 4; uint32_t bg = (*(uint32_t *)p) >> 8; int ia = 255 - alpha; unsigned r = ((((color >> 16) & 0xFF) * alpha + ((bg >> 16) & 0xFF) * ia) / 255); unsigned gg = ((((color >> 8) & 0xFF) * alpha + ((bg >> 8) & 0xFF) * ia) / 255); unsigned b = (((color & 0xFF) * alpha + (bg & 0xFF) * ia) / 255); *(uint32_t *)p = (r << 24) | (gg << 16) | (b << 8) | 0xFF; } } /* ================= 基本绘制 ================= */ void meow_ui_clear(uint32_t color) { if (!g_init_ok) return; /* 快速整刷(16bit 用半字填充,可 memset 当色值由 32bit 拼接掩码) */ if (g_bpp == 16) { uint16_t c16 = rgb565(color); uint32_t c32 = (uint32_t)c16 | ((uint32_t)c16 << 16); for (int y = 0; y < g_yres; y++) { uint32_t *row = (uint32_t *)(g_fb + (size_t)y * g_line); int n = g_xres / 2; for (int i = 0; i < n; i++) row[i] = c32; if (g_xres & 1) px_write_rgb565(g_xres - 1, y, c16); } } else { for (int y = 0; y < g_yres; y++) for (int x = 0; x < g_xres; x++) put_px(x, y, color); } dirty_full(); } void meow_ui_fill_rect(int x, int y, int w, int h, uint32_t color) { if (!g_init_ok) return; if (w <= 0 || h <= 0) return; int x0 = x, y0 = y; if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (x >= g_xres || y >= g_yres) return; if (x + w > g_xres) w = g_xres - x; if (y + h > g_yres) h = g_yres - y; if (w <= 0 || h <= 0) return; if (g_bpp == 16) { uint16_t c16 = rgb565(color); for (int ry = y; ry < y + h; ry++) { uint16_t *rp = (uint16_t *)(g_fb + (size_t)ry * g_line + (size_t)x * 2); for (int rx = 0; rx < w; rx++) rp[rx] = c16; } } else { for (int ry = y; ry < y + h; ry++) for (int rx = x; rx < x + w; rx++) put_px(rx, ry, color); } dirty_add(x, y, w, h); } void meow_ui_put_px(int x, int y, uint32_t color) { if (!g_init_ok) return; put_px(x, y, color); dirty_add(x, y, 1, 1); } void meow_ui_draw_rect(int x, int y, int w, int h, uint32_t color) { if (w <= 0 || h <= 0) return; meow_ui_fill_rect(x, y, w, 1, color); meow_ui_fill_rect(x, y + h - 1, w, 1, color); meow_ui_fill_rect(x, y, 1, h, color); meow_ui_fill_rect(x + w - 1, y, 1, h, color); } /* 圆角矩形区域判定(含内缩) */ static int rr_inside(int px, int py, int x, int y, int w, int h, int r) { if (px < x || px >= x + w || py < y || py >= y + h) return 0; if (r <= 0) return 1; if (w < 2 * r || h < 2 * r) return 1; /* 半径过大退化为矩形 */ int cx1 = x + r, cy1 = y + r; int cx2 = x + w - 1 - r, cy2 = y + h - 1 - r; if (px < cx1 && py < cy1) return ((px - cx1) * (px - cx1) + (py - cy1) * (py - cy1)) <= r * r; if (px > cx2 && py < cy1) return ((px - cx2) * (px - cx2) + (py - cy1) * (py - cy1)) <= r * r; if (px < cx1 && py > cy2) return ((px - cx1) * (px - cx1) + (py - cy2) * (py - cy2)) <= r * r; if (px > cx2 && py > cy2) return ((px - cx2) * (px - cx2) + (py - cy2) * (py - cy2)) <= r * r; return 1; } static int rr_border(int px, int py, int x, int y, int w, int h, int r) { if (!rr_inside(px, py, x, y, w, h, r)) return 0; int ir = r - 1; if (ir < 0) ir = 0; if (w > 2 && h > 2 && rr_inside(px, py, x + 1, y + 1, w - 2, h - 2, ir)) return 0; return 1; } void meow_gfx_fill_round(int x, int y, int w, int h, int r, uint32_t color) { if (!g_init_ok) return; int x1 = x, y1 = y, x2 = x + w, y2 = y + h; if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres; for (int py = y1; py < y2; py++) for (int px = x1; px < x2; px++) if (rr_inside(px, py, x, y, w, h, r)) put_px(px, py, color); dirty_add(x1, y1, x2 - x1, y2 - y1); } void meow_ui_draw_rect_round(int x, int y, int w, int h, int r, uint32_t color) { if (!g_init_ok) return; int x1 = x, y1 = y, x2 = x + w, y2 = y + h; if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres; for (int py = y1; py < y2; py++) for (int px = x1; px < x2; px++) if (rr_border(px, py, x, y, w, h, r)) put_px(px, py, color); dirty_add(x1, y1, x2 - x1, y2 - y1); } void meow_ui_fill_rect_alpha(int x, int y, int w, int h, uint32_t color, int alpha) { if (!g_init_ok) return; if (alpha <= 0) return; if (alpha >= 255) { meow_ui_fill_rect(x, y, w, h, color); return; } if (w <= 0 || h <= 0) return; if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (x >= g_xres || y >= g_yres) return; if (x + w > g_xres) w = g_xres - x; if (y + h > g_yres) h = g_yres - y; if (w <= 0 || h <= 0) return; for (int ry = y; ry < y + h; ry++) for (int rx = x; rx < x + w; rx++) put_px_blend(rx, ry, color, alpha); dirty_add(x, y, w, h); } /* ================= 左滑返回视觉条(GESTURE_BACK §2.3) ================= */ #define MUI_HINT_H 5 static int g_hint_last = 0; /* 上次画过的条长(清条时只清到它,避免残留) */ void meow_ui_swipe_hint(int x) { if (!g_init_ok) return; const meow_theme *th = meow_theme_get(); /* 进度条画在状态栏下沿(z 在状态栏之下,不被全局状态栏覆盖)。 * 有状态栏(非全屏)→ y = 状态栏高度;收合(collapsed)→ y=提示线下缘 * (MUI_SBAR_COLLAPSE_H,与提示线不重叠);无状态栏(全屏/隐藏)→ y=0。 */ int hy = meow_ui_statusbar_height(); /* 单一实现(与非全屏 Hello 完全一致,全屏仅 hy 不同):grow 画 0..x、shrunk 先清旧长 * 再画 0..x、x<=0 清空。x<0 视为清空;条长上限=屏宽。 */ if (x > 0) { if (x > g_xres) x = g_xres; /* 往回滑(新长度 < 上次)时,先清掉「上次画到旧长度、但本次超出的部分」, * 否则条只增不减(右滑变长、左滑回缩实时可见)。 */ if (g_hint_last > x) meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg); /* 条色取主题前景(fg):fg 定义为与背景对比,能同时覆盖「非全屏(底=主题bg)」与 * 「全屏 Hello 类(底=主题accent)」两种情况——accent 在 Hello 全屏 accent 底上同色不可见。 */ meow_ui_fill_rect(0, hy, x, MUI_HINT_H, th->fg); g_hint_last = x; } else if (g_hint_last > 0) { /* 手指回原点/抬手未达标/抬手达标:清掉此前画的条(填回背景色) */ meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg); g_hint_last = 0; } /* 不发 flush:由调用方 meow_ui_flush_region(0,0,宽,6) 只刷顶部条 */ } /* ================= 5x7 回退点阵字(与 launcher/hello 一致的表,独立携带) ================= */ static const unsigned char mfont5x7[][8] = { {0x0E,0x11,0x13,0x15,0x19,0x11,0x0E,0x00},{0x04,0x0C,0x04,0x04,0x04,0x04,0x0E,0x00}, {0x0E,0x11,0x01,0x02,0x04,0x08,0x1F,0x00},{0x1F,0x02,0x04,0x02,0x01,0x11,0x0E,0x00}, {0x02,0x06,0x0A,0x12,0x1F,0x02,0x02,0x00},{0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E,0x00}, {0x06,0x08,0x10,0x1E,0x11,0x11,0x0E,0x00},{0x1F,0x01,0x02,0x04,0x08,0x08,0x08,0x00}, {0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E,0x00},{0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C,0x00}, {0x0E,0x11,0x11,0x1F,0x11,0x11,0x11,0x00},{0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E,0x00}, {0x0E,0x11,0x10,0x10,0x10,0x11,0x0E,0x00},{0x1C,0x12,0x11,0x11,0x11,0x12,0x1C,0x00}, {0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F,0x00},{0x1F,0x10,0x10,0x1E,0x10,0x10,0x10,0x00}, {0x0E,0x11,0x10,0x17,0x11,0x11,0x0F,0x00},{0x11,0x11,0x11,0x1F,0x11,0x11,0x11,0x00}, {0x0E,0x04,0x04,0x04,0x04,0x04,0x0E,0x00},{0x07,0x02,0x02,0x02,0x02,0x12,0x0C,0x00}, {0x11,0x12,0x14,0x18,0x14,0x12,0x11,0x00},{0x10,0x10,0x10,0x10,0x10,0x10,0x1F,0x00}, {0x11,0x1B,0x15,0x15,0x11,0x11,0x11,0x00},{0x11,0x19,0x15,0x13,0x11,0x11,0x11,0x00}, {0x0E,0x11,0x11,0x11,0x11,0x11,0x0E,0x00},{0x1E,0x11,0x11,0x1E,0x10,0x10,0x10,0x00}, {0x0E,0x11,0x11,0x11,0x15,0x12,0x0D,0x00},{0x1E,0x11,0x11,0x1E,0x14,0x12,0x11,0x00}, {0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E,0x00},{0x1F,0x04,0x04,0x04,0x04,0x04,0x04,0x00}, {0x11,0x11,0x11,0x11,0x11,0x11,0x0E,0x00},{0x11,0x11,0x11,0x11,0x11,0x0A,0x04,0x00}, {0x11,0x11,0x11,0x15,0x15,0x1B,0x11,0x00},{0x11,0x11,0x0A,0x04,0x0A,0x11,0x11,0x00}, {0x11,0x11,0x11,0x0A,0x04,0x04,0x04,0x00},{0x1F,0x01,0x02,0x04,0x08,0x10,0x1F,0x00}, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},{0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00}, {0x04,0x04,0x04,0x04,0x04,0x00,0x04,0x00},{0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00}, }; #define FONT5X7_N (sizeof(mfont5x7)/sizeof(mfont5x7[0])) static int glyph_index(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'Z') return 10 + (c - 'A'); if (c >= 'a' && c <= 'z') return 10 + (c - 'a'); if (c == ' ') return 36; if (c == '.') return 37; if (c == '!') return 38; if (c == '-' || c == '_') return 39; return 36; } static void draw_glyph5x7(int x, int y, char c, int scale, uint32_t color) { const unsigned char *g = mfont5x7[glyph_index(c)]; for (int row = 0; row < 7; row++) { unsigned char bits = g[row]; for (int col = 0; col < 5; col++) { if (bits & (0x10 >> col)) { for (int dy = 0; dy < scale; dy++) for (int dx = 0; dx < scale; dx++) put_px(x + col * scale + dx, y + row * scale + dy, color); } } } } static void draw_text5x7(int x, int y, const char *s, int scale, uint32_t color) { while (*s) { draw_glyph5x7(x, y, *s, scale, color); x += 6 * scale; s++; } } /* ================= 文本(字库优先,回退点阵) ================= */ static int fallback_scale(int size) { return (size >= 16) ? 2 : 1; } static void text_draw(int x, int y, const char *s, int size, uint32_t color) { if (!g_init_ok || !s) return; if (g_mfont_ready && g_bpp == 16) { meow_font_drawtext((uint16_t *)g_fb, x, y, s, rgb565(color), size); int w = meow_font_width_text(s, size); int hh = meow_font_line_height(size); dirty_add(x, y, w, hh); } else { int sc = fallback_scale(size); draw_text5x7(x, y, s, sc, color); dirty_add(x, y, (int)strlen(s) * 6 * sc, 7 * sc); } } void meow_ui_text(int x, int y, const char *utf8, int size, uint32_t color) { text_draw(x, y, utf8, size, color); } void meow_ui_text_right(int right_x, int y, const char *utf8, int size, uint32_t color) { text_draw(right_x - meow_ui_text_width(utf8, size), y, utf8, size, color); } void meow_ui_text_center(int cx, int y, const char *utf8, int size, uint32_t color) { text_draw(cx - meow_ui_text_width(utf8, size) / 2, y, utf8, size, color); } int meow_ui_text_width(const char *utf8, int size) { if (!utf8) return 0; if (g_mfont_ready) return meow_font_width_text(utf8, size); return (int)strlen(utf8) * 6 * fallback_scale(size); } int meow_ui_text_height(int size) { if (g_mfont_ready) return meow_font_line_height(size); return 7 * fallback_scale(size); } /* ================= RGBA blit(图片层用) ================= */ void meow_gfx_blit_rgba(int x, int y, int w, int h, const uint8_t *rgba, int alpha) { if (!g_init_ok || !rgba || w <= 0 || h <= 0 || alpha <= 0) return; int x1 = x, y1 = y, x2 = x + w, y2 = y + h; if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres; for (int py = y1; py < y2; py++) { int sy = py - y; for (int px = x1; px < x2; px++) { int sx = px - x; const uint8_t *p = rgba + ((size_t)sy * w + sx) * 4; int a = (alpha * p[3]) / 255; if (a <= 0) continue; uint32_t c = ((uint32_t)p[0] << 16) | ((uint32_t)p[1] << 8) | (uint32_t)p[2]; if (a >= 255) put_px(px, py, c); else put_px_blend(px, py, c, a); } } dirty_add(x1, y1, x2 - x1, y2 - y1); } /* ================= present / 上屏 ================= */ static void dump_ppm(void) { if (!g_dump_path || !g_back_buf) return; FILE *f = fopen(g_dump_path, "wb"); if (!f) return; fprintf(f, "P6\n%d %d\n255\n", g_xres, g_yres); for (int y = 0; y < g_yres; y++) { for (int x = 0; x < g_xres; x++) { unsigned char r, g, b; if (g_bpp == 16) { uint16_t px = *(uint16_t *)(g_back_buf + (size_t)y * g_line + (size_t)x * 2); r = ex5((px >> 11) & 0x1F); g = ex6((px >> 5) & 0x3F); b = ex5(px & 0x1F); } else { uint32_t px = *(uint32_t *)(g_back_buf + (size_t)y * g_line + (size_t)x * 4); r = (px >> 16) & 0xFF; g = (px >> 8) & 0xFF; b = px & 0xFF; } fputc(r, f); fputc(g, f); fputc(b, f); } } fclose(f); } /* ================= 状态栏「上收出屏只留提示线」(collapsed,基座) ================= */ /* 每帧推进上收/恢复:向目标偏移(collapsed→MUI_SBAR_H、否则→0)线性逼近; * 无过渡时长(g_sbar_collapse_anim_ms<=0)= 一步到位(基座默认)。动画插值骨架在此, * 后续代理可换缓动/每帧步进。 */ static void statusbar_collapse_step(void) { int tgt = g_sbar_collapsed ? MUI_SBAR_H : 0; if (g_sbar_collapse_h == tgt) return; if (g_sbar_collapse_anim_ms <= 0) { g_sbar_collapse_h = tgt; return; } unsigned long long now = now_ms(); unsigned long long el = now - g_sbar_collapse_start_ms; int span = g_sbar_collapse_anim_ms; if ((int)el >= span) { g_sbar_collapse_h = tgt; return; } int delta = tgt - g_sbar_collapse_from_h; g_sbar_collapse_h = g_sbar_collapse_from_h + (int)((long long)delta * (long long)el / (long long)span); } /* 上收中是否需要逐帧重画(动画未达目标位置时置位,强制每帧推进)。 */ static int statusbar_collapse_in_transition(void) { int tgt = g_sbar_collapsed ? MUI_SBAR_H : 0; return g_sbar_collapse_h != tgt; } /* 上收状态栏:内容整体上移出屏(不画时钟/电量),仅在顶部画一条「提示线」。 * 与 meow_ui_swipe_hint(左滑返回进度条,5px,动态)不同:此线是「状态栏可下拉还原」的 * 常驻提示(主题强调色下边缘)。收合只占/清「提示线」高度区域(MUI_SBAR_COLLAPSE_H=4), * 不再清整 MUI_SBAR_H(28) 行 —— app 内容按 inset=提示线高可上移到提示线下缘,无 28px 空条。 */ static void statusbar_collapsed_render(int h) { (void)h; const meow_theme *th = meow_theme_get(); int w = meow_ui_width(); /* h 越大内容越贴顶;基座:折叠态清「提示线」区域底 + 顶部提示线(提示线下缘=主题 accent) */ meow_ui_fill_rect(0, 0, w, MUI_SBAR_COLLAPSE_H, th->bg); meow_ui_fill_rect(0, MUI_SBAR_COLLAPSE_H - 1, w, 1, th->accent); } /* ---- 公共 API(基座实现;动画插值骨架见 statusbar_collapse_step)---- */ void meow_ui_statusbar_set_collapsed(int on) { int tgt = on ? 1 : 0; if (tgt == g_sbar_collapsed) return; g_sbar_collapsed = tgt; g_sbar_collapse_from_h = g_sbar_collapse_h; g_sbar_collapse_start_ms = now_ms(); dirty_full(); /* 顶栏显隐/上收变化 → 全帧重画 */ } int meow_ui_statusbar_collapsed(void) { return g_sbar_collapsed; } void meow_ui_statusbar_animate(int ms) { g_sbar_collapse_anim_ms = (ms > 0) ? ms : 0; /* 0=瞬切(基座默认) */ } int meow_ui_statusbar_hotzone(void) { /* 非全屏=整条状态栏可视;全屏时缩小到 MEOW_SBAR_HOTZONE_FS(保留一处下拉入口)。 * 上收(collapsed、非全屏)时热区=提示线下缘高度(MUI_SBAR_COLLAPSE_H,与渲染/inset 同口径)。 * 全屏态由 meow_ui_sys 维护。 */ if (meow_ui_sys_statusbar_active()) return g_sbar_collapsed ? MUI_SBAR_COLLAPSE_H : MUI_SBAR_H; return MEOW_SBAR_HOTZONE_FS; } /* 顶部状态栏条是否被本次脏区触及(含整帧):触及或数据已变(force)才需要重画顶条。 */ static int statusbar_touched(void) { if (g_dirty_full) return 1; for (int i = 0; i < g_ndirty; i++) if (g_dirty[i][1] < MUI_SBAR_H) return 1; /* 脏矩形与顶条(前 MUI_SBAR_H 行)相交 */ return 0; } static void present(void) { if (!g_init_ok || !g_back_buf) return; if (!(g_dirty_full || g_ndirty > 0)) return; /* 帧率门控(A-1/A-2,从 launcher 迁入):present 距上次 ≥ period(1000/eff_rate) 才呈现; * 未满周期则保留脏区(不 dirty_reset),下一轮再试。refresh_always_max=1 时跳过门控(恒满速)。 */ unsigned long long now = now_ms(); int period = (g_eff_rate > 0) ? (1000 / g_eff_rate) : 16; if (!g_refresh_always_max) { if (g_last_present_ms && now - g_last_present_ms < (unsigned long long)period) return; } /* 全局状态栏(A-0)+ 上收(collapsed)基座:上屏前把状态栏画进 back_buf 顶部行。 * 展开态=整条(MUI_SBAR_H 行);收合态只占/清「提示线」区域(MUI_SBAR_COLLAPSE_H 行), * app 内容(inset=提示线高)可上移到提示线下缘,无 28px 空条。全屏/隐藏时 active()==0 * 不画(沉浸),app 内容可占满整屏。触发条件:数据已变(1s 轻刷/显隐切换) / * 顶条被 app 内容触及 / 上收动画未到位(逐帧推进)。展开态仍走 * meow_ui_sys_statusbar_render()(meow_ui_sys.c 不改,留给后续代理); * 上收折叠态(collapse_h>=MUI_SBAR_H)由基座只画提示线(内容上移出屏)。 */ if (meow_ui_sys_statusbar_active() && (meow_ui_sys_statusbar_force() || statusbar_touched() || statusbar_collapse_in_transition())) { statusbar_collapse_step(); if (g_sbar_collapse_h >= MUI_SBAR_H) statusbar_collapsed_render(g_sbar_collapse_h); else meow_ui_sys_statusbar_render(); } if (g_fb_mem) { ioctl(g_fb_fd, FBIO_WAITFORVSYNC, 0); /* 等垂直同步 */ unsigned long long tbg = now_ms(); if (g_dirty_full) { memcpy(g_fb_mem, g_back_buf, (size_t)g_line * (size_t)g_yres); } else { int bppb = g_bpp / 8; for (int i = 0; i < g_ndirty; i++) { int rw = g_dirty[i][2], ry = g_dirty[i][1]; for (int yy = 0; yy < g_dirty[i][3]; yy++) { size_t off = (size_t)(ry + yy) * g_line + (size_t)g_dirty[i][0] * bppb; memcpy(g_fb_mem + off, g_back_buf + off, (size_t)rw * bppb); } } } refresh_governor(now_ms() - tbg, period); } g_last_present_ms = now; g_frames_presented++; if (g_dump_path) dump_ppm(); dirty_reset(); } void meow_ui_flush(void) { present(); } void meow_ui_flush_region(int x, int y, int w, int h) { dirty_add(x, y, w, h); present(); } /* ---- 帧率调度设置接口(launcher 与 app 共用;归口 PERF_SCHEDULER 显示刷新率子域)---- */ /* 设目标档位(60/30/15):同时把当前有效档对齐到目标(与 launcher settings_load 语义一致)。 */ void meow_ui_set_refresh_rate(int r) { if (r != 60 && r != 30 && r != 15) return; g_refresh_rate_cfg = r; g_eff_rate = r; } /* 1=恒 max 不降档(性能优先);0=允许 governor 在 [15,30,60] 内自适应降/升档。 */ void meow_ui_set_refresh_always_max(int on) { g_refresh_always_max = on ? 1 : 0; } /* 当前有效帧率(governor 可能已降档;验证/诊断用)。 */ int meow_ui_effective_rate(void) { return g_eff_rate; } /* 已呈现帧计数(验证 present 频率用)。 */ unsigned long long meow_ui_frames_presented(void) { return g_frames_presented; } int meow_gfx_is_device(void) { return g_fb_mem != NULL; } int meow_gfx_mfont_ready(void) { return g_mfont_ready; } /* ---- 字体档位(公共 API,基座定义 + font 域真重载钩子)---- * meow_font_tier()/meow_font_set_tier() 在 meow_ui_gfx.c 定义(符号唯一),font 代理勿在 * meow_font.c 重复定义;大字库按档装载由 meow_font.c meow_font_reload_tier() 实现,并在 * meow_font_set_tier() 处唤起(见下)。 */ int meow_font_tier(void) { return g_font_tier; } int meow_font_set_tier(int tier) { int old = g_font_tier; if (tier < MEOW_FONT_TIER_COMMON) tier = MEOW_FONT_TIER_COMMON; if (tier > MEOW_FONT_TIER_FULL) tier = MEOW_FONT_TIER_FULL; if (tier == g_font_tier) return g_font_tier; g_font_tier = tier; /* 真重载(font 域钩子,M2.5x):按档装载大字库。launcher 已按档解析并注入 * MEOW_FONT_PNG/IDX(tier>0 → GBK/Full 大字库绝对路径);重读 env 重载。 * 无 env/失败 → 回退卡默认 MeowOS/fonts/default → 固件。 * 大字库 atlas 大(GBK≈5-15MB+),meow_font_reload_tier 先 unload 旧再 load 新,防双驻留。 */ const char *ep = getenv("MEOW_FONT_PNG"); const char *ei = getenv("MEOW_FONT_IDX"); if (ep && ep[0] && ei && ei[0] && meow_font_reload_tier(tier, ep, ei) == 0) return g_font_tier; if (meow_font_reload_tier(tier, MEOW_FONT_PNG_CARD, MEOW_FONT_IDX_CARD) == 0) return g_font_tier; if (meow_font_reload_tier(tier, MEOW_FONT_PNG_SYS, MEOW_FONT_IDX_SYS) == 0) return g_font_tier; g_font_tier = old; /* 全部失败 → 回退旧档(字库可能已不可用,5x7 点阵兜底,系统不死) */ return g_font_tier; } /* ================= 初始化/销毁 ================= */ /* 字库加载优先级(M2.x): * 1) 环境变量 MEOW_FONT_PNG/MEOW_FONT_IDX(launcher 注入的「当前生效字体」= 系统 font.file) * —— app 未自带字库(§5.7 load_handle 未实现)时跟随系统 font.file; * 2) 卡默认 MeowOS/fonts/default/regular/font.png(M2.6 字重结构;不再硬编码 MeowAPPs/fonts); * 3) 固件 /usr/share/meowos/font/font.png。 * 全失败 → g_mfont_ready=0,text_draw 回退 5x7 点阵(系统不死)。 */ static int load_font(void) { /* 字体档位(MEOW_FONT_TIER env,由 launcher 按 app.json "fonts" 注入): * 0=默认4K常用 1=大字库GBK 2=Full。launcher 已按档解析大字库路径并经 MEOW_FONT_PNG/IDX * 注入(见 meow_font_effective_paths_tiered),故下方 meow_font_load(ep,ei) 即装载 * 对应档位字库(tier>0 → GBK/Full 大字库);此处 read env 存档对齐公共 get/set。 */ const char *etr = getenv("MEOW_FONT_TIER"); if (etr && etr[0]) { g_font_tier = atoi(etr); if (g_font_tier < MEOW_FONT_TIER_COMMON) g_font_tier = MEOW_FONT_TIER_COMMON; if (g_font_tier > MEOW_FONT_TIER_FULL) g_font_tier = MEOW_FONT_TIER_FULL; } const char *ep = getenv("MEOW_FONT_PNG"); const char *ei = getenv("MEOW_FONT_IDX"); if (ep && ep[0] && ei && ei[0] && meow_font_load(ep, ei) == 0) return 1; if (meow_font_load(MEOW_FONT_PNG_CARD, MEOW_FONT_IDX_CARD) == 0) return 1; if (meow_font_load(MEOW_FONT_PNG_SYS, MEOW_FONT_IDX_SYS) == 0) return 1; return 0; } /* M2.5x 多字体:读 launcher 注入的环境变量,构建 6 字符类 → 字库的映射(同 family 去重、 * ≤4 套上限由 meow_font_config_multifont 统一处理)。launcher 注入(见 meow_launcherd.c * launch_app):MEOW_MULTI_FONT=0/1;MEOW_FONT_PNG/IDX=全局(保留);每类 * MEOW_FONT_CLS__PNG/IDX(命中则覆盖全局;未注入 = 该类跟随全局)。 * 仅当全局字库已装载(g_mfont_ready)才配置;关/无 env → 单字体,零额外开销。 */ static void meow_gfx_load_multifont(void) { static const char *cls_env[MEOW_FCLS_COUNT][2] = { { "MEOW_FONT_CLS_CJK_PNG", "MEOW_FONT_CLS_CJK_IDX" }, { "MEOW_FONT_CLS_CJK_SYM_PNG", "MEOW_FONT_CLS_CJK_SYM_IDX" }, { "MEOW_FONT_CLS_NUM_PNG", "MEOW_FONT_CLS_NUM_IDX" }, { "MEOW_FONT_CLS_LATIN_PNG", "MEOW_FONT_CLS_LATIN_IDX" }, { "MEOW_FONT_CLS_LATIN_SYM_PNG", "MEOW_FONT_CLS_LATIN_SYM_IDX" }, { "MEOW_FONT_CLS_OTHER_PNG", "MEOW_FONT_CLS_OTHER_IDX" }, }; const char *emf = getenv("MEOW_MULTI_FONT"); int on = emf && emf[0] == '1'; const char *gp = getenv("MEOW_FONT_PNG"); const char *gi = getenv("MEOW_FONT_IDX"); meow_font_class_cfg cfg[MEOW_FCLS_COUNT]; memset(cfg, 0, sizeof cfg); for (int c = 0; c < MEOW_FCLS_COUNT; c++) { const char *p = getenv(cls_env[c][0]); const char *idx = getenv(cls_env[c][1]); if (p && p[0] && idx && idx[0]) { cfg[c].png = p; cfg[c].idx = idx; } } int distinct = 0, truncated = 0; if (meow_font_config_multifont(on, gp, gi, cfg, &distinct, &truncated) != 0) meow_log(MEOW_LOG_WARN, "multi_font: config failed, single-font fallback"); else if (truncated) meow_log(MEOW_LOG_WARN, "multi_font: >%d distinct fonts -> capped (more memory!)", distinct); } int meow_ui_init(const meow_ui_opts *opts) { if (g_init_ok) return 0; int fw = 0, fh = 0; if (opts) { fw = opts->width; fh = opts->height; } int fd = open(FB_DEV, O_RDWR); if (fd >= 0) { unsigned char vinfo[160], finfo[68]; memset(vinfo, 0, sizeof vinfo); memset(finfo, 0, sizeof finfo); if (ioctl(fd, FBIOGET_VSCREENINFO, vinfo) == 0 && ioctl(fd, FBIOGET_FSCREENINFO, finfo) == 0) { g_xres = *(uint32_t *)(vinfo + 0); g_yres = *(uint32_t *)(vinfo + 4); g_bpp = *(uint32_t *)(vinfo + 24); g_line = *(uint32_t *)(finfo + 44); if (!g_line) g_line = g_xres * (g_bpp / 8); if (g_bpp != 16 && g_bpp != 32) g_bpp = 16; size_t maplen = (size_t)g_line * (size_t)g_yres; void *m = mmap(NULL, maplen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (m != MAP_FAILED) { g_fb_mem = m; g_fb_fd = fd; } else { close(fd); fd = -1; g_fb_mem = NULL; } } else { close(fd); fd = -1; g_fb_mem = NULL; } } /* host/qemu 无 fb:使用缺省/覆写几何,走离屏(present 只 dump) */ if (g_fb_mem == NULL) { if (g_xres <= 0 || g_xres > 4096) g_xres = MUI_DEFAULT_W; if (g_yres <= 0 || g_yres > 4096) g_yres = MUI_DEFAULT_H; if (g_bpp != 16 && g_bpp != 32) g_bpp = 16; g_line = g_xres * (g_bpp / 8); if (fd >= 0) { close(fd); fd = -1; } } if (fw > 0 && fw <= 4096) g_xres = fw; if (fh > 0 && fh <= 4096) g_yres = fh; g_line = g_xres * (g_bpp / 8); g_back_buf = malloc((size_t)g_line * (size_t)g_yres); if (!g_back_buf) { if (g_fb_mem) munmap(g_fb_mem, 0); if (fd >= 0) close(fd); return -1; } g_fb = g_back_buf; meow_font_set_fb((unsigned)g_xres, (unsigned)g_yres, (unsigned)g_line); g_mfont_ready = load_font(); if (g_mfont_ready) meow_gfx_load_multifont(); /* M2.5x 多字体(读 env;关/无 env = 单字体) */ const char *dp = getenv("MEOW_UI_DUMP"); if (dp && *dp) g_dump_path = strdup(dp); g_init_ok = 1; dirty_full(); meow_ui_sys_init(); /* A-0:系统状态读取器首刷(电量/WiFi/蓝牙/音量),状态栏就绪 */ /* 全屏预置(基座):launcher 按 app.json "fullscreen" 注入 MEOW_FULLSCREEN=1,SDK 据此 * 预先设全屏(隐藏整条状态栏/沉浸,statusbar_active()==0、statusbar_height()==0)。 * 未注入/非"1"=非全屏(默认)。放在 sys_init 后使首帧即为全屏态。 */ const char *efs = getenv("MEOW_FULLSCREEN"); if (efs && efs[0] == '1') meow_ui_set_fullscreen(1); return 0; } void meow_ui_shutdown(void) { if (!g_init_ok) return; meow_font_unload(); if (g_fb_mem) munmap(g_fb_mem, (size_t)g_line * (size_t)g_yres); if (g_fb_fd >= 0) close(g_fb_fd); if (g_back_buf) free(g_back_buf); if (g_dump_path) { free(g_dump_path); g_dump_path = NULL; } g_back_buf = NULL; g_fb = NULL; g_fb_mem = NULL; g_fb_fd = -1; g_init_ok = 0; g_mfont_ready = 0; dirty_reset(); } int meow_ui_width(void) { return g_xres; } int meow_ui_height(void) { return g_yres; }