fix(statusbar): 全屏状态栏收合 — 上移出屏+停渲染+热区28保留+左缘指示条屏顶(agent-146)

- 根因: launcher/App 双进程各持SDK态, hello运行时全屏(set_fullscreen+set_collapsed)只改App进程, launcher仍expanded→画28px+时序闪
- SDK render_off(yoff): 顶条内容整体上移+裁顶, 收合 statusbar_collapsed_render 只画4px提示线(无时钟/图标/值)
- 热区保留: meow_ui_statusbar_hotzone() 收合时返回28(渲染与热区事件分离)→收合仍可下滑
- 左缘指示条: meow_ui_swipe_hint y=statusbar_height()=4 平移屏顶
- 模式感知: /tmp/meowapp_sbar(App进程SDK写0展开/1收合/2隐藏, launcher读); launch_app循环新变立即叠(消半秒闪)+过渡期~20ms高频刷(动画平滑)
- meow_ui.a=ed0267a9 launcher=7ea80408 hello=1a3ff13e 0warning; 宿主 test_sb_collapse/test_sb_statusbar ALL PASS
- 遗留: 全屏上收观感/收合下App前台打开通知中心归④M3(meow_ui_sys_notif_pull桩)
This commit is contained in:
MeowOS committed 2026-08-30 20:18:13 +08:00
1 parent 5ee99265d2
commit 1880aba139
5 files changed
+97 -20

No files matched your search

