通知中心增强(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:
MeowOS committed 2026-08-30 17:01:47 +08:00
1 parent 2b17f69f24
commit a8ec8377ed
7 files changed
+542 -59

No files matched your search

+306 -58
View File
@@ -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);
draw_notif_center(list_scroll, 1);
meow_dirty_full(); meow_ui_flush();
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;
draw_notif_center(list_scroll, need_sb);
need_sb = 0;
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();
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");
}