1112 lines
51 KiB
C
1112 lines
51 KiB
C
/*
|
||
* 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 "sb_icons.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/app_launcher.c `launch_app` / font_theme.c `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(); }
|
||
void meow_gfx_dirty_reset(void) { dirty_reset(); }
|
||
|
||
/* back_buf 每行字节数 / 位深(M2.71-bug3:模态/IME 整帧快照恢复用)。 */
|
||
int meow_ui_back_buf_pitch(void) { return g_line; }
|
||
int meow_ui_back_buf_bpp(void) { return g_bpp; }
|
||
|
||
/* launcher(系统组件)与 SDK 共用同一 back_buf:launcher 的 fb 绘制指针指向它,
|
||
* 从而 launcher 与 app 统一渲染栈(同一离屏 + 同一 present)。 */
|
||
unsigned char *meow_ui_back_buf(void) { return g_back_buf; }
|
||
|
||
/* ================= 状态栏 MD3 图标 blit(写进本 SDK back_buf) =================
|
||
* 复用 launcher sb_icons.h(Material Symbols rounded,32x32 单通道 alpha)作为状态栏
|
||
* 右侧图标簇(「同一个状态栏」图标式,见 meow_ui_sys_statusbar_render)。
|
||
* idx = sb_icons[] 索引(SB_I_*,见 meow_ui_internal.h)
|
||
* x = 图标槽左缘(槽宽 SB_ICON_SIZE=32)
|
||
* y = 图标槽顶(仅作参考;实际按 ink 垂直中心对齐 cy)
|
||
* ink_h = 目标 ink 高(>0 等比缩放;0 = 保持位图自然 ink 尺寸——WiFi 强弱档用)
|
||
* fg = 24-bit RGB 前景(主题 on_surface / on_surface_variant,日/夜由主题表提供)
|
||
* cy = ink 垂直中心线(= 状态栏文字中心,28px 栏约 14)
|
||
* 先求位图 ink 边界(阈值 32,忽略近透明抗锯齿),按 ink 在槽内水平居中、垂直中心对齐 cy;
|
||
* 像素按 alpha 软边混合进 back_buf(自 g_fb/g_line/g_xres/g_yres,与 launcher sb_blit_icon 同源算法)。 */
|
||
void meow_gfx_blit_icon(int idx, int x, int y, int ink_h, uint32_t fg, int cy)
|
||
{
|
||
(void)y;
|
||
if (!g_init_ok || !g_fb) return;
|
||
if (idx < 0 || idx >= (int)(sizeof sb_icons / sizeof sb_icons[0])) return;
|
||
const unsigned char *src = sb_icons[idx].data;
|
||
uint16_t col = rgb565(fg);
|
||
/* 1) ink 边界(含空图兜底) */
|
||
int minx = SB_ICON_SIZE, miny = SB_ICON_SIZE, maxx = -1, maxy = -1;
|
||
for (int i = 0; i < SB_ICON_SIZE * SB_ICON_SIZE; i++) {
|
||
if (src[i] >= 32) {
|
||
int xx = i % SB_ICON_SIZE, yy = i / SB_ICON_SIZE;
|
||
if (xx < minx) minx = xx; if (xx > maxx) maxx = xx;
|
||
if (yy < miny) miny = yy; if (yy > maxy) maxy = yy;
|
||
}
|
||
}
|
||
if (maxx < minx || maxy < miny) { minx = miny = 0; maxx = maxy = SB_ICON_SIZE - 1; }
|
||
int ink_w = maxx - minx + 1, ink_h_n = maxy - miny + 1;
|
||
int t_h = (ink_h > 0) ? ink_h : ink_h_n;
|
||
int t_w = ink_w * t_h / ink_h_n; if (t_w < 1) t_w = 1;
|
||
if (t_w > SB_ICON_SIZE * 2) t_w = SB_ICON_SIZE * 2; /* 防御:等比极端上限 */
|
||
/* 2) 目标框左上:水平居中于 32px 槽;垂直中心 = cy */
|
||
int x0 = x + (SB_ICON_SIZE - t_w) / 2;
|
||
int y0 = cy - t_h / 2;
|
||
/* 3) 最近邻采样映射:目标 (dx,dy) ← 源 ink */
|
||
for (int dy = 0; dy < t_h; dy++) {
|
||
int ty = y0 + dy;
|
||
if (ty < 0 || ty >= g_yres) continue;
|
||
int sy = miny + (dy * ink_h_n) / t_h;
|
||
uint16_t *row = (uint16_t *)(g_fb + (size_t)ty * g_line);
|
||
for (int dx = 0; dx < t_w; dx++) {
|
||
int tx = x0 + dx;
|
||
if (tx < 0 || tx >= g_xres) continue;
|
||
int sx = minx + (dx * ink_w) / t_w;
|
||
int a = src[sy * SB_ICON_SIZE + sx];
|
||
if (a < 8) continue; /* 透明像素跳过 */
|
||
uint16_t bg = row[tx];
|
||
if (a >= 240) { row[tx] = col; continue; }
|
||
int a4 = a >> 4; /* 0..15 */
|
||
int r = ((col >> 11) * a4 + ((bg >> 11) & 0x1F) * (16 - a4)) >> 4;
|
||
int g = (((col >> 5) & 0x3F) * a4 + ((bg >> 5) & 0x3F) * (16 - a4)) >> 4;
|
||
int b = ((col & 0x1F) * a4 + (bg & 0x1F) * (16 - a4)) >> 4;
|
||
row[tx] = (uint16_t)((r << 11) | (g << 5) | b);
|
||
}
|
||
}
|
||
/* 整条顶栏作为脏区标出(图标在顶条内,present 只重画顶条即可) */
|
||
dirty_add(x, 0, SB_ICON_SIZE, MUI_SBAR_H);
|
||
}
|
||
|
||
/* ================= 像素写入 ================= */
|
||
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;
|
||
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);
|
||
}
|
||
|
||
/* 圆角矩形半透明填充(M2.71-bug4):按圆角裁剪后逐像素混合 α。供「禁用变暗」等状态层用,
|
||
* 避免在圆角外的方形区域也叠色(旧 fill_rect_alpha 会在圆角外露出灰方块)。 */
|
||
void meow_gfx_fill_round_alpha(int x, int y, int w, int h, int r, uint32_t color, int alpha)
|
||
{
|
||
if (!g_init_ok) return;
|
||
if (alpha <= 0) return;
|
||
if (alpha >= 255) { meow_gfx_fill_round(x, y, w, h, r, color); return; }
|
||
if (w <= 0 || h <= 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++)
|
||
for (int px = x1; px < x2; px++)
|
||
if (rr_inside(px, py, x, y, w, h, r)) put_px_blend(px, py, color, alpha);
|
||
dirty_add(x1, y1, x2 - x1, y2 - y1);
|
||
}
|
||
|
||
/* ================= §5.8.6 batch2:圆形/线段/圆弧环(软渲,无 -lm) =================
|
||
* 无 GPU 合成、构建只链 libz+pthread(无 -lm),故不用 math.h:COS 用象限对称查表、
|
||
* 圆用 bbox 距离测试、弧带用「沿弧采样成粗线(圆帽)」近似。 */
|
||
static const int m_cos90[91] = {
|
||
1024,1024,1023,1023,1022,1020,1018,1016,
|
||
1014,1011,1008,1005,1002,998,994,989,
|
||
984,979,974,968,962,956,949,943,
|
||
935,928,920,912,904,896,887,878,
|
||
868,859,849,839,828,818,807,796,
|
||
784,773,761,749,737,724,711,698,
|
||
685,672,658,644,630,616,602,587,
|
||
573,558,543,527,512,496,481,465,
|
||
449,433,416,400,384,367,350,333,
|
||
316,299,282,265,248,230,213,195,
|
||
178,160,143,125,107,89,71,54,
|
||
36,18,0,
|
||
};
|
||
|
||
static int m_cos_deg(int deg) /* 任意角度(可负)→ cos(deg)*1024 */
|
||
{
|
||
int a = deg % 360;
|
||
if (a < 0) a += 360;
|
||
if (a <= 90) return m_cos90[a];
|
||
if (a <= 180) return -m_cos90[180 - a];
|
||
if (a <= 270) return -m_cos90[a - 180];
|
||
return m_cos90[360 - a];
|
||
}
|
||
static int m_sin_deg(int deg) { return m_cos_deg(90 - deg); }
|
||
|
||
void meow_gfx_fill_circle(int cx, int cy, int r, uint32_t color)
|
||
{
|
||
if (!g_init_ok || r <= 0) return;
|
||
int x1 = cx - r, y1 = cy - r, x2 = cx + r + 1, y2 = cy + r + 1;
|
||
if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0;
|
||
if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres;
|
||
if (x2 <= x1 || y2 <= y1) return;
|
||
int r2 = r * r;
|
||
for (int py = y1; py < y2; py++) {
|
||
int dy = py - cy;
|
||
for (int px = x1; px < x2; px++) {
|
||
int dx = px - cx;
|
||
if (dx * dx + dy * dy <= r2) put_px(px, py, color);
|
||
}
|
||
}
|
||
dirty_add(x1, y1, x2 - x1, y2 - y1);
|
||
}
|
||
|
||
void meow_gfx_line(int x0, int y0, int x1, int y1, int th, uint32_t color)
|
||
{
|
||
if (!g_init_ok) return;
|
||
int dx = x1 - x0, dy = y1 - y0;
|
||
int adx = dx < 0 ? -dx : dx, ady = dy < 0 ? -dy : dy;
|
||
int steps = (adx > ady ? adx : ady) + 1;
|
||
if (steps <= 1) { meow_gfx_fill_circle(x0, y0, th / 2 + 1, color); return; }
|
||
int cap = th / 2;
|
||
if (cap < 1) cap = 1;
|
||
for (int i = 0; i <= steps; i++) {
|
||
int x = x0 + dx * i / steps;
|
||
int y = y0 + dy * i / steps;
|
||
if (cap <= 1) put_px(x, y, color);
|
||
else meow_gfx_fill_circle(x, y, cap, color);
|
||
}
|
||
}
|
||
|
||
void meow_gfx_ring_arc(int cx, int cy, int r, int th, int start_deg, int sweep_deg, uint32_t color)
|
||
{
|
||
if (!g_init_ok || r <= 0) return;
|
||
int sw = sweep_deg % 360;
|
||
if (sw < 0) sw += 360;
|
||
/* 整环(|sweep|>=360):bbox 距离带(无角度判定,最快)。注意 sweep=360 → sw=0,
|
||
* 若靠 sw>=360 判整环会落下走弧分支只画一个点(顺带让 Radio 外圈/圆形进度轨道环渲染成点)。 */
|
||
if (sweep_deg >= 360 || sweep_deg <= -360) {
|
||
int r1 = r - th / 2, r2 = r + th / 2;
|
||
if (r1 < 0) r1 = 0;
|
||
int x1 = cx - r2, y1 = cy - r2, x2 = cx + r2 + 1, y2 = cy + r2 + 1;
|
||
if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0;
|
||
if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres;
|
||
if (x2 <= x1 || y2 <= y1) return;
|
||
for (int py = y1; py < y2; py++)
|
||
for (int px = x1; px < x2; px++) {
|
||
int dx = px - cx, dy = py - cy;
|
||
int d2 = dx * dx + dy * dy;
|
||
if (d2 >= r1 * r1 && d2 <= r2 * r2) put_px(px, py, color);
|
||
}
|
||
dirty_add(x1, y1, x2 - x1, y2 - y1);
|
||
return;
|
||
}
|
||
if (sw == 0) return; /* 零弧长:不画(sweep=0;整环已在上面处理) */
|
||
/* 弧带:沿弧采样相邻点、粗线连接(圆帽)近似。方向:deg 右(0)→上(90)→左(180)→下(270),顺时针。 */
|
||
int n = sw / 2 + 1;
|
||
if (n < 2) n = 2;
|
||
int px = cx + (m_cos_deg(start_deg) * r) / 1024;
|
||
int py = cy - (m_sin_deg(start_deg) * r) / 1024; /* 屏幕 y 向下 → sin 取负 */
|
||
for (int i = 1; i <= n; i++) {
|
||
int deg = start_deg + sw * i / n;
|
||
int nx = cx + (m_cos_deg(deg) * r) / 1024;
|
||
int ny = cy - (m_sin_deg(deg) * r) / 1024;
|
||
meow_gfx_line(px, py, nx, ny, th, color);
|
||
px = nx; py = ny;
|
||
}
|
||
}
|
||
|
||
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; /* 上次画过的条长(清条时只清到它,避免残留) */
|
||
|
||
/* 顶部条区「快照恢复」:画条前把 [0..宽)×[hy..hy+MUI_HINT_H) 拍进小缓冲区;回缩/清空时
|
||
* 回填快照还原「真实底色」而非 th->bg —— 全屏页面底=主题 accent,若填 th->bg(黑) 会在
|
||
* accent 底上留下黑色残条(直到下次区域刷新才消失)。快照只在首现条时拍一次(后续 grow
|
||
* 不再覆盖快照),每行/全长布局全保留;缓冲为一次性分配(宽×H 行,480×5×2B≈4.8KB)。 */
|
||
static unsigned char *g_hint_snap = NULL; /* 快照(g_line×MUI_HINT_H 字节;NULL=未拍/分配失败) */
|
||
static int g_hint_snap_y = 0; /* 快照行的起始 y(本次手势时的 hy) */
|
||
|
||
/* 拍下「顶部条区」当前 back_buf 内容(只在 g_hint_last==0 的首现条时调用)。 */
|
||
static void hint_snapshot(void)
|
||
{
|
||
int hy = meow_ui_statusbar_height();
|
||
if (hy < 0) hy = 0;
|
||
g_hint_snap_y = hy;
|
||
if (hy + MUI_HINT_H > g_yres) return; /* 越界:放弃(回缩走 bg 兜底) */
|
||
size_t len = (size_t)MUI_HINT_H * (size_t)g_line;
|
||
if (!g_hint_snap) {
|
||
g_hint_snap = malloc(len);
|
||
if (!g_hint_snap) return;
|
||
}
|
||
memcpy(g_hint_snap, g_fb + (size_t)hy * g_line, len);
|
||
}
|
||
|
||
/* 从快照回填列区间 [x0,x1) 的 MUI_HINT_H 行(还原条下真实底色)。x0/x1 已夹到屏宽。 */
|
||
static void hint_restore(int x0, int x1)
|
||
{
|
||
if (x1 <= x0) return;
|
||
if (x0 < 0) x0 = 0;
|
||
if (x1 > g_xres) x1 = g_xres;
|
||
if (!g_hint_snap) { /* 无快照(分配失败):兜底填背景(旧行为) */
|
||
const meow_theme *th = meow_theme_get();
|
||
meow_ui_fill_rect(x0, g_hint_snap_y, x1 - x0, MUI_HINT_H, th->bg);
|
||
return;
|
||
}
|
||
int bpp = g_bpp / 8;
|
||
for (int r = 0; r < MUI_HINT_H; r++) {
|
||
size_t row = (size_t)(g_hint_snap_y + r) * g_line;
|
||
memcpy(g_fb + row + (size_t)x0 * bpp,
|
||
g_hint_snap + (size_t)r * g_line + (size_t)x0 * bpp,
|
||
(size_t)(x1 - x0) * bpp);
|
||
}
|
||
meow_gfx_mark_dirty(x0, g_hint_snap_y, x1 - x0, MUI_HINT_H); /* 标脏:调用方 flush_region 上屏 */
|
||
}
|
||
|
||
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 先把
|
||
* 超出部分还原为快照、x<=0 整条还原。x<0 视为清空;条长上限=屏宽。条色仍取 th->fg。 */
|
||
if (x > 0) {
|
||
if (x > g_xres) x = g_xres;
|
||
if (g_hint_last == 0) hint_snapshot(); /* 首现条:拍下未污染区,供此后回填 */
|
||
/* 往回滑(新长度 < 上次)时,把「上次画到旧长度、本次超出的 [x,old)」还原为快照
|
||
* 真实底色(全屏 accent),而非填 th->bg —— 否则左滑回缩在 accent 底上漏黑。 */
|
||
if (g_hint_last > x) hint_restore(x, g_hint_last);
|
||
meow_ui_fill_rect(0, hy, x, MUI_HINT_H, th->fg);
|
||
g_hint_last = x;
|
||
} else if (g_hint_last > 0) {
|
||
/* 手指回原点/抬手未达标/抬手达标:整条还原为快照真实底色(全屏 accent 上无黑残) */
|
||
hint_restore(0, g_hint_last);
|
||
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(); /* 顶栏显隐/上收变化 → 全帧重画 */
|
||
meow_ui_sbar_write_mode(); /* App 进程上报顶条模式(launcher 感知全屏/收合,agent-146) */
|
||
}
|
||
|
||
int meow_ui_statusbar_collapsed(void) { return g_sbar_collapsed; }
|
||
|
||
/* 当前上收进度(0..MUI_SBAR_H)。供 launcher 按「可见条高 = 28-offset」标脏切齐(agent-146)。 */
|
||
int meow_ui_statusbar_collapse_offset(void) { return g_sbar_collapse_h; }
|
||
|
||
/* 上收/恢复过渡是否仍在推进(current offset != target)。供 launcher 过渡期高频刷(agent-146)。 */
|
||
int meow_ui_statusbar_animating(void)
|
||
{
|
||
int tgt = g_sbar_collapsed ? MUI_SBAR_H : 0;
|
||
return g_sbar_collapse_h != tgt;
|
||
}
|
||
|
||
void meow_ui_statusbar_animate(int ms)
|
||
{
|
||
g_sbar_collapse_anim_ms = (ms > 0) ? ms : 0; /* 0=瞬切(基座默认) */
|
||
}
|
||
|
||
int meow_ui_statusbar_hotzone(void)
|
||
{
|
||
/* 通知中心触发(顶部下滑)热区:与渲染分离(agent-146 定案:收合 ≠ 禁用顶部热区)。
|
||
* 状态栏「激活」(非全屏隐藏,含收合)时热区恒 = 整条状态栏高 MUI_SBAR_H(28)——即使收合
|
||
* 也只上收渲染,顶部下滑仍可打开通知中心(渲染与热区事件分离)。
|
||
* 仅「全屏隐藏」(immersive,inset0)时缩小到 MEOW_SBAR_HOTZONE_FS(保留一处下拉入口)。 */
|
||
if (meow_ui_sys_statusbar_active())
|
||
return 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;
|
||
}
|
||
/* §6.1 ②:App 跨 Activity 全局控件(SDK 托管骨架)。在 active activity 内容之上、
|
||
* 全局状态栏之下绘制——跨 Activity 常驻,仅在有帧需上屏(已过帧率门控)时调用。
|
||
* render_cb 内的 meow_ui_* 绘制会自行标脏,随下方 dirty 列表一并上屏。 */
|
||
meow_ui_global_render();
|
||
/* 全局状态栏(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_render_enabled() && 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
|
||
/* 收合过渡/展开态:内容上移 g_sbar_collapse_h 像素并裁顶(yoff=0=整条)。
|
||
* 仅画可见条高 [0, 28-offset) 行,其余行=App 内容(launcher 按 statusbar_height 标脏
|
||
* 切齐),实现「平滑上移出屏」而不残留、不黑框。 */
|
||
meow_ui_sys_statusbar_render_off(g_sbar_collapse_h);
|
||
}
|
||
if (g_fb_mem) {
|
||
ioctl(g_fb_fd, FBIO_WAITFORVSYNC, 0); /* 等垂直同步 */
|
||
unsigned long long tbg = now_ms();
|
||
/* 「同一个状态栏」闭环:App 进程不渲染状态栏(meow_ui_sys_render_enabled()==0),也绝不向
|
||
* 真实 fb 写顶条 inset 区(顶条由 launcher 系统进程叠加绘制)。否则 App 首帧全量拷贝
|
||
* (meow_ui_init 置 dirty_full)会把 launcher 刚叠画的顶条冲成未初始化数据——真机
|
||
* 「进入 App 顶条黑 1 秒再出现」。sb_clip = 本进程当前状态栏 inset(展开=28 / 收合=4 /
|
||
* 隐藏=0):全量拷贝只拷 [sb, yres) 行;逐 dirty 矩形把上沿截断到 y>=sb,整块落在顶条区则跳过。
|
||
* 仅 App 进程(非系统 UI)截断;launcher(系统 UI)sb_clip=0,行为不变。 */
|
||
int sb_clip = 0;
|
||
if (!meow_ui_sys_render_enabled()) sb_clip = meow_ui_statusbar_height();
|
||
if (sb_clip < 0) sb_clip = 0;
|
||
if (sb_clip > g_yres) sb_clip = g_yres;
|
||
if (g_dirty_full) {
|
||
if (sb_clip > 0) {
|
||
memcpy(g_fb_mem + (size_t)sb_clip * g_line,
|
||
g_back_buf + (size_t)sb_clip * g_line,
|
||
(size_t)g_line * (size_t)(g_yres - sb_clip));
|
||
} else {
|
||
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], rh = g_dirty[i][3];
|
||
if (sb_clip > 0) {
|
||
if (ry + rh <= sb_clip) continue; /* 整块在顶条 inset 区:App 不写 */
|
||
if (ry < sb_clip) { /* 上沿与顶条区相交:截断到 sb_clip */
|
||
rh -= (sb_clip - ry);
|
||
ry = sb_clip;
|
||
if (rh <= 0 || rw <= 0) continue;
|
||
}
|
||
}
|
||
for (int yy = 0; yy < rh; 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/app_launcher.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; }
|
||
if (g_hint_snap) { free(g_hint_snap); g_hint_snap = 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; }
|