Files
MeowOS-For-R3P2/patch_v2/sdk/meow_ui_gfx.c
T

842 lines
38 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
/* 本设备实测 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 = <family> 或 "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_<X>_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; }