通知中心 ⑥⑦: 电源菜单chip + 勿扰面板内开关 + 单条左滑删除(agent-132)

- ⑥ 快捷行 5chip: WiFi/蓝牙/亮度/主题/电源菜单; 电源菜单chip点击=唤起 show_power_menu(真弹) + after_modal 还原
- 勿扰=面板内开关(notify.dnd bool, 零旁路), ncp_draw_list 按 urgency>=2 过滤; NCP_LIST_TOP 308
- ⑦ 单条通知行左滑删除: 跟手(mnp_swipe_clamp)+半程可回(未过阈值回弹), 过阈值滑出删除(mnp_swipe_delete/step)
- 手势区分: 行内左滑=删该条, 竖向=滚动, meow_edge_back(左缘<24) 优先; 局部刷该行带
- meow_ui.a 零改动(d249c6e6); launcher=115e2b1d; 宿主147断言PASS; 新增0warn
- SYSTEM_SETTINGS §1 加 notify.dnd; NOTIFICATION_SYSTEM §9 增 ⑥/⑦ 定案
This commit is contained in:
MeowOS committed 2026-08-30 17:23:04 +08:00
1 parent a8ec8377ed
commit 82b77d64b4
7 files changed
+452 -46

No files matched your search

Binary file not shown.
+321 -46
View File
@@ -6779,13 +6779,15 @@ done:
#define NCP_CHIP_H 44
#define NCP_CHIP_W 70
#define NCP_CHIP_GAP 8
#define NCP_CHIP_N 4
#define NCP_CHIP_N 5 /* ⑥:快捷 chip 扩充为 5 个(+电源菜单) */
#define NCP_SLIDER_X 20
#define NCP_SLIDER_W (fb_xres - 40)
#define NCP_SL1_Y 162 /* 亮度滑条 track 顶(label 在 -26 =136) */
#define NCP_SL2_Y 214 /* 音量滑条 track 顶(label 在 -26 =188) */
#define NCP_DIV_Y 258
#define NCP_LIST_TOP 266
#define NCP_DND_Y 264 /* ⑥:勿扰面板内开关带顶缘(分隔线下方) */
#define NCP_DND_H 40 /* 勿扰开关带高(label+状态+s_draw_switch 48×28) */
#define NCP_LIST_TOP 308 /* 通知列表顶缘(原 266 → 下移 42 给勿扰带) */
#define NCP_LIST_BOT 694
#define NCP_ROW_H 92
#define NCP_ROW_X 12
@@ -6798,13 +6800,22 @@ done:
#define NCP_SCROLL_REBOUND_TOL 48 /* px:列表拖到顶/底仍继续拖过此容差 → 判回弹(关闭语义),
避免在边界小幅抖动即误关(> S_SCROLL_SLOP=40) */
/* ---- ⑦ 单条通知行左滑删除(跟手 + 半程可回 + 回弹)---- */
#define NCP_SWIPE_SLOP 24 /* px:行内水平位移达此值且横向主导+左移 → 进行左滑(>阈值) */
#define NCP_SWIPE_RATE 3 /* px/ms:行左滑落位动画线性速率(回弹/滑出) */
#define NCP_SWIPE_DELETE_FRAC 40 /* %:行宽百分比阈值(│off│>=row_w*40/100 → 删) */
#define NCP_SWIPE_DELETE_MIN 110 /* px:绝对像素阈值(│off│>=110 → 删;OR 语义,窄行由 frac 起作用) */
/* 勿扰过滤阈值:dnd=1 只显示 urgency>=2(HIGH=2/CRITICAL=3) */
#define NCP_DND_MIN_URGENCY 2
/* ---- 开关平移动画(§9:状态时间轴 + 快照 blit 平移,不整屏重绘)---- */
#define NCP_ANIM_FIX 1024 /* 进度 p 的定点分母(1/1024 精度) */
#define NCP_ANIM_MS 180 /* 单程打开/关闭时长(ms,真机可调) */
/* chip 索引 */
/* chip 索引(⑥:第 5 个 = 电源菜单,点击唤起既有系统电源菜单 show_power_menu) */
enum { NCP_CHIP_WIFI=0, NCP_CHIP_BT=1, NCP_CHIP_THEME=2, NCP_CHIP_BRIGHT=3,
NCP_CHIP_COUNT=4 };
NCP_CHIP_POWER=4, NCP_CHIP_COUNT=5 };
/* 通知行快照(面板读取字段 + 缓存的 icon 句柄)。 */
typedef struct {
@@ -6825,6 +6836,17 @@ static int g_nrows = 0;
static struct app *g_ncp_apps = NULL; /* run_notif_center 注入(通知点击找 app 用) */
static int g_ncp_napps = 0;
/* ⑥ 勿扰面板内开关:当前 dnd 态(开面板/切换时从 meow_settings notify.dnd 读/写,零旁路)。
* ncp_collect_cb / ncp_active_count 按它过滤行(only urgency>=NCP_DND_MIN_URGENCY when on)。 */
static int g_ncp_dnd = 0;
/* ⑦ 行左滑删除的绘制态(供 ncp_draw_list / ncp_repaint_row 画该行位移)。
* g_ncp_swipe_row = 正在左滑的行下标(-1=无);g_ncp_swipe_off = 该行左移量(px,<=0)。
* run_notif_center 的手势状态机(row_swipe_row/row_off/row_settle/row_off_target/row_anim_at/row_paint)
* 与其同步,绘制只读这组全局,避免给 draw_notif_center/ncp_draw_list 加参数。 */
static int g_ncp_swipe_row = -1;
static int g_ncp_swipe_off = 0;
/* 短串拷贝(同 meow_notif.c 语义:保证 NUL + 截断) */
static void ncp_strncpy(char *dst, const char *src, size_t len)
{
@@ -6859,6 +6881,8 @@ static int ncp_collect_cb(const meow_notif_t *n, void *ctx)
{
(void)ctx;
if (g_nrows >= NCP_MAX_ROWS) return 1; /* 满:中止遍历 */
/* 勿扰(⑥):dnd 开时只收集 urgency>=NCP_DND_MIN_URGENCY(HIGH/CRITICAL)。 */
if (!mnp_dnd_should_show(n->urgency, g_ncp_dnd)) return 0;
ncp_row_t *r = &g_rows[g_nrows++];
memset(r, 0, sizeof *r);
r->id = n->id;
@@ -6890,12 +6914,25 @@ static int ncp_refresh_rows(void)
return g_nrows;
}
/* 通知列表是否变化(简单按活跃数比;新条/撤下/TTL 释放都反映在 count)。 */
/* 当前应显示的活跃通知数(按 g_ncp_dnd 过滤,与 g_rows 同口径)——供变化检测/重采集用。 */
static int ncp_count_cb(const meow_notif_t *n, void *ctx)
{
if (!mnp_dnd_should_show(n->urgency, g_ncp_dnd)) return 0;
(*(int *)ctx)++;
return 0;
}
static int ncp_active_count(void)
{
int c = 0;
meow_notif_list(NULL, ncp_count_cb, &c);
if (c > NCP_MAX_ROWS) c = NCP_MAX_ROWS;
return c;
}
/* 通知列表是否变化(按过滤后活跃数比;新条/撤下/TTL 释放都反映在 count,含 dnd 过滤)。 */
static int ncp_rows_changed(void)
{
int c = meow_notif_count(NULL);
if (c > NCP_MAX_ROWS) c = NCP_MAX_ROWS;
return c != g_nrows;
return ncp_active_count() != g_nrows;
}
/* ---- 开关平移动画辅助(§9:快照 blit + 平移;只在 run_notif_center 内分配/释放)----
@@ -7057,11 +7094,16 @@ 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_POWER) {
/* 电源菜单 chip:非开关(无 on/off 态),恒示中性;点击由 ncp_chip_apply 唤起电源菜单。 */
*on = 0;
}
}
/* chip 快捷动作(写 meow_settings_* + 侧效应;零手写文件)。
* 返回 1=主题变化(需重刷状态栏);0=其它。 */
* 返回 1=主题变化(需重刷状态栏);2=电源菜单已弹出(调用方置 after_modal=1 整帧重绘还原);
* 0=其它。电源菜单 chip 复用既有系统级 show_power_menu(含 dim/圆钮/音量导航/电源确认/8s强关/
* 空闲自动息屏),真弹对话框,动作在函数内 system();取消则返回续 panel —— 与电源键长按同款。 */
static int ncp_chip_apply(int chip)
{
if (chip == NCP_CHIP_WIFI) {
@@ -7098,12 +7140,72 @@ static int ncp_chip_apply(int chip)
g_sys.brightness = vmin;
}
paint_sb_right();
} else if (chip == NCP_CHIP_POWER) {
/* 电源菜单 chip:唤起既有系统级电源菜单(真弹对话框,动作在函数内 system();取消则返回)。
* 不涉及任何设置项(电源菜单不走 meow_settings,零旁路)。返回 2 → 调用方整帧重绘还原面板/状态栏。 */
logmsg("notif-center: power chip -> show power menu");
show_power_menu();
return 2;
}
meow_settings_save();
return 0;
}
/* 绘制通知列表可见区(scroll=像素偏移)。每行:图标 + app名 + 标题 + 内容 + 时间 + 进度。 */
/* ⑦ 画删除底:行右缘被空出的条(宽=-off)铺 error 红底 + 白「删除」字(条宽≥44 才显字,否则纯红渐进)。
* off<=0(负得多=卡滑出多、露出右条多);off=0 不画。 */
static void ncp_draw_delete_bg(int ry, int off, const meow_ui_theme_t *th)
{
if (off >= 0) return;
int bw = -off;
if (bw <= 0) return;
int bx = NCP_ROW_X + NCP_ROW_W + off; /* 被空出的右条左缘 */
fill_rect(bx, ry, bw, NCP_ROW_H - 6, th->error);
if (bw >= 44) {
int cx = bx + bw / 2, cy = ry + (NCP_ROW_H - 6) / 2;
ui_draw(cx - ui_width("删除", 16) / 2, cy - meow_font_line_height(16) / 2,
"删除", 0xFFFFFF, 16);
}
}
/* 画一行内容(行卡 + 图标/APP名/时间/标题/正文/进度),整体随 off 左移(⑦ 行左滑跟手)。
* i=g_rows 下标;ry=行顶(已含 scroll);off=行左移量(px,<=0)。 */
static void ncp_draw_row(int i, int ry, int off, const meow_ui_theme_t *th)
{
const ncp_row_t *r = &g_rows[i];
meow_gfx_fill_round(NCP_ROW_X + off, ry, NCP_ROW_W, NCP_ROW_H - 6, 12, th->surface_container);
/* 图标 */
int icx = NCP_ICON_X + off, icy = ry + (NCP_ROW_H - 32) / 2;
if (r->img) meow_ui_img_cover(icx, icy, 32, r->img);
else {
/* 回退:主题色圆点 + 首字符 */
const char *nm = ncp_app_name(r->app_id);
ncp_dot(icx + 16, icy + 16, 16, th->primary);
char ch = nm[0] ? nm[0] : '?';
char cbuf[4] = { ch, 0, 0, 0 };
/* 首字符若是多字节(中文),取整码 */
if ((unsigned char)ch >= 0x80 && nm[1]) { cbuf[1] = nm[1]; cbuf[2] = 0;
if ((unsigned char)ch >= 0xE0 && nm[2]) { cbuf[2] = nm[2]; cbuf[3] = 0; } }
int cw = ui_width(cbuf, 20);
ui_draw(icx + 16 - cw / 2, icy + 16 - meow_font_line_height(20) / 2, cbuf, 0xFFFFFF, 20);
}
/* app 名 + 时间 */
ui_draw(NCP_TEXT_X + off, ry + 10, ncp_app_name(r->app_id), th->on_surface_variant, 16);
char tbuf[32]; mnp_time_ago(r->created_ms, (uint64_t)time(NULL) * 1000u, tbuf, sizeof tbuf);
ui_draw(NCP_ROW_X + NCP_ROW_W - 8 - ui_width(tbuf, 16) + off, ry + 12, tbuf, th->on_surface_variant, 16);
/* 标题 + 内容 */
ncp_text(NCP_TEXT_X + off, ry + 30, r->summary, 20, th->on_surface, NCP_TEXT_W);
ncp_text(NCP_TEXT_X + off, ry + 56, r->body, 18, th->on_surface_variant, NCP_TEXT_W);
/* 进度(progress>=0):细条 */
if (r->progress >= 0) {
int px = NCP_TEXT_X + off, pw = NCP_TEXT_W, py = ry + NCP_ROW_H - 14;
fill_rect(px, py, pw, 4, th->surface_variant);
int fw = r->progress * pw / 100;
if (fw > 0) fill_rect(px, py, fw, 4, th->primary);
}
}
/* 绘制通知列表可见区(scroll=像素偏移)。每行:图标 + app名 + 标题 + 内容 + 时间 + 进度。
* ⑦:左滑中的行(g_ncp_swipe_row)按 g_ncp_swipe_off 左移,其下先画删除底(ncp_draw_delete_bg)。 */
static void ncp_draw_list(int scroll)
{
const meow_ui_theme_t *th = s_ui_theme();
@@ -7116,37 +7218,9 @@ static void ncp_draw_list(int scroll)
for (int i = 0; i < g_nrows; i++) {
int ry = NCP_LIST_TOP + i * NCP_ROW_H - scroll;
if (ry + NCP_ROW_H < NCP_LIST_TOP || ry >= NCP_LIST_BOT) continue; /* 视口外整行跳过 */
const ncp_row_t *r = &g_rows[i];
meow_gfx_fill_round(NCP_ROW_X, ry, NCP_ROW_W, NCP_ROW_H - 6, 12, th->surface_container);
/* 图标 */
int icx = NCP_ICON_X, icy = ry + (NCP_ROW_H - 32) / 2;
if (r->img) meow_ui_img_cover(icx, icy, 32, r->img);
else {
/* 回退:主题色圆点 + 首字符 */
const char *nm = ncp_app_name(r->app_id);
ncp_dot(icx + 16, icy + 16, 16, th->primary);
char ch = nm[0] ? nm[0] : '?';
char cbuf[4] = { ch, 0, 0, 0 };
/* 首字符若是多字节(中文),取整码 */
if ((unsigned char)ch >= 0x80 && nm[1]) { cbuf[1] = nm[1]; cbuf[2] = 0;
if ((unsigned char)ch >= 0xE0 && nm[2]) { cbuf[2] = nm[2]; cbuf[3] = 0; } }
int cw = ui_width(cbuf, 20);
ui_draw(icx + 16 - cw / 2, icy + 16 - meow_font_line_height(20) / 2, cbuf, 0xFFFFFF, 20);
}
/* app 名 + 时间 */
ui_draw(NCP_TEXT_X, ry + 10, ncp_app_name(r->app_id), th->on_surface_variant, 16);
char tbuf[32]; mnp_time_ago(r->created_ms, (uint64_t)time(NULL) * 1000u, tbuf, sizeof tbuf);
ui_draw(NCP_ROW_X + NCP_ROW_W - 8 - ui_width(tbuf, 16), ry + 12, tbuf, th->on_surface_variant, 16);
/* 标题 + 内容 */
ncp_text(NCP_TEXT_X, ry + 30, r->summary, 20, th->on_surface, NCP_TEXT_W);
ncp_text(NCP_TEXT_X, ry + 56, r->body, 18, th->on_surface_variant, NCP_TEXT_W);
/* 进度(progress>=0):细条 */
if (r->progress >= 0) {
int px = NCP_TEXT_X, pw = NCP_TEXT_W, py = ry + NCP_ROW_H - 14;
fill_rect(px, py, pw, 4, th->surface_variant);
int fw = r->progress * pw / 100;
if (fw > 0) fill_rect(px, py, fw, 4, th->primary);
}
int off = (i == g_ncp_swipe_row) ? g_ncp_swipe_off : 0; /* 左滑中的行位移 */
if (off < 0) ncp_draw_delete_bg(ry, off, th);
ncp_draw_row(i, ry, off, th);
}
/* 滚动条 */
if (total > NCP_LIST_BOT - NCP_LIST_TOP) {
@@ -7170,6 +7244,51 @@ static void ncp_repaint_list(int scroll)
meow_ui_flush();
}
/* ⑦ 仅重绘一行(行左滑跟手/回弹/滑出专用;只刷该行矩形,不整面板、不刷状态栏)。
* 清该行带(含被滑出的右条)→ 画删除底(若 off<0)+ 该行内容位移 → 只标该行带脏。 */
static void ncp_repaint_row(int scroll, int row, int off)
{
const meow_ui_theme_t *th = s_ui_theme();
if (row < 0 || row >= g_nrows) return;
int ry = NCP_LIST_TOP + row * NCP_ROW_H - scroll;
if (ry + NCP_ROW_H < NCP_LIST_TOP || ry >= NCP_LIST_BOT) return; /* 视口外:无需重绘 */
/* 清该行带全宽(含左缘 [0,NCP_ROW_X),因深滑时卡可滑到左缘外),再画删除底 + 位移行卡。 */
fill_rect(0, ry, NCP_ROW_X + NCP_ROW_W, NCP_ROW_H, th->surface_variant);
if (off < 0) ncp_draw_delete_bg(ry, off, th);
ncp_draw_row(row, ry, off, th);
meow_dirty_add(0, ry, fb_xres, NCP_ROW_H);
meow_ui_flush();
}
/* ⑥ 勿扰开关带命中(面板内小 toggle):落在 [NCP_DND_Y, NCP_DND_Y+NCP_DND_H) 整宽 → 命中。
* 不与 5 chip(y≤130)、滑条带(y≤238)、通知列表(y≥NCP_LIST_TOP=308)、状态栏(y<28)重叠。 */
static int ncp_dnd_hit(int tx, int ty)
{
return (ty >= NCP_DND_Y && ty < NCP_DND_Y + NCP_DND_H && tx >= 0 && tx < fb_xres);
}
/* ⑥ 绘勿扰带:label「勿扰」+ 铃铛图标 + 状态「开/关」 + 右侧 s_draw_switch(48×28)。 */
static void ncp_draw_dnd(const meow_ui_theme_t *th)
{
int y = NCP_DND_Y;
int dnd_on = g_ncp_dnd;
int cy = y + NCP_DND_H / 2;
/* 铃铛(简化:圆顶 + 顶蒂 + 底部小圆点)。 */
int bx = NCP_ROW_X + 4, bcy = cy - 2;
meow_gfx_fill_round(bx, bcy - 7, 16, 13, 7, th->on_surface_variant); /* 铃身圆顶 */
meow_gfx_fill_round(bx + 6, bcy - 11, 4, 4, 2, th->on_surface_variant);/* 顶蒂 */
ncp_dot(bx + 8, bcy + 6, 2, th->on_surface_variant); /* 底部小圆点 */
/* label + 状态 */
ui_draw(bx + 22, cy - meow_font_line_height(18) / 2 + 2, "勿扰", th->on_surface, 18);
char st[16];
snprintf(st, sizeof st, "%s", dnd_on ? "开" : "关");
int lw = ui_width("勿扰", 18);
ui_draw(bx + 22 + lw + 12, cy - meow_font_line_height(18) / 2 + 2, st,
dnd_on ? th->primary : th->on_surface_variant, 18);
/* 右侧开关(既有的开关绘制;48×28 垂直居中) */
s_draw_switch(fb_xres - 24 - 48, cy - 14, dnd_on, th);
}
/* 绘制整个通知中心面板(画进 SDK back_buf;redraw_sb=1 时另绘状态栏在最顶)。
* 性能:面板体单独;(0..27) 状态栏只在打开/主题变化时 redraw_sb=1 重绘,交互时跳过,
* 使脏区仅限面板体,绝不整屏重绘。 */
@@ -7191,8 +7310,8 @@ static void draw_notif_center(int scroll, int redraw_sb)
ui_draw(24, NCP_TITLE_Y, "通知中心", th->on_surface, 22);
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", "蓝牙", "主题", "亮度" };
/* 快捷开关行(⑥:5 个 = WiFi/蓝牙/主题/亮度/电源菜单,居中自动随 NCP_CHIP_N) */
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++) {
@@ -7210,6 +7329,13 @@ 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_POWER) {
/* 电源符号:圆环 + 顶部短柄,近似 ⏻(用现有 meow_gfx_fill_round 近似圆)。 */
int pc = icon_cx + SB_ICON_SIZE / 2;
meow_gfx_fill_round(pc - 8, icon_cy - 8, 16, 16, 8, fg); /* 外环 */
meow_gfx_fill_round(pc - 4, icon_cy - 4, 8, 8, 4, bg); /* 内孔(显环厚) */
meow_gfx_fill_round(pc - 1, icon_cy - 16, 3, 9, 1, fg); /* 顶部短柄 */
}
char label[24];
snprintf(label, sizeof label, "%s", chip_lbl[c]);
if (val[0] && c == NCP_CHIP_BRIGHT)
@@ -7240,6 +7366,9 @@ static void draw_notif_center(int scroll, int redraw_sb)
/* 分隔线 */
fill_rect(12, NCP_DIV_Y, W - 24, 1, th->outline);
/* ⑥ 勿扰面板内开关带(list 上方) */
ncp_draw_dnd(th);
/* 通知列表 */
ncp_draw_list(scroll);
@@ -7349,6 +7478,16 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
int scroll_invert = 0; /* input.swipe_invert(0=标准方向) */
int scroll_rebound = 0; /* 拖到顶/底仍继续拖(回弹)→ 抬手按关闭语义收起 */
int list_paint = 0; /* 仅列表区重绘(滚动专用,不整面板/不刷状态栏) */
/* ⑦ 单条通知行左滑删除(跟手+半程可回+回弹;绘制态见 g_ncp_swipe_row/off):
* row_swipe_row = 正在左滑的行下标(-1=无);row_off = 行左移量(px,<=0,跟手);
* row_settle = 0 跟手 / 1 松手后向 row_off_target 回弹或滑出;row_anim_at = 落位动画计时。
* row_paint = 仅重绘该行带(跟手局部 blit/位移,不整面板/不刷状态栏)。 */
int row_swipe_row = -1;
int row_off = 0;
int row_settle = 0;
int row_off_target = 0;
unsigned long long row_anim_at = 0;
int row_paint = 0; /* 仅重绘该行带(行左滑专用) */
int closed = 0; /* 非动画路径的兜底关面板(快照不可用时瞬时退出) */
int after_modal = 0; /* 电源菜单返回:整帧重绘还原被 dim 的状态栏 */
int wake_suppress = 0;
@@ -7365,6 +7504,12 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
int anim_prev_bot = NCP_TOP; /* 上一帧面板覆盖底缘(exclusive;初始=NCP_TOP=未覆盖) */
int anim_ok = 0;
/* ⑥ 读勿扰面板内开关态(开面板一次;切换在下面 tap 分支写回)。 */
{
int d = 0; meow_settings_get_bool("notify.dnd", &d);
g_ncp_dnd = d;
}
/* 首次绘制:先拍背景(应用列表)→ 渲染最终位置整面板 → 拍面板快照 → 还原背景到 p=0
* 起点(面板隐藏、先露应用列表),随后进入循环由动画推进 p:0→FIX 滑入。 */
ncp_refresh_rows();
@@ -7428,6 +7573,9 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
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;
row_swipe_row = -1; row_off = 0; row_settle = 0;
row_off_target = 0; row_anim_at = 0; row_paint = 0;
g_ncp_swipe_row = -1; g_ncp_swipe_off = 0;
/* 唤醒:面板直接就位(不重播滑入动画,面板早已打开);重拍快照防内容已变 */
anim_p = anim_target = anim_last_target = NCP_ANIM_FIX;
if (anim_ok) anim_prev_bot = NCP_TOP + g_ncp_ph;
@@ -7536,10 +7684,39 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
}
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; }
/* ⑦ 行左滑(进入):面板就位(open_settled)+ 未锁其它手势 + 位移达 slop,
* 判定横向主导(adx>ady)且左移(ddx<0)且起点落在可见行 → 进行左滑(只该行)。
* 与竖向滚动互斥:本分支锁 row_swipe_row 后下方 scroll 不再进入(gate 含
* row_swipe_row<0);竖向主导(ady>adx)或不左移则回退既有滚动(改变不大:
* 以前「横主导左移」也滚,现改为行左滑,符合「横向主导→行左滑」)。 */
if (open_settled && td_ok && !scroll_drag && drag_slider == 0 && row_swipe_row < 0) {
int ddx = tlx - tdx, ddy = tly - tdy;
int adx = ddx < 0 ? -ddx : ddx, ady = ddy < 0 ? -ddy : ddy;
if ((adx >= NCP_SWIPE_SLOP || ady >= S_SCROLL_SLOP) && adx > ady && ddx < 0) {
int eff = tdy - NCP_LIST_TOP + list_scroll;
int row = mnp_row_at(eff, 0, g_nrows * NCP_ROW_H, NCP_ROW_H);
/* 起点必须在列表区(DND 带/底部提示等不在列表区,防 scroll 导致误匹行) */
if (tdy >= NCP_LIST_TOP && tdy < NCP_LIST_BOT &&
row >= 0 && row < g_nrows) {
row_swipe_row = row;
row_off = mnp_swipe_clamp(ddx, NCP_ROW_W);
row_settle = 0; row_off_target = 0;
row_anim_at = now_ms();
g_ncp_swipe_row = row; g_ncp_swipe_off = row_off;
row_paint = 1;
}
}
}
/* ⑦ 行左滑跟手:已进则更新 off(反向可回 → 回落至 0);只该行生效。 */
if (row_swipe_row >= 0 && td_ok && !row_settle) {
row_off = mnp_swipe_clamp(tlx - tdx, NCP_ROW_W);
g_ncp_swipe_off = row_off;
row_paint = 1;
}
/* 列表滚动(§11):面板就位(open_settled)+ 起点在列表区 + 内容溢出 +
* 纵向位移达 slop → 进滚动。方向遵循 input.swipe_invert(与设置页列表同式);
* 拖到顶/底继续拖 → 回弹标记。动画进行中(面板在滑,未就位)不启动滚动。 */
if (open_settled && td_ok && !scroll_drag && drag_slider == 0) {
if (open_settled && td_ok && !scroll_drag && drag_slider == 0 && row_swipe_row < 0) {
int ddy = tly - scroll_y0;
if (ddy >= S_SCROLL_SLOP || ddy <= -S_SCROLL_SLOP) {
int total = g_nrows * NCP_ROW_H;
@@ -7569,6 +7746,10 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
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;
/* ⑦ 新触摸:清行左滑(防上一手势残留) */
row_swipe_row = -1; row_off = 0; row_settle = 0;
row_off_target = 0; row_anim_at = 0; row_paint = 0;
g_ncp_swipe_row = -1; g_ncp_swipe_off = 0;
}
} else if (!val && td) {
td = 0;
@@ -7584,7 +7765,20 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
if (anim_ok) anim_target = 0; else closed = 1;
break;
}
if (scroll_drag) {
if (row_swipe_row >= 0) {
/* ⑦ 行左滑松手:过阈值 → 滑出删除;未过/反向 → 回弹复原(半程取消)。
* 只该行生效;本手势锁定后不再走 tap/上滑关闭/点空白,不关面板。 */
/* 用抬手最终位移兜底刷新 off(可能最后有位移但无 ABS 事件跟进)。 */
row_off = mnp_swipe_clamp(dx, NCP_ROW_W);
g_ncp_swipe_off = row_off;
if (mnp_swipe_delete(row_off, NCP_ROW_W,
NCP_SWIPE_DELETE_FRAC, NCP_SWIPE_DELETE_MIN)) {
row_off_target = -NCP_ROW_W; /* 滑出(删除)目标:整行移出 */
} else {
row_off_target = 0; /* 回弹复原 */
}
row_settle = 1; row_anim_at = now_ms();
} else if (scroll_drag) {
/* 列表滚动落定:最终 clamp 到边界(拖动中已实时跟手,此处确认终值);
* 若回弹(拖到顶/底继续拖)→ 按「上滑/下滑关闭」收起(§11.3)。
* 绝不把「滑列表」误判成「上滑关闭」——scroll_drag 一旦置位即跳过
@@ -7605,11 +7799,44 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
int hit = 0;
for (int c = 0; c < NCP_CHIP_COUNT; c++)
if (ncp_chip_hit(c, tx, ty)) {
if (ncp_chip_apply(c)) need_sb = 1; /* 主题变化 → 重刷状态栏 */
int ar = ncp_chip_apply(c);
if (ar == 1) need_sb = 1; /* 主题变化 → 重刷状态栏 */
else if (ar == 2) {
after_modal = 1; /* 电源菜单已弹 → 整帧重绘还原 */
/* 电源菜单返回:菜单期间用户可能按电源确认/取消,键边沿可能错配,
* 同电源键长按路径(line 7480-7488)重 poll + 重采 m_prev_*,防误判「单击」→ 误切 panel。 */
memset(&kd_pwr, 0, sizeof kd_pwr);
memset(&kd_vold, 0, sizeof kd_vold);
memset(&kd_volu, 0, sizeof kd_volu);
memset(&kd_play, 0, sizeof kd_play);
kp_poll(&kp_power, NULL, NULL);
kp_poll(&kp_keys, NULL, NULL);
m_prev_pwr = kp_is_down(&kp_power, KEY_POWER);
m_prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN);
m_prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP);
m_prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE);
/* 菜单期间用户的触摸经 evdev 广播也会进本 panel 的 tfd → 立即排空,
* 防返回后把「菜单里的点击」误判为 panel 内 tap/滑动。 */
if (tfd >= 0) {
unsigned char ev2[16];
while (read(tfd, ev2, sizeof ev2) == 16) {}
}
}
hit = 1; need_paint = 1; break;
}
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; }
/* ⑥ 勿扰面板内开关(列表上方小 toggle):tap 即切换(整带命中) */
if (!hit && ncp_dnd_hit(tx, ty)) {
int dv = 0; meow_settings_get_bool("notify.dnd", &dv);
meow_settings_set_bool("notify.dnd", dv ? 0 : 1);
meow_settings_save();
g_ncp_dnd = dv ? 0 : 1;
ncp_refresh_rows(); /* 按 dnd 过滤重采集 */
list_scroll = mnp_scroll_clamp(list_scroll, g_nrows * NCP_ROW_H,
NCP_LIST_BOT - NCP_LIST_TOP);
hit = 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;
@@ -7644,6 +7871,10 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
last_listchk = now;
meow_notif_tick();
if (ncp_rows_changed()) {
/* ⑦ 列表已变(新条/撤下/TTL):取消当前行左滑,避免行下标错位。 */
row_swipe_row = -1; row_off = 0; row_settle = 0;
row_off_target = 0; row_anim_at = 0; row_paint = 0;
g_ncp_swipe_row = -1; g_ncp_swipe_off = 0;
ncp_refresh_rows();
list_scroll = mnp_scroll_clamp(list_scroll, g_nrows * NCP_ROW_H,
NCP_LIST_BOT - NCP_LIST_TOP);
@@ -7681,6 +7912,42 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
}
if (anim_ok && anim_target == 0 && anim_p == 0) break; /* 滑出到 0 → 真正退出子循环 */
/* ⑦ 行左滑落位动画:row_settle=1 时按壁钟差向 row_off_target 线性步进(回弹→0 / 滑出→-row_w)。
* 到达目标:0=回弹完成(行复原,无需重排);-row_w=删除 → dismiss + 重采集 + 重排(need_paint)。
* 只该行生效;与面板开关动画(上块)互不干扰(行左滑仅在面板稳态 open_settled 下进行)。 */
if (row_swipe_row >= 0 && row_settle) {
unsigned long long rnow = now_ms();
int rdt = (int)((rnow > row_anim_at) ? (rnow - row_anim_at) : 0);
row_anim_at = rnow;
int noff = mnp_swipe_step(row_off, row_off_target, (unsigned long long)rdt, NCP_SWIPE_RATE);
if (noff != row_off) {
row_off = noff;
g_ncp_swipe_off = noff;
row_paint = 1;
}
if (noff == row_off_target) {
if (row_off_target == 0) {
/* 回弹复原完成:先把行重画回 0 位(清残留偏移),再清滑动态(无需重排)。 */
ncp_repaint_row(list_scroll, row_swipe_row, 0);
row_swipe_row = -1; row_settle = 0; row_off = 0;
row_off_target = 0; row_paint = 0;
g_ncp_swipe_row = -1; g_ncp_swipe_off = 0;
if (anim_ok) ncp_snap_panel(); /* 行已复原:刷新面板快照(供滑出动画用) */
} else {
/* 滑出删除完成:撤下该通知 + 重采集 + 重排列表(整面板重画)。 */
if (row_swipe_row >= 0 && row_swipe_row < g_nrows)
meow_notif_dismiss(g_rows[row_swipe_row].id);
row_swipe_row = -1; row_settle = 0; row_off = 0;
row_off_target = 0; row_paint = 0;
g_ncp_swipe_row = -1; g_ncp_swipe_off = 0;
ncp_refresh_rows();
list_scroll = mnp_scroll_clamp(list_scroll, g_nrows * NCP_ROW_H,
NCP_LIST_BOT - NCP_LIST_TOP);
need_paint = 1; /* 行已移除/重排:整面板(列表区)重画 */
}
}
}
/* 重绘(面板体区域;状态栏 0-27 由 draw_statusbar 单独维护,不落入此脏区)。
* need_sb=1(打开/主题变化)时 draw_notif_center 内另绘状态栏并标其脏——脏区仍
* 只含「面板体 + 28px 状态栏」两带,绝不整屏重绘。
@@ -7709,6 +7976,14 @@ static void run_notif_center(struct app *apps, int napps, int *want_launch)
list_paint = 0;
}
}
/* 仅重绘该行带(行左滑跟手/回弹/滑出专用:只刷该行矩形,不整面板/不刷状态栏;同受动画门控)。
* row_swipe_row<0(行已回弹/删除/被取消)则跳过并清标记,避免对已移除行重绘。 */
if (row_paint) {
last_act = now;
if (row_swipe_row >= 0 && !(anim_ok && anim_p != anim_target))
ncp_repaint_row(list_scroll, row_swipe_row, row_off);
row_paint = 0;
}
/* ~1s 状态栏时钟 */
if (now - last_tick >= 1000) {
last_tick = now;
@@ -193,4 +193,55 @@ static inline int mnp_anim_covered_bot(int top, int ph, int off)
return top + ph + off;
}
/* ---- 勿扰(DND)过滤(通知中心面板内开关 → 基于 urgency 过滤,纯逻辑,可单测)----
* urgency 见 meow_notif.h:0=LOW 低 / 1=NORMAL 普通 / 2=HIGH 高 / 3=CRITICAL 紧急(缺省 NORMAL)。
* dnd=0(关):全部显示;dnd=1(开):只显示 urgency>=2(HIGH/CRITICAL)。返回 1=应显示。 */
static inline int mnp_dnd_should_show(int urgency, int dnd)
{
if (!dnd) return 1;
return urgency >= 2;
}
/* ---- 单条通知行左滑删除(纯逻辑,可单测)----
* 交互(launcher run_notif_center):行内左横滑(横向主导+左移)→ 该行跟随左移(off<0,露出删除底);
* 松手过阈值 → meow_notif_dismiss + 行移除/重排;未过/反向滑回 → 回弹复原(跟手、半程可回)。
* 本组函数只做标量:clamp / 删除阈值 / 落位步进,不碰 fb/几何——供 launcher 与宿主单测共用。 */
/* 行左移 clamp:把手指位移钳到 [-row_w, 0](正向=右移钳 0;越左钳 -row_w=卡全滑出)。
* row_w=行宽(如 NCP_ROW_W),off<=0 表示左移程度(负得多=滑出多)。 */
static inline int mnp_swipe_clamp(int off, int row_w)
{
if (off > 0) off = 0;
if (off < -row_w) off = -row_w;
return off;
}
/* 松手删除判定:│off│ >= row_w*frac_pct/100 或 │off│ >= min_px → 返回 1(应删除);
* 否则 0(未过阈值/半程取消 → 回弹)。frac_pct 为行宽百分比(如 40)、min_px 为绝对像素保底
* (防极窄行/极小滑也判删,取二者较大者实际生效——满足「≥row_w 某比例 或 ≥ 阈值 px」)。 */
static inline int mnp_swipe_delete(int off, int row_w, int frac_pct, int min_px)
{
if (off > 0) off = 0;
int amt = -off; /* 左移幅度(px,>=0) */
if (amt >= row_w * frac_pct / 100) return 1;
if (amt >= min_px) return 1;
return 0;
}
/* 落位动画线性步进:off 沿方向向 target 走 dt_ms*px_per_ms,到 target 即停(可正/反向回弹或滑出)。
* dt_ms=距上次 tick 单调毫秒差;px_per_ms=速率(px/ms,如 3)。dt_ms<=0 或已到位返回原 off。
* 单调、可打断(任意时刻改 off 即反向),与面板开关平移动画(mnp_anim_step)同「状态时间轴」思想,
* 但按固定 px/ms 而非剩余比例(行位移远小于面板高,线性更稳)。 */
static inline int mnp_swipe_step(int off, int target, unsigned long long dt_ms, int px_per_ms)
{
if (dt_ms == 0 || off == target) return off;
int delta = target - off;
int dir = (delta < 0) ? -1 : 1;
long long absd = (long long)(dir < 0 ? -delta : delta);
long long step = (long long)dt_ms * px_per_ms;
if (step > absd) step = absd;
if (step < 1) step = 1; /* 单调前进,防快循环 dt 极小卡住 */
return off + dir * (int)step;
}
#endif /* MEOW_NOTIF_PANEL_H */
+3
View File
@@ -121,6 +121,9 @@ static const meow_setting_def g_defs[] = {
{ "notify.categories", SET_INT, "4", 1, 4, NULL, NULL },
{ "notify.category_quota", SET_INT, "4", 1, 16, NULL, NULL },
{ "notify.media_quota", SET_INT, "1", 1, 1, NULL, NULL },
/* 通知中心「勿扰」面板内开关(SET_BOOL,默认 0=关):开=只显示 urgency>=2(HIGH/CRITICAL)。
* 面板内 toggle 读写均走 meow_settings_set_bool/get_bool,零旁路(通知中心 UI 方案 §12.2)。 */
{ "notify.dnd", SET_BOOL, "0", 0, 0, NULL, NULL },
/* 日志 */
{ "log.level", SET_ENUM, "info", 0, 0, "error,warn,info,debug", "错误,警告,信息,调试" },
{ "log.to_card", SET_BOOL, "1", 0, 0, NULL, NULL },
@@ -238,6 +238,79 @@ static void test_anim_pure(void)
CHECK(mnp_anim_step(0, 0, FIX, 10, 180) == 0, "at p==0 with target 0 stays put (would break)");
}
/* P5 ⑥ 勿扰过滤 + ⑦ 行左滑删除(纯逻辑,header-only):
* - mnp_dnd_should_show:dnd=0 全显示;dnd=1 只 urgency>=2(HIGH/CRITICAL)。
* - mnp_swipe_clamp:跟手 clamp 到 [-row_w,0]。
* - mnp_swipe_delete:松手阈值(行宽百分比 / 绝对像素,取大者)。
* - mnp_swipe_step:落位动画线性步进(回弹/滑出,正反向,到底停,半程取消)。
* 另加一段 mini 状态机:手指拖 → 松手过阈值=滑出删除 / 未过=回弹复原(跟手、半程可回)。 */
static void test_dnd_swipe_pure(void)
{
fprintf(stderr, "--- P5 DND filter + row left-swipe ---\n");
/* mnp_dnd_should_show */
CHECK(mnp_dnd_should_show(0, 0) == 1, "dnd off -> LOW(0) shows");
CHECK(mnp_dnd_should_show(1, 0) == 1, "dnd off -> NORMAL(1) shows");
CHECK(mnp_dnd_should_show(2, 0) == 1, "dnd off -> HIGH(2) shows");
CHECK(mnp_dnd_should_show(3, 0) == 1, "dnd off -> CRITICAL(3) shows");
CHECK(mnp_dnd_should_show(0, 1) == 0, "dnd on -> LOW(0) hidden");
CHECK(mnp_dnd_should_show(1, 1) == 0, "dnd on -> NORMAL(1) hidden");
CHECK(mnp_dnd_should_show(2, 1) == 1, "dnd on -> HIGH(2) shows");
CHECK(mnp_dnd_should_show(3, 1) == 1, "dnd on -> CRITICAL(3) shows");
/* mnp_swipe_clamp(跟手 offset clamp:off∈[-row_w,0]) */
CHECK(mnp_swipe_clamp(-200, 456) == -200, "swipe_clamp in-range kept");
CHECK(mnp_swipe_clamp(30, 456) == 0, "swipe_clamp rightward -> 0");
CHECK(mnp_swipe_clamp(-500, 456) == -456, "swipe_clamp past left -> -row_w");
CHECK(mnp_swipe_clamp(-456, 456) == -456, "swipe_clamp exactly -row_w kept");
CHECK(mnp_swipe_clamp(0, 456) == 0, "swipe_clamp 0 kept");
/* mnp_swipe_delete(row_w=456, frac_pct=40 → 40%=182.4;min_px=110;OR 语义 → 有效阈值=110px) */
CHECK(mnp_swipe_delete(-80, 456, 40, 110) == 0, "delete -80 (<110 & <182.4) -> cancel");
CHECK(mnp_swipe_delete(-110, 456, 40, 110) == 1, "delete -110 (>=min_px) -> delete");
CHECK(mnp_swipe_delete(-150, 456, 40, 110) == 1, "delete -150 (>=min_px, <40%) -> delete");
CHECK(mnp_swipe_delete(-183, 456, 40, 110) == 1, "delete -183 (>=40%) -> delete");
CHECK(mnp_swipe_delete(-456, 456, 40, 110) == 1, "delete full slide -> delete");
CHECK(mnp_swipe_delete(0, 456, 40, 110) == 0, "delete 0 -> cancel");
/* 窄行:40%=24px(row_w=60),min_px=110 不起作用 → 阈值=24px */
CHECK(mnp_swipe_delete(-25, 60, 40, 110) == 1, "delete narrow row -25 (>=40%) -> delete");
CHECK(mnp_swipe_delete(-20, 60, 40, 110) == 0, "delete narrow row -20 (<40%) -> cancel");
/* mnp_swipe_step(线性步进:off 向 target 走 dt*rate,可正/反向,到底停) */
CHECK(mnp_swipe_step(-100, 0, 10, 3) == -70, "step springback +30px/tick");
CHECK(mnp_swipe_step(-100, 0, 0, 3) == -100, "step dt=0 -> unchanged");
CHECK(mnp_swipe_step(-100, 0, 1000, 3) == 0, "step huge dt -> clamp at 0 (springback done)");
CHECK(mnp_swipe_step(-50, -456, 10, 3) == -80, "step slide-out leftward");
CHECK(mnp_swipe_step(-50, -456, 1000, 3) == -456, "step huge dt -> clamp at -row_w (deleted)");
CHECK(mnp_swipe_step(-30, -30, 10, 3) == -30, "step at target -> stays");
CHECK(mnp_swipe_step(0, 0, 10, 3) == 0, "step 0 target 0 -> stays");
CHECK(mnp_swipe_step(-1000, 0, 5, 3) == -985, "step linear rate not exponential (small dt)");
/* mini 状态机:跟手拖 → 松手判定 → 动画到目标(半程取消 vs 删除)。 */
int row_w = 456, off = 0;
/* 拖 80px 左(低于阈值 110)→ 松手回弹复原 */
off = mnp_swipe_clamp(-80, row_w);
CHECK(mnp_swipe_delete(off, row_w, 40, 110) == 0, "mini: drag -80 -> cancel (springback)");
int target = mnp_swipe_delete(off, row_w, 40, 110) ? -row_w : 0;
off = mnp_swipe_step(off, target, 1000, 3);
CHECK(off == 0, "mini: springback reaches 0 (row restored)");
/* 拖 150px 左(>=110)→ 松手滑出删除 */
off = mnp_swipe_clamp(-150, row_w);
CHECK(mnp_swipe_delete(off, row_w, 40, 110) == 1, "mini: drag -150 -> delete");
target = mnp_swipe_delete(off, row_w, 40, 110) ? -row_w : 0;
off = mnp_swipe_step(off, target, 1000, 3);
CHECK(off == -row_w, "mini: slide-out reaches -row_w (dismiss)");
/* 半程可反向:拖 200 左再反向回摄(dx 变 +30)→ off 回 0(跟手可回、松手在途取消) */
off = mnp_swipe_clamp(-200, row_w);
CHECK(off == -200, "mini: drag -200 follows finger");
off = mnp_swipe_clamp(30, row_w); /* 反向拖回原点左侧以右 → clamp 0 */
CHECK(off == 0, "mini: reverse drag -> row slides back to 0 (halfway cancel)");
/* 半程取消:拖 150 左(未达 182.4 的 40% 但达 110px 阈值)→ 松手删;若再拖回 <110 → 回弹 */
off = mnp_swipe_clamp(-150, row_w);
CHECK(mnp_swipe_delete(off, row_w, 40, 110) == 1, "mini: -150 passed threshold -> delete");
off = mnp_swipe_clamp(-100, row_w); /* 又拖回 100 <110 */
CHECK(mnp_swipe_delete(off, row_w, 40, 110) == 0, "mini: -100 back under threshold -> cancel");
}
static void test_storage_contract(void)
{
fprintf(stderr, "--- P0 storage contract (panel reads) ---\n");
@@ -341,6 +414,7 @@ int main(void)
test_pure_logic();
test_scroll_pure();
test_anim_pure();
test_dnd_swipe_pure();
test_storage_contract();
unlink(g_tflog); rmdir(dir);