+10 -2
View File
@@ -223,10 +223,18 @@ int meow_ui_statusbar_height(void);
void meow_ui_statusbar_set_collapsed(int on); /* on=1 上收(内容上移出屏、只留提示线);on=0 恢复 */
int meow_ui_statusbar_collapsed(void); /* 当前是否处于「上收」目标(1=是) */
void meow_ui_statusbar_animate(int ms); /* TBD:上收/恢复过渡时长(ms);占位,后续代理实现插值 */
/* 当前上收进度(0..MUI_SBAR_H:0=完全展开,28=内容全部上移出屏、仅留提示线)。
* 供系统进程(launcher)在 App 前台叠加顶条时读取,按「当前可见条高 = 28-offset」
* 标脏/切齐,实现收合时只 blit 可见条高、其余行保留 App 内容(平滑上收、无残留)。 */
int meow_ui_statusbar_collapse_offset(void);
/* 上收/恢复过渡是否仍在进行(current offset != target,逐帧推进中)。
* 供 launcher 在过渡期高频刷新顶条(launch_app 循环据此缩短 sleep),驱动平滑动画;稳定后回 1s 慢刷。 */
int meow_ui_statusbar_animating(void);
/* 顶部下滑热区(下拉展开状态栏/通知)高度(px)。
* 非全屏=整条状态栏可视高度;全屏时缩小到 MEOW_SBAR_HOTZONE_FS(保留一处下拉入口)。
* 返回「可用于起始下拉」的热区高度;由输入层/后续代理据此判定下拉起点。 */
* 状态栏「激活」(含**收合**)时恒=整条状态栏高 MUI_SBAR_H(28)——收合只上收渲染、**不禁用顶部热区**
* (渲染与热区事件分离,全屏下顶部下滑仍开通知中心);仅「全屏隐藏」(immersive inset0)时缩小到
* MEOW_SBAR_HOTZONE_FS(保留一处下拉入口)。返回「可用于起始下拉」的热区高度。 */
int meow_ui_statusbar_hotzone(void);
#define MEOW_SBAR_HOTZONE_FS 4 /* 全屏时缩小后的顶部热区高度(px,草案值) */
+21 -5
View File
@@ -177,6 +177,7 @@ static void dirty_add(int x, int y, int w, int h)
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(); }
/* launcher(系统组件)与 SDK 共用同一 back_buf:launcher 的 fb 绘制指针指向它,
* 从而 launcher 与 app 统一渲染栈(同一离屏 + 同一 present)。 */
@@ -674,10 +675,21 @@ void meow_ui_statusbar_set_collapsed(int on)
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=瞬切(基座默认) */
@@ -685,11 +697,12 @@ void meow_ui_statusbar_animate(int ms)
int meow_ui_statusbar_hotzone(void)
{
/* 非全屏=整条状态栏可视;全屏时缩小到 MEOW_SBAR_HOTZONE_FS(保留一处下拉入口)。
* 上收(collapsed、非全屏)时热区=提示线下缘高度(MUI_SBAR_COLLAPSE_H,与渲染/inset 同口径)。
* 全屏态由 meow_ui_sys 维护。 */
/* 通知中心触发(顶部下滑)热区:与渲染分离(agent-146 定案:收合 ≠ 禁用顶部热区)。
* 状态栏「激活」(非全屏隐藏,含收合)时热区恒 = 整条状态栏高 MUI_SBAR_H(28)——即使收合
* 也只上收渲染,顶部下滑仍可打开通知中心(渲染与热区事件分离)。
* 仅「全屏隐藏」(immersive,inset0)时缩小到 MEOW_SBAR_HOTZONE_FS(保留一处下拉入口)。 */
if (meow_ui_sys_statusbar_active())
return g_sbar_collapsed ? MUI_SBAR_COLLAPSE_H : MUI_SBAR_H;
return MUI_SBAR_H;
return MEOW_SBAR_HOTZONE_FS;
}
@@ -731,7 +744,10 @@ static void present(void)
if (g_sbar_collapse_h >= MUI_SBAR_H)
statusbar_collapsed_render(g_sbar_collapse_h);
else
meow_ui_sys_statusbar_render();
/* 收合过渡/展开态:内容上移 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); /* 等垂直同步 */
+9
View File
@@ -40,6 +40,10 @@ int meow_ui_sys_render_enabled(void); /* 当前进程是否渲染系统顶
/* 把全局状态栏画进本进程 back_buf 顶部 MUI_SBAR_H 行(仅系统进程调用,见 render_enabled)。
* launcher 的 App 前台叠加(launch_app 循环)直接调它 + 标脏顶条上屏。实现见 meow_ui_sys.c。 */
void meow_ui_sys_statusbar_render(void);
/* 收合「上移出屏」过渡渲染:把顶条内容整体上移 yoff 像素并裁顶(y<0 处自然剪裁,meow_font/图标
* blit 已带边界钳制),仅画可见条高 [0, MUI_SBAR_H-yoff) 行(其上为 App 内容、由 launcher 保留)。
* yoff=0 等价 meow_ui_sys_statusbar_render()。实现见 meow_ui_sys.c。 */
void meow_ui_sys_statusbar_render_off(int yoff);
/* ---- 状态栏 MD3 图标簇(meow_ui_sys.c 渲染右侧;位图在 SDK 侧 sb_icons.h)----
* meow_ui_gfx.c 的 meow_gfx_blit_icon(idx,...) 用这些索引查 sb_icons[]。
@@ -97,6 +101,11 @@ void meow_gfx_mark_dirty(int x, int y, int w, int h);
/* 标记整帧脏(launcher 手动脏模型桥接:自绘 raw 像素后标全屏,present 走 SDK)。 */
void meow_gfx_mark_full(void);
/* 清空脏区(含整帧标记)。用于 App 前台叠加路径:meow_ui_statusbar_set_collapsed 会标整帧脏,
* 但 launcher 在 App 上只应 blit 顶条(可见条高),绝不能整帧 blit(会把自身 back_buf 的 stale
* 菜单内容盖到 App 内容上)——叠加前用本函数撤销整帧脏,再按需标局部脏(agent-146)。 */
void meow_gfx_dirty_reset(void);
/* 返回离屏 back_buf 指针(launcher 系统组件与 SDK 共用同一 back_buf 用)。 */
unsigned char *meow_ui_back_buf(void);
+48 -13
View File
@@ -25,6 +25,11 @@
#define MEOW_SETTINGS_FILE "/usr/data/meow/settings.conf"
/* App 前台顶条模式文件(App 进程 SDK 写,launcher 读)。与 /tmp/meowapp_state(immersive,launcher 写/
* watchdog 读)语义不同,独立文件避免冲突。值:'0'=展开 28px 整条;'1'=收合(提示线);'2'=隐藏(沉浸 inset0)。
* 仅 App 进程写(!g_sys_render);launcher(系统 UI)不写,只读。App 退出时由 launcher unlink。 */
#define MEOW_SBAR_MODE_FILE "/tmp/meowapp_sbar"
/* 蓝牙 powered 缓存刷新间隔(hciconfig 较贵) */
#define BT_REFRESH_MS 30000
@@ -190,6 +195,21 @@ void meow_ui_sys_set_system_mode(int on)
g_statusbar_force = 1; /* 切换即强制下次 present 反映(画/不画顶条) */
}
/* ---- App 前台顶条模式上报(agent-146 全屏收合接入)----
* 仅在 App 进程(!g_sys_render)写;launcher(system_mode=1)不写,避免覆盖 App 的状态。
* 由 meow_ui_set_fullscreen / meow_ui_statusbar_set_collapsed 调用,使 launcher(read 进程)
* 能感知 App 运行时的全屏/收合态,从而把新顶条切到收合(提示线)而非仍画 28px 整条。 */
void meow_ui_sbar_write_mode(void)
{
if (g_sys_render) return; /* 系统进程(launcher)不写 */
char m = meow_ui_statusbar_collapsed() ? '1'
: (g_fullscreen ? '2' : '0');
int fd = open(MEOW_SBAR_MODE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) return;
if (write(fd, &m, 1) < 0) { /* ignore write error */ }
close(fd);
}
int meow_ui_sys_render_enabled(void)
{
return g_sys_render;
@@ -200,6 +220,7 @@ void meow_ui_set_statusbar(int visible)
{
g_statusbar_on = visible ? 1 : 0;
g_statusbar_force = 1; /* 显隐切换也要重画顶条 */
meow_ui_sbar_write_mode(); /* App 进程上报顶条模式(launcher 感知全屏/收合) */
}
int meow_ui_statusbar_visible(void)
@@ -211,6 +232,7 @@ void meow_ui_set_fullscreen(int on)
{
g_fullscreen = on ? 1 : 0;
g_statusbar_force = 1; /* 进出全屏立即反映(显示/隐藏顶条) */
meow_ui_sbar_write_mode(); /* App 进程上报顶条模式(launcher 感知全屏/收合) */
}
int meow_ui_statusbar_height(void)
@@ -259,14 +281,14 @@ int meow_ui_sys_statusbar_force(void)
/* 画一个右对齐项:右边界=*xr,画完向左推进 *xr(含 MEOW_SB_XGAP 项间隙)。
* icon>=0 画图标(可配右侧数值 txt;txt==NULL 仅图标)。文字/图标同用主题色 col。
* ink_h 传给 meow_gfx_blit_icon(主图标用 MEOW_SB_ICON_INK 等大;WiFi 强弱档传 0 保持自然 ink)。
* 返回该项左边缘(供亮度可选判宽)。 */
static int sb_r_place(int *xr, int icon, const char *txt, uint32_t col, int ink_h, int ty)
* ycy = 图标 ink 垂直中心线(收合上移时随 -off 上移);ty = 文字 y。返回该项左边缘(供亮度可选判宽)。 */
static int sb_r_place(int *xr, int icon, const char *txt, uint32_t col, int ink_h, int ty, int ycy)
{
int iconw = (icon >= 0) ? MEOW_SB_ICON_SIZE : 0;
int gap = (icon >= 0 && txt) ? MEOW_SB_TGAP : 0;
int tw = txt ? meow_ui_text_width(txt, MUI_SBAR_FONT) : 0;
int x = *xr - (iconw + gap + tw);
if (icon >= 0) { meow_gfx_blit_icon(icon, x, 0, ink_h, col, MEOW_SB_ICON_CY); x += iconw + gap; }
if (icon >= 0) { meow_gfx_blit_icon(icon, x, 0, ink_h, col, ycy); x += iconw + gap; }
if (txt) meow_ui_text_right(*xr, ty, txt, MUI_SBAR_FONT, col);
*xr -= (iconw + gap + tw) + MEOW_SB_XGAP;
return *xr + MEOW_SB_XGAP;
@@ -285,19 +307,31 @@ static int sb_wifi_icon(void)
}
void meow_ui_sys_statusbar_render(void)
{
meow_ui_sys_statusbar_render_off(0);
}
void meow_ui_sys_statusbar_render_off(int yoff)
{
if (!g_sys_render) return; /* 仅系统进程(launcher)渲染;App 不画顶条 */
if (!meow_ui_sys_statusbar_active()) return;
if (yoff < 0) yoff = 0;
if (yoff > MUI_SBAR_H) yoff = MUI_SBAR_H;
const meow_theme *th = meow_theme_get();
int w = meow_ui_width();
int h = MUI_SBAR_H;
int vis = h - yoff; /* 收合上移:可见条高 = 28-yoff(其余行=App 内容,由 launcher 保留) */
if (vis <= 0) return; /* 完全收合由 statusbar_collapsed_render(提示线)处理 */
/* 整条清底:覆盖 app 可能画进顶行的内容(z 最顶)。仅系统进程(launcher)渲染本函数。 */
meow_ui_fill_rect(0, 0, w, h, th->bg);
/* 整条清底(仅可见条高 [0,vis) 行):覆盖 app 可能画进顶行的内容(z 最顶)。仅系统进程渲染本函数。 */
meow_ui_fill_rect(0, 0, w, vis, th->bg);
/* 文字垂直锚 y:(栏高-行高)/2 居中;下限 1 防极端行高 > 栏高时负 y 溢出顶端(不越界优先)。 */
/* 文字垂直锚 y:(栏高-行高)/2 居中;下限 1 防极端行高 > 栏高时负 y 溢出顶端(不越界优先)。
* 收合上移:内容整体上移 yoff 像素并裁顶(meow_font/图标 blit 已带 y<0 边界钳制,安全)。 */
int ty = (h - meow_ui_text_height(MUI_SBAR_FONT)) / 2;
if (ty < 1) ty = 1;
ty -= yoff;
int ycy = MEOW_SB_ICON_CY - yoff; /* 图标 ink 垂直中心随上移 */
/* 时钟(左):每秒现取 localtime */
char timebuf[16];
@@ -314,28 +348,29 @@ void meow_ui_sys_statusbar_render(void)
char val[16];
if (g_sys.batt >= 0) { /* 电量:图标+%(未知不画)——最右项 */
snprintf(val, sizeof val, "%d%%", g_sys.batt);
sb_r_place(&xr, MEOW_SB_ICON_BATT, val, col, MEOW_SB_ICON_INK, ty);
sb_r_place(&xr, MEOW_SB_ICON_BATT, val, col, MEOW_SB_ICON_INK, ty, ycy);
}
snprintf(val, sizeof val, "%d%%", g_sys.vol);
sb_r_place(&xr, MEOW_SB_ICON_VOL, val, col, MEOW_SB_ICON_INK, ty); /* 音量:图标+% */
sb_r_place(&xr, MEOW_SB_ICON_VOL, val, col, MEOW_SB_ICON_INK, ty, ycy); /* 音量:图标+% */
if (g_sys.bt_seen && g_sys.bt_on) { /* 蓝牙:仅图标(上电即显示,连接态占位;多态留后续) */
sb_r_place(&xr, MEOW_SB_ICON_BT_CONN, NULL, col2, MEOW_SB_ICON_INK, ty);
sb_r_place(&xr, MEOW_SB_ICON_BT_CONN, NULL, col2, MEOW_SB_ICON_INK, ty, ycy);
}
if (g_sys.wifi_on) { /* WiFi:仅图标(关不画;开未连/分档) */
int wco = sb_wifi_icon();
int is_weak = (wco == MEOW_SB_ICON_WIFI_MEDIUM || wco == MEOW_SB_ICON_WIFI_WEAK);
sb_r_place(&xr, wco, NULL, col, is_weak ? 0 : MEOW_SB_ICON_INK, ty);
sb_r_place(&xr, wco, NULL, col, is_weak ? 0 : MEOW_SB_ICON_INK, ty, ycy);
}
if (g_sys.brightness > 0) { /* 亮度:图标+%(>0 才显示;与时钟不重叠才画) */
int bl = (g_sys.brightness > 100) ? 100 : g_sys.brightness;
snprintf(val, sizeof val, "%d%%", bl);
int bw = MEOW_SB_ICON_SIZE + MEOW_SB_TGAP + meow_ui_text_width(val, MUI_SBAR_FONT) + MEOW_SB_XGAP;
if (xr - bw > 8 + meow_ui_text_width(timebuf, MUI_SBAR_FONT) + MEOW_SB_CLK_MARGIN)
sb_r_place(&xr, MEOW_SB_ICON_BRIGHT, val, col, MEOW_SB_ICON_INK, ty);
sb_r_place(&xr, MEOW_SB_ICON_BRIGHT, val, col, MEOW_SB_ICON_INK, ty, ycy);
}
/* 分隔线(栏内最底 1px,主题次要色);内容 inset=statusbar_height()=28 从 y=28 起即干净 */
meow_ui_fill_rect(0, h - 1, w, 1, th->dim);
/* 分隔线(栏内最底 1px,主题次要色);上移后仍在可见条高内;内容 inset=statusbar_height() 从该线之下即干净 */
int dy = h - 1 - yoff;
if (dy >= 0 && dy < vis) meow_ui_fill_rect(0, dy, w, 1, th->dim);
g_statusbar_force = 0;
}
+9
View File
@@ -76,4 +76,13 @@ int meow_ui_sys_statusbar_force(void);
/* 把状态栏画进 back_buf(顶部 MUI_SBAR_H 行)+ 标脏。调用方负责随后上屏。 */
void meow_ui_sys_statusbar_render(void);
/* 收合「上移出屏」过渡渲染(仅系统进程/launcher,见 meow_ui_sys_statusbar_render):内容整体上移
* yoff 像素并裁顶;只画可见条高 [0, MUI_SBAR_H-yoff) 行,其余行保留(App 内容由 launcher 标脏切齐)。
* yoff=0 等价 meow_ui_sys_statusbar_render()。实现见 meow_ui_sys.c。 */
void meow_ui_sys_statusbar_render_off(int yoff);
/* 把当前顶条模式写到 /tmp/meowapp_sbar(仅 App 进程 !g_sys_render 才写;launcher 读)。
* meow_ui_statusbar_set_collapsed(gfx.c)也调用它,故在头文件声明。 */
void meow_ui_sbar_write_mode(void);
#endif /* MEOW_UI_SYS_H */