通知中心增强(agent-126/127/128/130) + App内通知中心方案(agent-129)
- 开关平移动画(时间轴180ms可打断/快照memcpy/顶28不盖状态栏) agent-126 - 音量/亮度滑条复用设置页方案(跳步+上限clamp+写DAC去抖/状态栏带仅redraw_sb清绘) agent-127 - 通知列表滚动(方向随input.swipe_invert, 滚动≠关闭, 回弹才关) agent-128 - 去音量chip(静音↔恢复last_nz, NCP_CHIP_N 5→4: WiFi/蓝牙/主题/亮度) agent-130 - meow_ui.a 零改动(d249c6e6); launcher md5 c7893faf; 宿主 90+/0warn - ④App前台划出通知中心方案: FRAMEWORK_DESIGN §10.5 + NOTIFICATION_SYSTEM §9 + MEOW_APP_SDK §6.1(M3跨进程: SDK全局状态栏下滑唤出+半程可回+点通知拉app)
This commit is contained in:
1 parent
2b17f69f24
commit
a8ec8377ed
7 files changed
+535
-52
No files matched your search
@@ -47,6 +47,8 @@ struct ipc_hdr { /* LE */
|
||||
| 0x0021 | MSG_NOTIFY_ACK | ↓ | 落库结果 |
|
||||
| 0x0022 | MSG_NOTIFY_DISMISS | ↑ | 撤下通知 |
|
||||
| 0x0023 | MSG_NOTIFY_LIST | ↓ | 下发通知清单(重同步) |
|
||||
| 0x0025 | MSG_NOTIF_OPEN | ↑ | **(拟,号待 `meow_ipc.h` 回填)** App 检测到顶部下滑→请求 launcher 展开通知中心叠加会话(App 前台,见 §10.5) |
|
||||
| 0x0026 | MSG_NOTIF_CLOSE | ↑ | **(拟,号待 `meow_ipc.h` 回填,可选用途)** App 告知结束/退出→launcher 收尾兜底(见 §10.5) |
|
||||
| 0x0030 | MSG_ACT_DECLARE | ↑ | (重)声明路由表 |
|
||||
| 0x0031 | MSG_ACT_QUERY | ↑ | 解析 activity 链路 |
|
||||
| 0x0032 | MSG_ACT_RESULT | ↓ | 返回链路 |
|
||||
@@ -166,6 +168,37 @@ meow_sysui(系统进程总监,meow_svcd 托管;最小依赖/不渲染/最
|
||||
- **launcher 主界面 / 设置页 / 电源菜单全部迁到 SDK 控件**:逻辑/渲染走 SDK(MEOW_APP_SDK §6.1 ② App 跨 Activity 控件、§5.4 A① 分组设置表单、§5.7 B④ 面板等模板),MD3 化、日/夜主题(`display.theme_mode`)、全局 UI 统一。
|
||||
- **不让 app 自建叠加**:app 一律经 SDK 标准能力;系统级叠加(状态栏/通知中心/电源菜单/全局手势)只在系统进程内实现。迁移后 launcher 与 APP 用同一套 SDK 渲染地基,观感/生命周期统一。
|
||||
|
||||
### 10.5 App 前台划出通知中心(跨进程叠加会话)[源:2026-08-30]
|
||||
|
||||
> 定位:补齐「全屏 App(如 hello)前台时,App 进程内顶部下滑也能唤起**由 launcher 渲染**的通知中心」。
|
||||
> 详细实现方案见 `devdocs/plan/NOTIF_CENTER_APP_OVERLAY_PLAN.md`;本节为**定案决策**(归属/归属不变/协议/会话/故障隔离)。
|
||||
|
||||
- **交互与归属(不变)**:App 前台时顶部下滑,**App 进程只负责「检测手势 + 经 IPC 上报」**;通知中心
|
||||
**仍由 launcher 渲染**(符合 §10.2「系统 UI 归 launcher」/`MEOW_APP_SDK §6.1 ③`/`NOTIFICATION_SYSTEM §9`);
|
||||
**App 进程不渲染系统 UI**(不叠加状态栏/通知中心)。手势检测在 App 进程 SDK(`meow_ui_input.c` 已把
|
||||
「起点在顶部热区+向下主导」识别为系统保留手势 `MEOW_EV_NOTIF_PULL` → 路由 `meow_ui_sys_notif_pull()`)。
|
||||
- **跨进程握手(复用现有 IPC,不新增 socket)**:复用 `AF_UNIX /usr/data/meow_ipc.sock`(`meow_ipc.h
|
||||
MEOW_IPC_PATH`,launcher `ipc_thread` bind/listen)。拟新增上行消息:
|
||||
- `MSG_NOTIF_OPEN`(App→launcher):App 检测到顶部下滑→请求展开通知中心叠加会话;负载 `m_notif_open_t`
|
||||
(app_id/event_seq/origin_x/origin_y/hotzone/flags)。launcher 回 `MSG_ACK` 受理(best-effort,App 不强依赖)。
|
||||
- `MSG_NOTIF_CLOSE`(App→launcher,可选用途):App 告知结束/退出→launcher 收尾(清 active/SIGCONT 兜底)。
|
||||
- 号段/结构以 `meow_ipc.h` 回填为准(拟 `0x0025/0x0026`,见 §13 决策点)。
|
||||
- **launcher 叠加会话(复用电源菜单对 App 的冻结模板)**:收到 `MSG_NOTIF_OPEN`(且当前在 App 前台会话,
|
||||
`launch_app` waitpid 循环内)→ **SIGSTOP App** → 读回 App 帧(`memcpy` fb0 buffer0 → back_buf,App 帧恒
|
||||
buffer0 不走 `fb_backup_player`;同 `pm_capture_app_frame`)→ 写 `/tmp/meow_notif_active`(同
|
||||
`/tmp/meow_power_menu_active`,让 watchdog 让位)→ 叠加渲染 `draw_notif_center` + `meow_ui_flush` →
|
||||
**接管输入**(`open(DEV_TOUCH)`+`kp_open`,App 已冻不影响)→ 关闭(上滑/点空白/左缘左滑/点通知/物理键)→
|
||||
恢复 App 帧 → `unlink(active)` → **SIGCONT App** → 重播输入基线。差异:背景=App 帧(非应用列表);
|
||||
关闭后回 App(非回 run_menu)。
|
||||
- **App 崩溃/卡死**:launcher 会话子循环周期用 `waitpid(pid,&st,WNOHANG)`/`kill(pid,0)` 探测;App 死→立即结束会话
|
||||
(不 SIGCONT 死进程、不恢复死帧),走既有**统一应用退出路径**归位;launcher 不崩(`kill(pid,0)` 存活检测
|
||||
与 watchdog 让位语义同 `meow_launcherd.c` ~8533-8555)。
|
||||
- **全屏/沉浸不冲突**:全屏/收合时 SDK 顶部热区缩小为 `MEOW_SBAR_HOTZONE_FS=4`(或提示线高),**仍保留顶部
|
||||
一处下拉入口**(`meow_ui_statusbar_hotzone()`),spec-ref §3.1/A-13/§5.1 已定「全屏下顶部下滑仍开通知中心」。
|
||||
- **与 M3 关系**:本方案是 M3「前台会话」的一个切面(App 前台会话+跨进程叠加)。**本方案先行**(握手+叠加会话+
|
||||
故障隔离,不依赖 M3);通知点击 `target_activity` 的 activity 级跳转 / 点击通知**跨 app 启动** / nav_stack 回退
|
||||
**留 M3**(首期:点通知 = dismiss + 关面板 + SIGCONT 回当前 App)。
|
||||
|
||||
---
|
||||
|
||||
## 附录 A(用户追加):后台运行 / 后台播放权限
|
||||
|
||||
@@ -552,6 +552,10 @@ void meow_ui_global_remove(int id);
|
||||
- **SDK 提供系统组件 API 仅供系统进程链接调用**:SDK 保留系统级控件内部接口位(上面 ③);**用户 App 拿不到**、**不可自建悬浮层/全局叠加**——App 这类需求只能走 SDK 标准能力。
|
||||
- **进程隔离(核心)**:SDK 负责**系统组件渲染**的进程(launcher)与负责 **APP 渲染**的进程**相互独立**——APP 卡死/崩溃时,系统 UI(launcher)**不崩**,始终可息屏/返回/下拉通知中心(对应 APPS §3.4.1 物理键全局接管、APP 禁映射)。
|
||||
- **APP 侧职责**:只画自己的内容;经 IPC 上报**全屏声明**(`fullscreen`/`route.<id>.fullscreen`)、**内容区域**(inset)与**前台 activity**;系统 UI 据此做叠画 + 命中分流(§6.1 交互分层:③ 系统全局 → ② App 全局 → ① 当前 Activity 页面控件)。
|
||||
- **App 前台划出通知中心(跨进程,[源:2026-08-30])**:全屏 App 前台时顶部下滑——**App 进程 SDK 只检测手势
|
||||
(`MEOW_EV_NOTIF_PULL`→`meow_ui_sys_notif_pull()`)并经 IPC 上报**(`MSG_NOTIF_OPEN`),**不渲染系统 UI**;
|
||||
通知中心由 launcher 渲染(SIGSTOP App+读回其帧+叠加 `run_notif_center`+接管输入+关闭恢复),
|
||||
见 `FRAMEWORK_DESIGN §10.5` / `devdocs/plan/NOTIF_CENTER_APP_OVERLAY_PLAN.md`。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -73,6 +73,10 @@ target_activity(可选, 来自 hints) / progress(可选 int) / urgency(0-3, 缺
|
||||
- **交互**:**状态栏下滑展开**(用户已定);**上滑 / 点面板外关**;**左缘返回关**(与全局左缘返回/顶部滑条按起始位置分流,见 spec-ref §5.1)。列表显示通知(app 图标 + summary + body + target Activity),点击按路由表解析跳转(M3 导航栈),下拉关闭/清除。
|
||||
- **协议/约束**:遵守 freedesktop 字段(summary/body/app_icon/actions/hints/expire_timeout/replaces_id)、配额(每 App 4 类型×每类型 4、媒体单例)、TTL、转储(10s + `MeowOS/notifications/` 10MB ring)、`urgency`/`icon`/`progress`(见 §5-§8)。
|
||||
- **依赖**:M2 存储 + M3 导航栈(点击跳转)+ SDK 控件基座。**阶段 B 进行中。**(叠加/与播放器帧备份的互斥边界见 DEV_NOTES #16。)
|
||||
- **App 前台变体(跨进程,[源:2026-08-30])**:全屏 App 前台时,App 进程内顶部下滑也能唤起通知中心——**App
|
||||
进程只检测手势并经 IPC 上报**(`MSG_NOTIF_OPEN`,见 `FRAMEWORK_DESIGN §10.5`),**通知中心仍由 launcher
|
||||
渲染**(SIGSTOP App+读回其帧+叠加 `run_notif_center`+接管输入+关闭恢复),符合本节「渲染主体=系统进程(launcher)」。
|
||||
仍遵守 freedesktop 字段/配额/TTL/urgency/icon/progress(§5-§8),数据在 launcher `meow_notif` 直读,不跨进程传全量。
|
||||
|
||||
## 10. 分步实施建议
|
||||
1. **M2a 通知存储 + 原生 IPC**:按上面的配额(category 级)+ TTL 10s 转储钩子 + 系统事件源(系统作 App)。→ 可先落地。
|
||||
|
||||
Binary file not shown.
@@ -6779,7 +6779,7 @@ done:
|
||||
#define NCP_CHIP_H 44
|
||||
#define NCP_CHIP_W 70
|
||||
#define NCP_CHIP_GAP 8
|
||||
#define NCP_CHIP_N 5
|
||||
#define NCP_CHIP_N 4
|
||||
#define NCP_SLIDER_X 20
|
||||
#define NCP_SLIDER_W (fb_xres - 40)
|
||||
#define NCP_SL1_Y 162 /* 亮度滑条 track 顶(label 在 -26 =136) */
|
||||
@@ -6795,10 +6795,16 @@ done:
|
||||
#define NCP_TEXT_W (NCP_ROW_W - (NCP_TEXT_X - NCP_ROW_X) - 10)
|
||||
#define NCP_FOOT_Y 700
|
||||
#define NCP_MAX_ROWS 24 /* 面板一次缓冲的通知行上限(防御) */
|
||||
#define NCP_SCROLL_REBOUND_TOL 48 /* px:列表拖到顶/底仍继续拖过此容差 → 判回弹(关闭语义),
|
||||
避免在边界小幅抖动即误关(> S_SCROLL_SLOP=40) */
|
||||
|
||||
/* ---- 开关平移动画(§9:状态时间轴 + 快照 blit 平移,不整屏重绘)---- */
|
||||
#define NCP_ANIM_FIX 1024 /* 进度 p 的定点分母(1/1024 精度) */
|
||||
#define NCP_ANIM_MS 180 /* 单程打开/关闭时长(ms,真机可调) */
|
||||
|
||||
/* chip 索引 */
|
||||
enum { NCP_CHIP_WIFI=0, NCP_CHIP_BT=1, NCP_CHIP_THEME=2, NCP_CHIP_BRIGHT=3,
|
||||
NCP_CHIP_VOL=4, NCP_CHIP_COUNT=5 };
|
||||
NCP_CHIP_COUNT=4 };
|
||||
|
||||
/* 通知行快照(面板读取字段 + 缓存的 icon 句柄)。 */
|
||||
typedef struct {
|
||||
@@ -6892,6 +6898,86 @@ static int ncp_rows_changed(void)
|
||||
return c != g_nrows;
|
||||
}
|
||||
|
||||
/* ---- 开关平移动画辅助(§9:快照 blit + 平移;只在 run_notif_center 内分配/释放)----
|
||||
* g_ncp_ph = 面板高 PH = fb_yres - NCP_TOP;两块快照各 PH × fb_line 字节:
|
||||
* bg_snap = 进入面板时(绘制面板前)从 back_buf 行 [NCP_TOP, NCP_TOP+PH) 拍的背景(应用列表);
|
||||
* panel_snap= 最终位置整面板渲染(draw_notif_center 后拍),稳态重绘后刷新。
|
||||
* 快照不可用(分配失败/无 fb)时动画降级为瞬时显示/消失(fallback)。 */
|
||||
static unsigned char *g_ncp_bg_snap = NULL;
|
||||
static unsigned char *g_ncp_panel_snap = NULL;
|
||||
static int g_ncp_ph = 0;
|
||||
|
||||
static void ncp_snap_init(void)
|
||||
{
|
||||
int ph = fb_yres - NCP_TOP;
|
||||
if (ph <= 0 || fb_line <= 0) { g_ncp_ph = 0; return; }
|
||||
size_t bsz = (size_t)ph * (size_t)fb_line;
|
||||
g_ncp_bg_snap = malloc(bsz);
|
||||
g_ncp_panel_snap = malloc(bsz);
|
||||
if (!g_ncp_bg_snap || !g_ncp_panel_snap) {
|
||||
free(g_ncp_bg_snap); g_ncp_bg_snap = NULL;
|
||||
free(g_ncp_panel_snap); g_ncp_panel_snap = NULL;
|
||||
g_ncp_ph = 0;
|
||||
return;
|
||||
}
|
||||
g_ncp_ph = ph;
|
||||
}
|
||||
static void ncp_snap_free(void)
|
||||
{
|
||||
free(g_ncp_bg_snap); g_ncp_bg_snap = NULL;
|
||||
free(g_ncp_panel_snap); g_ncp_panel_snap = NULL;
|
||||
g_ncp_ph = 0;
|
||||
}
|
||||
|
||||
/* 从 back_buf 行 [NCP_TOP, NCP_TOP+PH) 拷贝到 dst(行宽 fb_line)。 */
|
||||
static void ncp_snap_copy_rows(unsigned char *dst, const unsigned char *base)
|
||||
{
|
||||
if (!dst || g_ncp_ph <= 0) return;
|
||||
size_t row = (size_t)fb_line;
|
||||
for (int i = 0; i < g_ncp_ph; i++)
|
||||
memcpy(dst + (size_t)i * row, base + (size_t)i * row, row);
|
||||
}
|
||||
/* 拍背景(应用列表):进入面板时、面板绘制前调用。 */
|
||||
static void ncp_snap_bg(void)
|
||||
{
|
||||
ncp_snap_copy_rows(g_ncp_bg_snap, fb + (size_t)NCP_TOP * fb_line);
|
||||
}
|
||||
/* 拍最终整面板:draw_notif_center 画完后 / 每次稳态重绘后调用。 */
|
||||
static void ncp_snap_panel(void)
|
||||
{
|
||||
ncp_snap_copy_rows(g_ncp_panel_snap, fb + (size_t)NCP_TOP * fb_line);
|
||||
}
|
||||
|
||||
/* 重建动画帧:把 back_buf 面板区 [NCP_TOP..) 还原为「背景 + 平移后的面板」。
|
||||
* off∈[-PH,0];prev_bot=上一帧覆盖底缘(exclusive),用于关(收窄)时恢复新露出背景条。
|
||||
* 返回本帧覆盖底缘(exclusive,供调用方算脏区与传给下次)。 */
|
||||
static int ncp_anim_rebuild(int off, int prev_bot)
|
||||
{
|
||||
if (!g_ncp_bg_snap || !g_ncp_panel_snap || g_ncp_ph <= 0) return NCP_TOP;
|
||||
int top = NCP_TOP;
|
||||
int ph = g_ncp_ph;
|
||||
int cur_bot = mnp_anim_covered_bot(top, ph, off); /* ∈[top, top+ph](§9.3 裁切) */
|
||||
if (cur_bot < top) cur_bot = top;
|
||||
if (cur_bot > top + ph) cur_bot = top + ph;
|
||||
size_t row = (size_t)fb_line;
|
||||
/* 1) 面板收窄(收回中):[cur_bot, prev_bot) 新露出 → 恢复背景(应用列表)。 */
|
||||
if (prev_bot > cur_bot) {
|
||||
int b0 = prev_bot < top + ph ? prev_bot : top + ph;
|
||||
for (int sy = cur_bot; sy < b0; sy++)
|
||||
memcpy(fb + (size_t)sy * row,
|
||||
g_ncp_bg_snap + (size_t)(sy - top) * row, row);
|
||||
}
|
||||
/* 2) 贴平移面板:[top, cur_bot) 从 panel_snap 行 yy = sy-(top+off) 拷回。 */
|
||||
for (int sy = top; sy < cur_bot; sy++) {
|
||||
int yy = sy - (top + off); /* ∈[0, ph)(见 §9.3 裁切论证) */
|
||||
if (yy < 0) yy = 0;
|
||||
if (yy >= ph) yy = ph - 1;
|
||||
memcpy(fb + (size_t)sy * row,
|
||||
g_ncp_panel_snap + (size_t)yy * row, row);
|
||||
}
|
||||
return cur_bot;
|
||||
}
|
||||
|
||||
/* 把文本按宽度截断(UTF-8 安全:不切开多字节码),超出加 "..."。 */
|
||||
static void ncp_text(int x, int y, const char *s, int size, uint32_t color, int maxw)
|
||||
{
|
||||
@@ -6937,7 +7023,7 @@ static void ncp_sb_icon(int icon, int slot_x, int cy, uint32_t col, int ink_h)
|
||||
g_sb_icon_cy = saved;
|
||||
}
|
||||
|
||||
/* chip 位置(5 个,居中排布)。返回下标对应 chip 的 x 左缘。 */
|
||||
/* chip 位置(4 个,居中排布)。返回下标对应 chip 的 x 左缘。 */
|
||||
static int ncp_chip_x(int chip)
|
||||
{
|
||||
int total = NCP_CHIP_N * NCP_CHIP_W + (NCP_CHIP_N - 1) * NCP_CHIP_GAP;
|
||||
@@ -6950,7 +7036,7 @@ static int ncp_chip_hit(int chip, int tx, int ty)
|
||||
return (tx >= x && tx < x + NCP_CHIP_W && ty >= NCP_CHIP_Y && ty < NCP_CHIP_Y + NCP_CHIP_H);
|
||||
}
|
||||
|
||||
/* 面板当前 5 个 chip 的状态(① 能否写入/取值在 meow_settings_*;② 显示用)。 */
|
||||
/* 面板当前 4 个 chip 的状态(① 能否写入/取值在 meow_settings_*;② 显示用)。 */
|
||||
static void ncp_chip_state(int chip, int *on, char *val, int vlen)
|
||||
{
|
||||
*on = 0;
|
||||
@@ -6971,10 +7057,6 @@ static void ncp_chip_state(int chip, int *on, char *val, int vlen)
|
||||
int vc = mnp_brightness_clamp(v, vmin);
|
||||
if (val && vlen > 0) snprintf(val, vlen, "%d%%", vc);
|
||||
*on = (vc >= 50); /* 过半视为"亮"态 */
|
||||
} else if (chip == NCP_CHIP_VOL) {
|
||||
int v = 0; meow_settings_get_int("audio.volume", &v);
|
||||
if (val && vlen > 0) snprintf(val, vlen, "%d%%", v);
|
||||
*on = (v > 0); /* 非静音=开 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7016,15 +7098,6 @@ static int ncp_chip_apply(int chip)
|
||||
g_sys.brightness = vmin;
|
||||
}
|
||||
paint_sb_right();
|
||||
} else if (chip == NCP_CHIP_VOL) {
|
||||
/* 快捷音量:静音<->恢复上一次非零音量。 */
|
||||
static int last_nz = 60;
|
||||
int cur = 0; meow_settings_get_int("audio.volume", &cur);
|
||||
if (cur > 0) { last_nz = vol_clamp(cur); meow_settings_set_int("audio.volume", 0); }
|
||||
else { meow_settings_set_int("audio.volume", vol_clamp(last_nz)); }
|
||||
g_sys.vol = settings_volume_get();
|
||||
paint_sb_right();
|
||||
meow_dac_vol_flush(); /* 抬手=无条件直写真实 DAC */
|
||||
}
|
||||
meow_settings_save();
|
||||
return 0;
|
||||
@@ -7085,6 +7158,18 @@ static void ncp_draw_list(int scroll)
|
||||
}
|
||||
}
|
||||
|
||||
/* 仅重绘列表可见区(滚动专用;不整面板、不刷状态栏)。先清列表底(面板 surface_variant),
|
||||
* 再画可见行 + 滚动条;只标列表带脏([NCP_LIST_TOP, NCP_LIST_BOT)),面板体其它控件 / 状态栏带
|
||||
* [0,NCP_TOP) 均不触碰 —— 满足「滚动只重绘列表可视区,绝不整屏」。 */
|
||||
static void ncp_repaint_list(int scroll)
|
||||
{
|
||||
const meow_ui_theme_t *th = s_ui_theme();
|
||||
fill_rect(0, NCP_LIST_TOP, fb_xres, NCP_LIST_BOT - NCP_LIST_TOP, th->surface_variant);
|
||||
ncp_draw_list(scroll);
|
||||
meow_dirty_add(0, NCP_LIST_TOP, fb_xres, NCP_LIST_BOT - NCP_LIST_TOP);
|
||||
meow_ui_flush();
|
||||
}
|
||||
|
||||
/* 绘制整个通知中心面板(画进 SDK back_buf;redraw_sb=1 时另绘状态栏在最顶)。
|
||||
* 性能:面板体单独;(0..27) 状态栏只在打开/主题变化时 redraw_sb=1 重绘,交互时跳过,
|
||||
* 使脏区仅限面板体,绝不整屏重绘。 */
|
||||
@@ -7092,8 +7177,11 @@ static void draw_notif_center(int scroll, int redraw_sb)
|
||||
{
|
||||
const meow_ui_theme_t *th = s_ui_theme();
|
||||
int W = fb_xres, H = fb_yres;
|
||||
fill_rect(0, 0, W, H, th->surface);
|
||||
/* 面板体(状态栏之下 surface_variant) */
|
||||
/* 状态栏带 [0,NCP_TOP) 仅当 redraw_sb=1(打开/主题变化)时清底,随后由下方 draw_statusbar()
|
||||
* 覆盖重绘;redraw_sb=0(交互重绘,如拖滑条)时**绝不触碰**——保留 back_buf 中 paint_sb_right()
|
||||
* 已绘好的状态栏,避免被清成 surface 底仍被 blit → 状态栏数值闪烁/消失(真机①修复)。
|
||||
* 面板体(状态栏之下 surface_variant)态恒整片铺。 */
|
||||
if (redraw_sb) fill_rect(0, 0, W, NCP_TOP, th->surface);
|
||||
fill_rect(0, NCP_TOP, W, H - NCP_TOP, th->surface_variant);
|
||||
|
||||
/* 把手 */
|
||||
@@ -7104,7 +7192,7 @@ static void draw_notif_center(int scroll, int redraw_sb)
|
||||
ui_draw(W - 24 - ui_width("点空白关闭", 16), NCP_TITLE_Y + 2, "点空白关闭", th->on_surface_variant, 16);
|
||||
|
||||
/* 快捷开关行 */
|
||||
static const char *chip_lbl[NCP_CHIP_COUNT] = { "WiFi", "蓝牙", "主题", "亮度", "音量" };
|
||||
static const char *chip_lbl[NCP_CHIP_COUNT] = { "WiFi", "蓝牙", "主题", "亮度" };
|
||||
int total_chip = NCP_CHIP_N * NCP_CHIP_W + (NCP_CHIP_N - 1) * NCP_CHIP_GAP;
|
||||
int chip_x0 = (W - total_chip) / 2;
|
||||
for (int c = 0; c < NCP_CHIP_N; c++) {
|
||||
@@ -7122,10 +7210,9 @@ static void draw_notif_center(int scroll, int redraw_sb)
|
||||
else { ncp_dot(icon_cx + 16, icon_cy, 11, fg); ncp_dot(icon_cx + 16, icon_cy, 6, bg); }
|
||||
}
|
||||
else if (c == NCP_CHIP_BRIGHT) ncp_sb_icon(SB_I_BRIGHT, icon_cx, icon_cy, fg, 22);
|
||||
else if (c == NCP_CHIP_VOL) ncp_sb_icon(SB_I_VOL, icon_cx, icon_cy, fg, 22);
|
||||
char label[24];
|
||||
snprintf(label, sizeof label, "%s", chip_lbl[c]);
|
||||
if (val[0] && (c == NCP_CHIP_BRIGHT || c == NCP_CHIP_VOL))
|
||||
if (val[0] && c == NCP_CHIP_BRIGHT)
|
||||
snprintf(label, sizeof label, "%.9s", val); /* chip 下示值 */
|
||||
ui_draw(cx + (NCP_CHIP_W - ui_width(label, 14)) / 2, NCP_CHIP_Y + NCP_CHIP_H - 18,
|
||||
label, fg, 14);
|
||||
@@ -7172,25 +7259,37 @@ static int ncp_slider_band(int tx, int ty, int sy)
|
||||
ty >= sy - 30 && ty <= sy + 24);
|
||||
}
|
||||
|
||||
/* 滑条值应用(拖动实时 set_*,save_now=抬手落盘;亮度 clamp、音量 clamp)。 */
|
||||
static void ncp_slider_apply(int which, int tx, int save_now)
|
||||
/* 滑条值应用(拖动实时 set_*,save_now=抬手落盘)。
|
||||
* 复用设置页方案:亮度 clamp [bmin,100];音量 vol_clamp 上限 + 跳步吸附 + DAC 节流去抖。
|
||||
* 返回 1=值实际变更(需重绘面板/刷状态栏);0=吸附+钳制后与当前已提交值相同(懒跳过:
|
||||
* 不写 DAC/背光、不刷状态栏、不标脏)→ 拖动去抖,消除逐事件刷状态栏/DAC 的卡顿(真机②修复)。
|
||||
* 亮度写背光仅 sysfs 不引额外节流;音量写 DAC 节流(meow_dac_vol_write_throttled/flush 同 s_commit)。 */
|
||||
static int ncp_slider_apply(int which, int tx, int save_now)
|
||||
{
|
||||
if (which == 1) {
|
||||
int bmin = 1; meow_settings_get_int("display.brightness_min", &bmin);
|
||||
int v = mnp_slider_val_from_x(tx, NCP_SLIDER_X, NCP_SLIDER_W, bmin, 100);
|
||||
v = mnp_brightness_clamp(v, bmin); /* 下限 brightness_min(≥1)、上限 100 */
|
||||
int cur = 0; meow_settings_get_int("display.brightness", &cur);
|
||||
if (!save_now && v == mnp_brightness_clamp(cur, bmin)) return 0; /* 去抖:值未变(含 clamp 后) */
|
||||
meow_settings_set_int("display.brightness", v);
|
||||
write_brightness(v);
|
||||
g_sys.brightness = v;
|
||||
g_sys.brightness = v; /* 状态栏数值源=最近写入值(含 clamp 后) */
|
||||
paint_sb_right();
|
||||
if (save_now) meow_settings_save();
|
||||
} else if (which == 2) {
|
||||
int v = mnp_slider_val_from_x(tx, NCP_SLIDER_X, NCP_SLIDER_W, 0,
|
||||
vol_clamp(settings_volume_max));
|
||||
return 1;
|
||||
} else {
|
||||
int vmax = vol_clamp(settings_volume_max); /* 音量上限(尊重 audio.volume_max) */
|
||||
int v = mnp_slider_val_from_x(tx, NCP_SLIDER_X, NCP_SLIDER_W, 0, vmax);
|
||||
v = mnp_step_round(v, settings_volume_step); /* 跳步吸附(默认 step=1 → 连续同设置页) */
|
||||
v = vol_clamp(v); /* 0..MEOW_VOL_MAX + 上限 clamp */
|
||||
if (!save_now && v == settings_volume_get()) return 0; /* 去抖:值未变跳过写 DAC/刷状态栏 */
|
||||
meow_settings_set_int("audio.volume", v);
|
||||
g_sys.vol = settings_volume_get();
|
||||
g_sys.vol = settings_volume_get(); /* 状态栏数值源=最近写入值(含 clamp 后) */
|
||||
paint_sb_right();
|
||||
if (save_now) { meow_dac_vol_flush(); meow_settings_save(); }
|
||||
else meow_dac_vol_write_throttled();
|
||||
if (save_now) { meow_dac_vol_flush(); meow_settings_save(); } /* 抬手=无条件直写 DAC */
|
||||
else meow_dac_vol_write_throttled();/* 拖动中=墙钟节流写 DAC */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7243,7 +7342,14 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
|
||||
|
||||
int td = 0, td_ok = 0, tdx = -1, tdy = -1, tlx = -1, tly = -1;
|
||||
int drag_slider = 0; /* 0=无, 1=亮度, 2=音量 */
|
||||
int closed = 0; /* 本次触发放置:关面板(跳回 run_menu) */
|
||||
/* 通知列表拖动滚动(方向遵循 input.swipe_invert,与设置页列表同式;关闭手势区分见 §11.3) */
|
||||
int scroll_drag = 0; /* 是否处于列表区拖动滚动手势 */
|
||||
int scroll_base = 0; /* 进入滚动时锁定的 list_scroll 基准(连续跟手,防触发瞬间跳变) */
|
||||
int scroll_y0 = 0; /* 按下原点纵向坐标 */
|
||||
int scroll_invert = 0; /* input.swipe_invert(0=标准方向) */
|
||||
int scroll_rebound = 0; /* 拖到顶/底仍继续拖(回弹)→ 抬手按关闭语义收起 */
|
||||
int list_paint = 0; /* 仅列表区重绘(滚动专用,不整面板/不刷状态栏) */
|
||||
int closed = 0; /* 非动画路径的兜底关面板(快照不可用时瞬时退出) */
|
||||
int after_modal = 0; /* 电源菜单返回:整帧重绘还原被 dim 的状态栏 */
|
||||
int wake_suppress = 0;
|
||||
unsigned long long pwr_hold_at = 0;
|
||||
@@ -7251,17 +7357,42 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
|
||||
|
||||
int need_sb = 1; /* 打开/主题变化时重绘状态栏;其余交互跳过(脏区只限面板体) */
|
||||
|
||||
/* 首次绘制 */
|
||||
/* 开关平移动画状态(§9):anim_p∈[0,NCP_ANIM_FIX],anim_target 为意图(FIX=开、0=关)。
|
||||
* 仅当快照可用(anim_ok)才做平滑动画;否则降级为瞬时显示/消失(closed 兜底)。 */
|
||||
int anim_p = 0, anim_target = NCP_ANIM_FIX;
|
||||
int anim_last_target = NCP_ANIM_FIX; /* 记录上一步 anim_target(用于检测「意图切换」重置计时) */
|
||||
unsigned long long anim_last = 0;
|
||||
int anim_prev_bot = NCP_TOP; /* 上一帧面板覆盖底缘(exclusive;初始=NCP_TOP=未覆盖) */
|
||||
int anim_ok = 0;
|
||||
|
||||
/* 首次绘制:先拍背景(应用列表)→ 渲染最终位置整面板 → 拍面板快照 → 还原背景到 p=0
|
||||
* 起点(面板隐藏、先露应用列表),随后进入循环由动画推进 p:0→FIX 滑入。 */
|
||||
ncp_refresh_rows();
|
||||
list_scroll = mnp_scroll_clamp(0, g_nrows * NCP_ROW_H, NCP_LIST_BOT - NCP_LIST_TOP);
|
||||
ncp_snap_init();
|
||||
if (g_ncp_ph > 0) {
|
||||
anim_ok = 1;
|
||||
ncp_snap_bg(); /* 背景(应用列表)在面板绘制前拍 */
|
||||
draw_notif_center(list_scroll, 1); /* 渲染最终位置整面板 → back_buf */
|
||||
ncp_snap_panel(); /* 拍面板快照 */
|
||||
/* 起点 p=0:按「上一帧为全覆盖(NCP_TOP+PH)」重建 → 整段还原为背景(面板隐藏) */
|
||||
anim_prev_bot = ncp_anim_rebuild(mnp_anim_off(0, NCP_ANIM_FIX, g_ncp_ph),
|
||||
NCP_TOP + g_ncp_ph);
|
||||
meow_dirty_add(0, NCP_TOP, fb_xres, fb_yres - NCP_TOP);
|
||||
meow_ui_flush();
|
||||
} else {
|
||||
draw_notif_center(list_scroll, 1);
|
||||
meow_dirty_full(); meow_ui_flush();
|
||||
}
|
||||
need_sb = 0;
|
||||
anim_last = now_ms();
|
||||
|
||||
int need_paint = 0; /* 持久:跨 tick 保持(动画进行中延迟到稳态再画) */
|
||||
|
||||
for (;;) {
|
||||
unsigned long long now = now_ms();
|
||||
if (sigterm_flag) break;
|
||||
int need_paint = 0;
|
||||
int animating = anim_ok && (anim_p != anim_target);
|
||||
|
||||
/* 自动息屏(同设置页语义) */
|
||||
if (meow_auto_slept && !meow_screen_on) {
|
||||
@@ -7295,8 +7426,15 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
|
||||
m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE);
|
||||
touch_cooldown_until = now_ms() + 300;
|
||||
td = 0; td_ok = 0; drag_slider = 0; tlx = tly = -1;
|
||||
scroll_drag = 0; scroll_base = 0; scroll_y0 = 0;
|
||||
scroll_invert = 0; scroll_rebound = 0; list_paint = 0;
|
||||
/* 唤醒:面板直接就位(不重播滑入动画,面板早已打开);重拍快照防内容已变 */
|
||||
anim_p = anim_target = anim_last_target = NCP_ANIM_FIX;
|
||||
if (anim_ok) anim_prev_bot = NCP_TOP + g_ncp_ph;
|
||||
draw_notif_center(list_scroll, 1);
|
||||
if (anim_ok) ncp_snap_panel();
|
||||
meow_dirty_full(); meow_ui_flush();
|
||||
anim_last = now_ms();
|
||||
need_sb = 0;
|
||||
}
|
||||
usleep(POLL_MS * 1000);
|
||||
@@ -7376,6 +7514,9 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
|
||||
|
||||
/* 触摸 */
|
||||
if (tfd >= 0 && meow_screen_on) {
|
||||
/* 面板是否就位(稳态开):就位才允许控件交互;动画中抑制 chip/滑条/通知行命中
|
||||
* (面板在滑,控件坐标已定但只部分可见,命中会误触——见 §9.5)。 */
|
||||
int open_settled = anim_ok ? (anim_target >= NCP_ANIM_FIX && anim_p >= NCP_ANIM_FIX) : 1;
|
||||
struct pollfd pfd = { tfd, POLLIN, 0 };
|
||||
if (poll(&pfd, 1, 0) > 0) {
|
||||
while (read(tfd, evbuf16, sizeof evbuf16) == 16) {
|
||||
@@ -7386,18 +7527,49 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
|
||||
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) tly = val;
|
||||
if (td && !td_ok && tlx >= 0 && tly >= 0) {
|
||||
tdx = tlx; tdy = tly; td_ok = 1;
|
||||
/* 按下原点落在滑条带 → 开始拖动 */
|
||||
if (drag_slider == 0) {
|
||||
if (ncp_slider_band(tlx, tly, NCP_SL1_Y)) { drag_slider = 1; ncp_slider_apply(1, tlx, 0); }
|
||||
else if (ncp_slider_band(tlx, tly, NCP_SL2_Y)) { drag_slider = 2; ncp_slider_apply(2, tlx, 0); }
|
||||
scroll_y0 = tdy; /* 列表拖动基准:按下原点(进滚动时才锁 scroll_base) */
|
||||
/* 按下原点落在滑条带 → 开始拖动(仅面板就位) */
|
||||
if (open_settled && drag_slider == 0) {
|
||||
if (ncp_slider_band(tlx, tly, NCP_SL1_Y)) { drag_slider = 1; if (ncp_slider_apply(1, tlx, 0)) need_paint = 1; }
|
||||
else if (ncp_slider_band(tlx, tly, NCP_SL2_Y)) { drag_slider = 2; if (ncp_slider_apply(2, tlx, 0)) need_paint = 1; }
|
||||
}
|
||||
}
|
||||
if (drag_slider == 1 && td_ok) { ncp_slider_apply(1, tlx, 0); need_paint = 1; }
|
||||
else if (drag_slider == 2 && td_ok) { ncp_slider_apply(2, tlx, 0); need_paint = 1; }
|
||||
if (drag_slider == 1 && td_ok) { if (ncp_slider_apply(1, tlx, 0)) need_paint = 1; }
|
||||
else if (drag_slider == 2 && td_ok) { if (ncp_slider_apply(2, tlx, 0)) need_paint = 1; }
|
||||
/* 列表滚动(§11):面板就位(open_settled)+ 起点在列表区 + 内容溢出 +
|
||||
* 纵向位移达 slop → 进滚动。方向遵循 input.swipe_invert(与设置页列表同式);
|
||||
* 拖到顶/底继续拖 → 回弹标记。动画进行中(面板在滑,未就位)不启动滚动。 */
|
||||
if (open_settled && td_ok && !scroll_drag && drag_slider == 0) {
|
||||
int ddy = tly - scroll_y0;
|
||||
if (ddy >= S_SCROLL_SLOP || ddy <= -S_SCROLL_SLOP) {
|
||||
int total = g_nrows * NCP_ROW_H;
|
||||
int view = NCP_LIST_BOT - NCP_LIST_TOP;
|
||||
if (mnp_scroll_max(total, view) > 0 &&
|
||||
tdy >= NCP_LIST_TOP && tdy < NCP_LIST_BOT) {
|
||||
scroll_drag = 1;
|
||||
meow_settings_get_bool("input.swipe_invert", &scroll_invert);
|
||||
scroll_base = mnp_scroll_base(list_scroll, ddy, scroll_invert);
|
||||
scroll_rebound = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scroll_drag && td_ok) {
|
||||
int total = g_nrows * NCP_ROW_H;
|
||||
int view = NCP_LIST_BOT - NCP_LIST_TOP;
|
||||
int target = mnp_scroll_target(scroll_base, tly - scroll_y0, scroll_invert);
|
||||
list_scroll = mnp_scroll_clamp(target, total, view);
|
||||
scroll_rebound = mnp_scroll_rebound(target, mnp_scroll_max(total, view),
|
||||
NCP_SCROLL_REBOUND_TOL);
|
||||
list_paint = 1;
|
||||
}
|
||||
} else if (type == EV_KEY && code == BTN_TOUCH) {
|
||||
if (val && !td) {
|
||||
if (now_ms() < touch_cooldown_until) { tlx = tly = -1; }
|
||||
else { td = 1; td_ok = 0; tlx = -1; tly = -1; drag_slider = 0; }
|
||||
else {
|
||||
td = 1; td_ok = 0; tlx = -1; tly = -1; drag_slider = 0;
|
||||
scroll_drag = 0; scroll_base = 0; scroll_y0 = 0;
|
||||
scroll_invert = 0; scroll_rebound = 0;
|
||||
}
|
||||
} else if (!val && td) {
|
||||
td = 0;
|
||||
if (drag_slider) { /* 滑条拖动结束 → 落盘 */
|
||||
@@ -7406,9 +7578,29 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
|
||||
} else if (td_ok) {
|
||||
int dx = tlx - tdx, dy = tly - tdy;
|
||||
int adx = dx < 0 ? -dx : dx, ady = dy < 0 ? -dy : dy;
|
||||
/* 左缘左滑 = 关闭(复用全局左缘返回语义) */
|
||||
if (meow_edge_back(tdx, dx, dy)) { closed = 1; break; }
|
||||
if (adx < 60 && ady < 60) { /* tap */
|
||||
/* 左缘左滑 = 关闭(复用全局左缘返回语义;动画中同样可打断)——
|
||||
* 优先于列表滚动落定,保「左缘返回关」语义不受滚动影响 */
|
||||
if (meow_edge_back(tdx, dx, dy)) {
|
||||
if (anim_ok) anim_target = 0; else closed = 1;
|
||||
break;
|
||||
}
|
||||
if (scroll_drag) {
|
||||
/* 列表滚动落定:最终 clamp 到边界(拖动中已实时跟手,此处确认终值);
|
||||
* 若回弹(拖到顶/底继续拖)→ 按「上滑/下滑关闭」收起(§11.3)。
|
||||
* 绝不把「滑列表」误判成「上滑关闭」——scroll_drag 一旦置位即跳过
|
||||
* 下方 mnp_vertical_up 关闭判定。 */
|
||||
int total = g_nrows * NCP_ROW_H;
|
||||
int view = NCP_LIST_BOT - NCP_LIST_TOP;
|
||||
int target = mnp_scroll_target(scroll_base, tly - scroll_y0, scroll_invert);
|
||||
list_scroll = mnp_scroll_clamp(target, total, view);
|
||||
if (scroll_rebound) {
|
||||
if (anim_ok) anim_target = 0; else closed = 1;
|
||||
break;
|
||||
}
|
||||
list_paint = 1;
|
||||
scroll_base = 0; scroll_y0 = 0; scroll_rebound = 0; scroll_invert = 0;
|
||||
} else if (adx < 60 && ady < 60) { /* tap */
|
||||
if (animating) break; /* 动画进行中:tap 忽略(控件命中会误触,见 §9.5) */
|
||||
int tx = tdx, ty = tdy;
|
||||
int hit = 0;
|
||||
for (int c = 0; c < NCP_CHIP_COUNT; c++)
|
||||
@@ -7416,18 +7608,26 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
|
||||
if (ncp_chip_apply(c)) need_sb = 1; /* 主题变化 → 重刷状态栏 */
|
||||
hit = 1; need_paint = 1; break;
|
||||
}
|
||||
if (!hit && ncp_slider_band(tx, ty, NCP_SL1_Y)) { ncp_slider_apply(1, tx, 1); hit = 1; need_paint = 1; }
|
||||
else if (!hit && ncp_slider_band(tx, ty, NCP_SL2_Y)) { ncp_slider_apply(2, tx, 1); hit = 1; need_paint = 1; }
|
||||
if (!hit && ncp_slider_band(tx, ty, NCP_SL1_Y)) { hit = 1; if (ncp_slider_apply(1, tx, 1)) need_paint = 1; }
|
||||
else if (!hit && ncp_slider_band(tx, ty, NCP_SL2_Y)) { hit = 1; if (ncp_slider_apply(2, tx, 1)) need_paint = 1; }
|
||||
if (!hit && ty >= NCP_LIST_TOP && ty < NCP_LIST_BOT) {
|
||||
if (ncp_row_tap(tx, ty, list_scroll, want_launch)) {
|
||||
hit = 1;
|
||||
closed = 1; /* 点击通知:dismiss + 记录启动 → 关面板 */
|
||||
if (anim_ok) anim_target = 0; /* 点击通知:dismiss + 记录启动 → 滑出收回 */
|
||||
else closed = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hit) { closed = 1; break; } /* 点空白(标题/把手/提示/分隔线/列表外)→ 关闭 */
|
||||
if (!hit) { /* 点空白(标题/把手/提示/分隔线/列表外)→ 关闭 */
|
||||
if (anim_ok) anim_target = 0; else closed = 1;
|
||||
break;
|
||||
}
|
||||
} else if (mnp_vertical_up(dx, dy, MENU_SWIPE_THRESH)) {
|
||||
closed = 1; break; /* 面板内上滑 → 关闭 */
|
||||
if (anim_ok) anim_target = 0; else closed = 1; /* 面板内上滑 → 关闭 */
|
||||
break;
|
||||
} else if (anim_ok && anim_target == 0 &&
|
||||
mnp_top_swipe_down(tdx, tdy, dx, dy, SB_BAR_H, MENU_SWIPE_THRESH)) {
|
||||
anim_target = NCP_ANIM_FIX; /* 收回途中顶部热区下滑 → 重新打开(平滑转向) */
|
||||
}
|
||||
/* 其余横滑/下滑忽略(不关闭不动作) */
|
||||
}
|
||||
@@ -7437,7 +7637,7 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (closed) break; /* 上滑 / 点空白 / 左缘左滑 / 通知点击 → 关面板返回 run_menu */
|
||||
if (closed) break; /* 非动画兜底:上滑 / 点空白 / 左缘左滑 / 通知点击 → 关面板返回 run_menu */
|
||||
|
||||
/* 列表变化检测:~500ms 对比 count;变化重采集 + 重绘列表区 */
|
||||
if (now - last_listchk >= 500) {
|
||||
@@ -7451,16 +7651,63 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
|
||||
}
|
||||
}
|
||||
|
||||
/* 开关动画推进(§9:状态时间轴 + 快照 blit 平移;present 尊重 SDK 帧率门控,动画不更高频)。
|
||||
* 每 tick 依壁钟差推进 p;改 anim_target 即平滑转向(可打断,不跳变)。 */
|
||||
if (anim_ok && anim_p != anim_target) {
|
||||
unsigned long long anow = now_ms();
|
||||
int dt;
|
||||
if (anim_target != anim_last_target) {
|
||||
/* 意图刚切换(打开↔关闭):重置计时起点,本 tick 不计时,
|
||||
* 避免稳态闲置后 anim_last 陈旧导致首帧就跳到目标(不产生动画)。 */
|
||||
anim_last_target = anim_target;
|
||||
anim_last = anow;
|
||||
dt = 0;
|
||||
} else {
|
||||
dt = (int)((anow > anim_last) ? (anow - anim_last) : 0);
|
||||
anim_last = anow;
|
||||
}
|
||||
int np = mnp_anim_step(anim_p, anim_target, NCP_ANIM_FIX,
|
||||
(unsigned long long)dt, NCP_ANIM_MS);
|
||||
if (np != anim_p) {
|
||||
anim_p = np;
|
||||
int off = mnp_anim_off(anim_p, NCP_ANIM_FIX, g_ncp_ph);
|
||||
int cur_bot = ncp_anim_rebuild(off, anim_prev_bot);
|
||||
int maxb = (anim_prev_bot > cur_bot) ? anim_prev_bot : cur_bot;
|
||||
anim_prev_bot = cur_bot;
|
||||
/* 只刷面板矩形 + 新露出的条(不整屏;不含状态栏 0-27 带) */
|
||||
meow_dirty_add(0, NCP_TOP, fb_xres, maxb - NCP_TOP);
|
||||
meow_ui_flush();
|
||||
}
|
||||
}
|
||||
if (anim_ok && anim_target == 0 && anim_p == 0) break; /* 滑出到 0 → 真正退出子循环 */
|
||||
|
||||
/* 重绘(面板体区域;状态栏 0-27 由 draw_statusbar 单独维护,不落入此脏区)。
|
||||
* need_sb=1(打开/主题变化)时 draw_notif_center 内另绘状态栏并标其脏——脏区仍
|
||||
* 只含「面板体 + 28px 状态栏」两带,绝不整屏重绘。 */
|
||||
* 只含「面板体 + 28px 状态栏」两带,绝不整屏重绘。
|
||||
* 动画进行中:不画稳态面板(会覆盖正在滑动的帧),need_paint 保持到到位后再补画。 */
|
||||
if (need_paint) {
|
||||
last_act = now;
|
||||
if (!(anim_ok && anim_p != anim_target)) {
|
||||
draw_notif_center(list_scroll, need_sb);
|
||||
need_sb = 0;
|
||||
if (anim_ok) ncp_snap_panel(); /* 面板内容已变:刷新快照(供滑出动画用) */
|
||||
if (after_modal) { meow_dirty_full(); after_modal = 0; }
|
||||
else meow_dirty_add(0, NCP_TOP, fb_xres, fb_yres - NCP_TOP);
|
||||
meow_ui_flush();
|
||||
need_paint = 0;
|
||||
list_paint = 0; /* 整面板已含列表:滚动重绘标记随之清除 */
|
||||
}
|
||||
/* 否则动画进行中:保留 need_paint,到位后(下一稳态 tick)补画最终面板 */
|
||||
}
|
||||
/* 仅列表区重绘(滚动专用:只刷列表带,不整面板/不刷状态栏;同受动画门控,
|
||||
* 动画进行中不画稳态,list_paint 保持到到位后再补画一次列表终值)。 */
|
||||
if (list_paint) {
|
||||
last_act = now;
|
||||
if (!(anim_ok && anim_p != anim_target)) {
|
||||
ncp_repaint_list(list_scroll);
|
||||
if (anim_ok) ncp_snap_panel(); /* 列表已变:刷新面板快照(供滑出动画用) */
|
||||
list_paint = 0;
|
||||
}
|
||||
}
|
||||
/* ~1s 状态栏时钟 */
|
||||
if (now - last_tick >= 1000) {
|
||||
@@ -7473,6 +7720,7 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
|
||||
}
|
||||
if (tfd >= 0) close(tfd);
|
||||
kp_close(&kp_power); kp_close(&kp_keys);
|
||||
ncp_snap_free(); /* 释放动画快照(退出即回收,不驻留) */
|
||||
ncp_rows_free();
|
||||
logmsg("notification center closed");
|
||||
}
|
||||
|
||||
@@ -59,6 +59,18 @@ static inline int mnp_slider_val_from_x(int px, int sx, int sw, int min, int max
|
||||
return v;
|
||||
}
|
||||
|
||||
/* 滑条值的「跳步」吸附:把 v 取到最近的 step 整数倍。
|
||||
* step<=1 原样返回(默认 audio.volume_step=1 ⇒ 连续,与设置页滑条逐值行为一致);
|
||||
* step>1(可配 1-5)时吸附到最近整数倍,减少拖动逐值写 DAC/刷状态栏(音量滑条卡顿修复)。
|
||||
* 不处理负数(音量/亮度值域均 ≥0);结果应再经 mnp_brightness_clamp / vol_clamp。 */
|
||||
static inline int mnp_step_round(int v, int step)
|
||||
{
|
||||
if (step <= 1) return v;
|
||||
int r = ((v + step / 2) / step) * step;
|
||||
if (r < 0) r = 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
/* ---- 亮度 clamp ---- */
|
||||
|
||||
/* 亮度 clamp:下限 vmin(=display.brightness_min,≥1 防黑屏)、上限 100(防闪屏)。
|
||||
@@ -103,7 +115,7 @@ static inline int mnp_row_at(int y, int list_top, int list_bot, int row_h)
|
||||
return r;
|
||||
}
|
||||
|
||||
/* 列表滚动 clainmp:把 scroll 钳到 [0, total-view]。content 高度=total(总行数*row_h)。 */
|
||||
/* 列表滚动 clamp:把 scroll 钳到 [0, total-view]。content 高度=total(总行数*row_h)。 */
|
||||
static inline int mnp_scroll_clamp(int scroll, int total, int view)
|
||||
{
|
||||
if (total <= view) return 0;
|
||||
@@ -113,4 +125,72 @@ static inline int mnp_scroll_clamp(int scroll, int total, int view)
|
||||
return scroll;
|
||||
}
|
||||
|
||||
/* ---- 列表拖动滚动(方向遵循 input.swipe_invert,与 launcher 设置页列表同式)---- */
|
||||
|
||||
/* 最大滚动位移:total=内容总高、view=可视区高。内容不足视口时恒 0(不滚动)。 */
|
||||
static inline int mnp_scroll_max(int total, int view)
|
||||
{
|
||||
return (total > view) ? (total - view) : 0;
|
||||
}
|
||||
|
||||
/* 进入滚动时锁定的 scroll 基准(保证触发那一刻不跳变,连续跟手)。
|
||||
* scroll=进入时 scroll;d=进入时位移(当前 y-原点 y,屏幕 y 向下为正);
|
||||
* invert=0(标准:手指上滑→scroll 增大=内容向上滚看后面项)映射 -d;invert=1(反向)映射 +d。
|
||||
* 与设置页 run_settings_page 的 `scroll_base = scroll_off + (invert ? -ddy : ddy)` 同式。 */
|
||||
static inline int mnp_scroll_base(int scroll, int d, int invert)
|
||||
{
|
||||
return scroll + (invert ? -d : d);
|
||||
}
|
||||
|
||||
/* 由基准 + 当前位移推目标 scroll(含方向映射);结果交 mnp_scroll_clamp 钳到 [0,max]。
|
||||
* 与设置页 `scroll_off = clamp(scroll_base + (invert ? dydt : -dydt))` 同式。 */
|
||||
static inline int mnp_scroll_target(int base, int d, int invert)
|
||||
{
|
||||
return base + (invert ? d : -d);
|
||||
}
|
||||
|
||||
/* 是否已越过滚动边界成「回弹」(拖到顶/底仍继续拖)——用于列表滚满后继续拖 → 关闭。
|
||||
* target=未钳位目标 scroll;max=mnp_scroll_max(...);tol=回弹容差(px,超过才认,
|
||||
* 避免在边界小幅抖动即误关)。返回 1=已越过边界(回弹,可触发关闭语义)。 */
|
||||
static inline int mnp_scroll_rebound(int target, int max, int tol)
|
||||
{
|
||||
return (target < -tol || target > max + tol);
|
||||
}
|
||||
|
||||
/* ---- 开关平移动画(状态时间轴,纯逻辑,可单测)----
|
||||
* 通知中心面板「打开/关闭滑入/收回」= 面板内容整体做 y-offset 平移(参照 SDK §5.2 快照 blit 平移)。
|
||||
* 状态:progress p ∈ [0, fix],0=全隐藏(面板内容在面板区上缘之外),fix=到位(offset_y=0)。
|
||||
* 本组函数只做标量时间轴/裁切计算,不碰 fb/几何——供 launcher(meow_launcherd.c 内嵌面板)与
|
||||
* 宿主单测(test_notif_panel.c)共用同一份实现,避免两端漂移。 */
|
||||
|
||||
/* 进度 p([0,fix])→ 面板内容 y-offset(px,∈[-ph,0])。
|
||||
* p=0 → -ph(内容上移出面板区,全隐藏);p=fix → 0(到位)。线性。 */
|
||||
static inline int mnp_anim_off(int p, int fix, int ph)
|
||||
{
|
||||
return (int)((long long)(p - fix) * ph / fix);
|
||||
}
|
||||
|
||||
/* 沿时间轴单向推进:dt_ms(距上次 tick 的单调毫秒差)→ 新 p(向 target 逼近并 clamp)。
|
||||
* fix=进度分母(如 1024);dur_ms=单程时长(ms)。dt_ms<=0 或已达 target 时返回原 p。
|
||||
* 为保证单调前进(防快循环 dt 为 0 卡住),dt_ms>0 且未到位时 step 至少为 1。
|
||||
* 反转(p>target)即回退,同样逐帧平滑。 */
|
||||
static inline int mnp_anim_step(int p, int target, int fix, unsigned long long dt_ms, int dur_ms)
|
||||
{
|
||||
if (dt_ms == 0) return p;
|
||||
if (p == target) return p;
|
||||
long long step = (long long)dt_ms * fix / (dur_ms > 0 ? dur_ms : 1);
|
||||
if (step < 1) step = 1;
|
||||
if (target > p) { p += (int)step; if (p > target) p = target; }
|
||||
else { p -= (int)step; if (p < target) p = target; }
|
||||
return p;
|
||||
}
|
||||
|
||||
/* 面板覆盖区底缘(screen y,exclusive):off 时面板可见区 = [top, top+ph+off)。
|
||||
* off∈[-ph,0] ⇒ 结果∈[top, top+ph]。top 通常=状态栏高(NCP_TOP=28,不盖状态栏)。
|
||||
* 调用方用「顶=top、高=covered_bot-top」作为脏区上缘/裁切范围。 */
|
||||
static inline int mnp_anim_covered_bot(int top, int ph, int off)
|
||||
{
|
||||
return top + ph + off;
|
||||
}
|
||||
|
||||
#endif /* MEOW_NOTIF_PANEL_H */
|
||||
@@ -96,6 +96,18 @@ static void test_pure_logic(void)
|
||||
CHECK(mnp_slider_val_from_x(0, 20, 440, 1, 100) == 1, "slider x out-left clamped to min");
|
||||
CHECK(mnp_slider_val_from_x(999, 20, 440, 1, 100) == 100, "slider x out-right clamped to max");
|
||||
|
||||
/* mnp_step_round:跳步吸附(音量滑条去抖/减少逐值写 DAC) */
|
||||
CHECK(mnp_step_round(37, 1) == 37, "step_round step=1 -> unchanged (continuous, same as settings slider)");
|
||||
CHECK(mnp_step_round(0, 1) == 0, "step_round step=1 -> 0 kept");
|
||||
CHECK(mnp_step_round(37, 5) == 35, "step_round 37 step=5 -> 35 (nearest multiple of 5)");
|
||||
CHECK(mnp_step_round(38, 5) == 40, "step_round 38 step=5 -> 40 (rounds up to nearest)");
|
||||
CHECK(mnp_step_round(50, 5) == 50, "step_round 50 step=5 -> exactly on step");
|
||||
CHECK(mnp_step_round(0, 5) == 0, "step_round 0 step=5 -> 0");
|
||||
CHECK(mnp_step_round(100, 5) == 100, "step_round 100 step=5 -> 100");
|
||||
CHECK(mnp_step_round(99, 5) == 100, "step_round 99 step=5 -> 100 (rounds up to max)");
|
||||
CHECK(mnp_step_round(2, 4) == 4, "step_round 2 step=4 -> 4 (round to nearest multiple)");
|
||||
CHECK(mnp_step_round(-3, 5) == 0, "step_round negative clamps to 0 (value domain >=0)");
|
||||
|
||||
/* mnp_brightness_clamp */
|
||||
CHECK(mnp_brightness_clamp(0, 1) == 1, "brightness clamp below min -> min");
|
||||
CHECK(mnp_brightness_clamp(150, 1) == 100, "brightness clamp above 100 -> 100");
|
||||
@@ -126,6 +138,106 @@ static void test_pure_logic(void)
|
||||
CHECK(mnp_scroll_clamp(5, 400, 444) == 0, "content fits view -> 0");
|
||||
}
|
||||
|
||||
/* P4 通知列表滚动:方向映射(input.swipe_invert)+ 回弹判定(纯逻辑,header-only)。
|
||||
* 语义与设置页列表一致:invert=0 手指上滑(d<0) → target 增大(内容上滚看后面项);
|
||||
* invert=1(反向)手指上滑 → target 减小。进滚动锁 base 保证触发瞬间不跳变。 */
|
||||
static void test_scroll_pure(void)
|
||||
{
|
||||
fprintf(stderr, "--- P4 notification list scroll (swipe_invert direction + rebound) ---\n");
|
||||
int total = 2000, view = 444, max_scroll = total - view; /* 1556 */
|
||||
|
||||
/* mnp_scroll_max */
|
||||
CHECK(mnp_scroll_max(2000, 444) == 1556, "scroll_max overflow -> total-view");
|
||||
CHECK(mnp_scroll_max(400, 444) == 0, "scroll_max content fits -> 0");
|
||||
CHECK(mnp_scroll_max(444, 444) == 0, "scroll_max equal -> 0");
|
||||
CHECK(mnp_scroll_max(445, 444) == 1, "scroll_max 1px over -> 1");
|
||||
|
||||
/* mnp_scroll_base:进滚动锁基准(invert=0 同设置页 `scroll + d`;invert=1 反向) */
|
||||
CHECK(mnp_scroll_base(100, -45, 0) == 55, "base invert=0 finger-up -> scroll+d");
|
||||
CHECK(mnp_scroll_base(100, -45, 1) == 145, "base invert=1 finger-up -> scroll-d");
|
||||
|
||||
/* mnp_scroll_target:触发瞬间 target 回到原 scroll(连续跟手,不跳变) */
|
||||
CHECK(mnp_scroll_target(55, -45, 0) == 100, "target invert=0 at engage -> back to scroll");
|
||||
CHECK(mnp_scroll_target(145, -45, 1) == 100, "target invert=1 at engage -> back to scroll");
|
||||
|
||||
/* 方向映射:invert=0 手指持续上滑(更负) → target 增大;invert=1 → target 减小 */
|
||||
CHECK(mnp_scroll_target(55, -200, 0) == 255, "target invert=0 finger-up more -> increases");
|
||||
CHECK(mnp_scroll_target(145, -200, 1) == -55, "target invert=1 finger-up more -> decreases");
|
||||
CHECK(mnp_scroll_target(55, 200, 0) == -145, "target invert=0 finger-down -> decreases");
|
||||
CHECK(mnp_scroll_target(145, 200, 1) == 345, "target invert=1 finger-down -> increases");
|
||||
|
||||
/* clamp 落位:进滚动 + 拖到边界 → 钳到 [0,max_scroll] */
|
||||
CHECK(mnp_scroll_clamp(mnp_scroll_target(55, -200, 0), total, view) == 255, "clamp invert=0 up -> in range");
|
||||
CHECK(mnp_scroll_clamp(mnp_scroll_target(145, -200, 1), total, view) == 0, "clamp invert=1 up -> bottom(0)");
|
||||
CHECK(mnp_scroll_clamp(mnp_scroll_target(55, 200, 0), total, view) == 0, "clamp invert=0 down -> top(0)");
|
||||
|
||||
/* mnp_scroll_rebound:边内容差 tol=48;界内不触发,越过才触发 */
|
||||
CHECK(mnp_scroll_rebound(0, max_scroll, 48) == 0, "rebound at top(0) in-tol -> 0");
|
||||
CHECK(mnp_scroll_rebound(-47, max_scroll, 48) == 0, "rebound top overshoot 47<48 -> 0");
|
||||
CHECK(mnp_scroll_rebound(-49, max_scroll, 48) == 1, "rebound top overshoot 49>48 -> 1");
|
||||
CHECK(mnp_scroll_rebound(max_scroll, max_scroll, 48) == 0, "rebound at bottom in-tol -> 0");
|
||||
CHECK(mnp_scroll_rebound(max_scroll + 47, max_scroll, 48) == 0, "rebound bottom overshoot 47<48 -> 0");
|
||||
CHECK(mnp_scroll_rebound(max_scroll + 49, max_scroll, 48) == 1, "rebound bottom overshoot 49>48 -> 1");
|
||||
CHECK(mnp_scroll_rebound(500, max_scroll, 48) == 0, "rebound mid-range -> 0");
|
||||
}
|
||||
|
||||
/* P3 开关平移动画状态机:p 推进/翻转/中断、offset 计算、裁切边界、关闭到 0 才退出。
|
||||
* 用任意 fix/ph 参数化(默认 1024/692,通知中心面板 PH=720-28=692);纯函数不依赖 fb。 */
|
||||
static void test_anim_pure(void)
|
||||
{
|
||||
fprintf(stderr, "--- P3 open/close slide animation state machine ---\n");
|
||||
enum { FIX = 1024, PH = 692 };
|
||||
|
||||
/* mnp_anim_off:p→offset 线性映射(∈[-PH,0];p=0 全隐藏 / p=FIX 到位) */
|
||||
CHECK(mnp_anim_off(0, FIX, PH) == -PH, "anim_off p=0 -> -PH (fully hidden above)");
|
||||
CHECK(mnp_anim_off(FIX, FIX, PH) == 0, "anim_off p=FIX -> 0 (settled open)");
|
||||
CHECK(mnp_anim_off(FIX / 2, FIX, PH) == -(PH / 2), "anim_off mid -> -PH/2");
|
||||
|
||||
/* mnp_anim_step:打开推进(0→FIX) */
|
||||
CHECK(mnp_anim_step(0, FIX, FIX, 180, 180) == FIX, "step dt=dur -> reaches FIX in one tick");
|
||||
CHECK(mnp_anim_step(0, FIX, FIX, 90, 180) == FIX / 2, "step dt=dur/2 -> half progress");
|
||||
CHECK(mnp_anim_step(0, FIX, FIX, 10, 180) >= 1, "step small dt -> at least 1 (monotonic, no stall)");
|
||||
CHECK(mnp_anim_step(0, FIX, FIX, 0, 180) == 0, "step dt=0 -> no move");
|
||||
CHECK(mnp_anim_step(0, FIX, FIX, 100000, 180) == FIX, "step huge dt -> clamped at FIX");
|
||||
|
||||
/* 反向推进(关闭:FIX→0,线性步进率 = dt*fix/dur,非剩余比例) */
|
||||
CHECK(mnp_anim_step(FIX, 0, FIX, 90, 180) == FIX / 2, "step close dt=dur/2 -> half back");
|
||||
CHECK(mnp_anim_step(FIX / 2, 0, FIX, 45, 180) == FIX / 4, "step reversal mid -> toward 0 linearly");
|
||||
CHECK(mnp_anim_step(FIX / 4, 0, FIX, 100000, 180) == 0, "step reversal huge dt -> clamped at 0");
|
||||
|
||||
/* 打断(打开中→关闭→再打开):平滑转向,不跳变到目标 */
|
||||
int p = mnp_anim_step(0, FIX, FIX, 90, 180); /* 打开到半程 (512) */
|
||||
int p_before = p;
|
||||
p = mnp_anim_step(p, 0, FIX, 30, 180); /* 打断:转为关闭,小步推进 */
|
||||
CHECK(p < p_before && p > 0, "interrupt open->close: heading to 0 (not target), mid value ok");
|
||||
int s2 = 0;
|
||||
while (s2++ < 1000 && p != 0) p = mnp_anim_step(p, 0, FIX, 30, 180);
|
||||
CHECK(p == 0, "interrupt close completes at 0");
|
||||
p = mnp_anim_step(p, FIX, FIX, 180, 180); /* 关闭到位后再打开 */
|
||||
CHECK(p == FIX, "reopen after interrupt -> settled open");
|
||||
|
||||
/* mnp_anim_covered_bot:裁切边界(off∈[-PH,0] → bot∈[top, top+PH]) */
|
||||
CHECK(mnp_anim_covered_bot(28, PH, -PH) == 28, "covered_bot fully hidden -> top (statusbar bottom)");
|
||||
CHECK(mnp_anim_covered_bot(28, PH, 0) == 28 + PH, "covered_bot settled -> top+PH");
|
||||
CHECK(mnp_anim_covered_bot(28, PH, -(PH / 2)) == 28 + PH / 2, "covered_bot halfway -> top+PH/2");
|
||||
CHECK(mnp_anim_covered_bot(28, PH, 0) <= 720 && mnp_anim_covered_bot(28, PH, -PH) >= 28,
|
||||
"covered_bot within [28,720] — 不盖状态栏、不出屏底");
|
||||
|
||||
/* 完整关闭:多次 tick 才到 0;单调不增;且只在 p==0 才算退出 */
|
||||
p = FIX;
|
||||
int steps = 0, prev = p, monotonic = 1;
|
||||
while (steps++ < 100000 && p != 0) {
|
||||
int np = mnp_anim_step(p, 0, FIX, 10, 180);
|
||||
if (np > prev) monotonic = 0; /* 关闭方向必须单调减小 */
|
||||
if (np < 0 || np > FIX) monotonic = 0;
|
||||
p = np; prev = np;
|
||||
}
|
||||
CHECK(p == 0, "close animation terminates at p=0 (exit condition)");
|
||||
CHECK(steps > 1, "close takes multiple ticks (smooth sliding, not a jump)");
|
||||
CHECK(monotonic, "close progress monotonic non-increasing");
|
||||
CHECK(mnp_anim_step(0, 0, FIX, 10, 180) == 0, "at p==0 with target 0 stays put (would break)");
|
||||
}
|
||||
|
||||
static void test_storage_contract(void)
|
||||
{
|
||||
fprintf(stderr, "--- P0 storage contract (panel reads) ---\n");
|
||||
@@ -227,6 +339,8 @@ int main(void)
|
||||
meow_notif_set_tf_max(1024 * 1024);
|
||||
|
||||
test_pure_logic();
|
||||
test_scroll_pure();
|
||||
test_anim_pure();
|
||||
test_storage_contract();
|
||||
|
||||
unlink(g_tflog); rmdir(dir);
|
||||
|
||||
Reference in new issue
Block a user