feat(launcher): 媒体会话+Now Playing (agent-204) — MSG_META(0x72)/MSG_PLAYCTL(0x73)+meow_media.c(最近活跃源=active_seq净序,30s超时回收); 通知中心顶部媒体卡片(封面缓存+进度seek+控制键); 主界面中部/右缘左滑进 Now Playing 大页(封面+元数据+进度+控制+歌词占位), 左缘返回; 空态完备

launcher a42171a1→ddfe616e; 宿主 test_media ALL PASS + qemu 端到端(META注册→PLAYCTL下发闭环)
出入: 卡片以媒体会话为权威非通知; PLAYCTL 走会话fd(断连防御留后续)
This commit is contained in:
MeowOS committed 2026-08-31 06:51:04 +08:00
1 parent 8012948b26
commit 32543ac616
15 files changed
+1429 -34

No files matched your search

+212 -28
View File
@@ -26,6 +26,34 @@ static int g_ncp_swipe_row = -1;
static int g_ncp_swipe_off = 0;
/* ---- 阶段D附录B:通知中心顶部媒体卡片(固定于通知列表上方,活动媒体会话时显示)---- */
#define NCP_MEDIA_H 112 /* 卡片高(封面/信息/控制/进度) */
#define NCP_MEDIA_CARD_X 12
#define NCP_MEDIA_CARD_W (fb_xres - 24)
static meow_img *g_ncp_media_cover = NULL; /* 封面缓存(按 artwork 路径变化重载,不每帧解码) */
static char g_ncp_media_cover_path[200] = "";
/* 卡片顶缘(恒定 = 通知列表区上缘)。 */
static int ncp_media_y(void) { return NCP_LIST_TOP; }
/* 卡片是否显示(活动媒体会话存在且非停止)。 */
static int ncp_media_on(void) { return meow_media_has_source() ? NCP_MEDIA_H : 0; }
/* 通知列表可视区实际顶缘(含媒体卡片时下移一个卡片高)——绘制/命中统一用,避免硬编码 NCP_LIST_TOP。 */
static int ncp_list_top(void) { return NCP_LIST_TOP + ncp_media_on(); }
/* 列表可视区高(含媒体卡片时缩短)。 */
static int ncp_list_view(void) { int v = NCP_LIST_BOT - ncp_list_top(); return v > 0 ? v : 0; }
/* 卡片内按钮几何(供绘制与命中共用)。 */
#define NCP_MEDIA_BTN_Y 34 /* 相对卡片顶的按钮中心 y */
#define NCP_MEDIA_PREV_CX 300
#define NCP_MEDIA_PLAY_CX 372
#define NCP_MEDIA_NEXT_CX 444
#define NCP_MEDIA_BTN_R 22 /* play 半径;prev/next 略小 */
#define NCP_MEDIA_PROG_Y 74 /* 相对卡片顶的进度条 top */
/* 短串拷贝(同 meow_notif.c 语义:保证 NUL + 截断) */
static void ncp_strncpy(char *dst, const char *src, size_t len)
{
@@ -432,25 +460,25 @@ static void ncp_draw_row(int i, int ry, int off, const meow_ui_theme_t *th)
static void ncp_draw_list(int scroll)
{
const meow_ui_theme_t *th = s_ui_theme();
int lt = ncp_list_top(), lv = ncp_list_view();
if (g_nrows == 0) {
ui_draw(NCP_ROW_X + 8, NCP_LIST_TOP + 40, "暂无通知", th->on_surface_variant, 22);
ui_draw(NCP_ROW_X + 8, lt + 40, "暂无通知", th->on_surface_variant, 22);
return;
}
int total = g_nrows * NCP_ROW_H;
scroll = mnp_scroll_clamp(scroll, total, NCP_LIST_BOT - NCP_LIST_TOP);
scroll = mnp_scroll_clamp(scroll, total, lv);
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; /* 视口外整行跳过 */
int ry = lt + i * NCP_ROW_H - scroll;
if (ry + NCP_ROW_H < lt || ry >= NCP_LIST_BOT) continue; /* 视口外整行跳过 */
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) {
int view = NCP_LIST_BOT - NCP_LIST_TOP;
int thumb_h = view * view / total; if (thumb_h < 24) thumb_h = 24;
int ty = NCP_LIST_TOP + (view - thumb_h) * scroll / (total - view);
meow_gfx_fill_round(fb_xres - 8, NCP_LIST_TOP, 5, view, 3, th->surface_container);
if (total > lv) {
int thumb_h = lv * lv / total; if (thumb_h < 24) thumb_h = 24;
int ty = lt + (lv - thumb_h) * scroll / (total - lv);
meow_gfx_fill_round(fb_xres - 8, lt, 5, lv, 3, th->surface_container);
meow_gfx_fill_round(fb_xres - 8, ty, 5, thumb_h, 3, th->on_surface_variant);
}
}
@@ -462,9 +490,10 @@ static void ncp_draw_list(int scroll)
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);
int lt = ncp_list_top(), lv = ncp_list_view();
fill_rect(0, lt, fb_xres, lv, th->surface_variant);
ncp_draw_list(scroll);
meow_dirty_add(0, NCP_LIST_TOP, fb_xres, NCP_LIST_BOT - NCP_LIST_TOP);
meow_dirty_add(0, lt, fb_xres, lv);
meow_ui_flush();
}
@@ -475,8 +504,9 @@ 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; /* 视口外:无需重绘 */
int lt = ncp_list_top();
int ry = lt + row * NCP_ROW_H - scroll;
if (ry + NCP_ROW_H < lt || 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);
@@ -577,6 +607,139 @@ static int ncp_clear_all(void)
/* 绘制整个通知中心面板(画进 SDK back_buf;redraw_sb=1 时另绘状态栏在最顶)。
* 性能:面板体单独;(0..27) 状态栏只在打开/主题变化时 redraw_sb=1 重绘,交互时跳过,
* 使脏区仅限面板体,绝不整屏重绘。 */
/* ================= 阶段D附录B:顶部媒体卡片(绘制/命中) ================= */
/* 封面缓存:artwork 路径变化才重载(不每帧解码)。 */
static void ncp_media_cover_load(const meow_media_session_t *s)
{
if (!s) return;
if (strcmp(g_ncp_media_cover_path, s->artwork) == 0) return;
if (g_ncp_media_cover) { meow_img_free(g_ncp_media_cover); g_ncp_media_cover = NULL; }
ncp_strncpy(g_ncp_media_cover_path, s->artwork, sizeof g_ncp_media_cover_path);
if (!s->artwork[0]) return;
char path[200];
if (s->artwork[0] == '/') snprintf(path, sizeof path, "%s", s->artwork);
else snprintf(path, sizeof path, "/usr/data/mnt/sd_0/%s", s->artwork);
g_ncp_media_cover = meow_img_load_file(path);
}
/* 实心三角(dir=+1 右向 / -1 左向);size=最大宽度 px。按扫描线 fill_rect 近似 */
static void ncp_media_tri(int cx, int cy, int size, int dir, uint32_t col)
{
int half = size / 2;
for (int dy = -half; dy <= half; dy++) {
int w = half + 1 - (dy < 0 ? -dy : dy); /* 顶宽 1 → 底宽 half+1 */
if (w <= 0) continue;
int x0 = cx + (dir > 0 ? -w / 2 : w / 2 - w);
fill_rect(x0, cy + dy, w, 1, col);
}
}
/* 圆形按钮底 + 字形(kind:0=prev 1=play 2=next 3=pause 4=暂停态play)。 */
static void ncp_media_btn(int cx, int cy, int r, int kind, const meow_ui_theme_t *th)
{
meow_gfx_fill_round(cx - r, cy - r, r * 2, r * 2, r, th->primary);
uint32_t fg = 0xFFFFFF;
if (kind == 0) { /* prev:左三角 + 左竖条 */
ncp_media_tri(cx + 4, cy, r - 4, -1, fg);
fill_rect(cx - 3, cy - (r - 5), 3, (r - 5) * 2, fg);
} else if (kind == 1) { /* play:右三角 */
ncp_media_tri(cx + 2, cy, r - 2, 1, fg);
} else if (kind == 2) { /* next:右三角 + 右竖条 */
ncp_media_tri(cx - 4, cy, r - 4, 1, fg);
fill_rect(cx + 1, cy - (r - 5), 3, (r - 5) * 2, fg);
} else if (kind == 3) { /* pause:双竖条 */
int bw = (r - 6) / 2; if (bw < 2) bw = 2;
fill_rect(cx - 7, cy - (r - 5), bw, (r - 5) * 2, fg);
fill_rect(cx + 7 - bw, cy - (r - 5), bw, (r - 5) * 2, fg);
}
}
/* 画顶部媒体卡片(封面小图 + 标题/歌手 + 播放/暂停/上下曲 + 进度条)。 */
static void ncp_draw_media_card(const meow_ui_theme_t *th)
{
const meow_media_session_t *s = meow_media_current();
if (!s || s->state == MEOW_MEDIA_STOPPED) return;
int cy = ncp_media_y();
meow_gfx_fill_round(NCP_MEDIA_CARD_X, cy, NCP_MEDIA_CARD_W, NCP_MEDIA_H, 14, th->surface_container);
ncp_media_cover_load(s);
int cover_s = 88, cover_x = NCP_MEDIA_CARD_X + 12, cover_y = cy + 12;
if (g_ncp_media_cover) {
meow_gfx_fill_round(cover_x, cover_y, cover_s, cover_s, 12, th->surface_variant);
meow_ui_img_cover(cover_x + 4, cover_y + 4, cover_s - 8, g_ncp_media_cover);
} else {
meow_gfx_fill_round(cover_x, cover_y, cover_s, cover_s, 12, th->primary);
const char *t = s->title[0] ? s->title : s->app_id;
int cw = ui_width(t, 20);
ui_draw(cover_x + (cover_s - cw) / 2, cover_y + (cover_s - meow_font_line_height(20)) / 2,
t, 0xFFFFFF, 20);
}
int tx = cover_x + cover_s + 14;
int tw = (NCP_MEDIA_PREV_CX - NCP_MEDIA_BTN_R - 8) - tx; /* 文本宽限到上一首按钮左缘前 8px */
if (tw < 0) tw = 0;
ncp_text(tx, cy + 14, s->title, 18, th->on_surface, tw);
ncp_text(tx, cy + 40, s->artist[0] ? s->artist : s->album, 16, th->on_surface_variant, tw);
/* 控制按钮 */
int by = cy + NCP_MEDIA_BTN_Y;
ncp_media_btn(NCP_MEDIA_PREV_CX, by, NCP_MEDIA_BTN_R - 4, 0, th);
int pk = (s->state == MEOW_MEDIA_PLAYING) ? 3 /* pause */ : 1 /* play */;
ncp_media_btn(NCP_MEDIA_PLAY_CX, by, NCP_MEDIA_BTN_R, pk, th);
ncp_media_btn(NCP_MEDIA_NEXT_CX, by, NCP_MEDIA_BTN_R - 4, 2, th);
/* 进度条 + 时间 */
uint32_t pos = meow_media_display_pos(s);
int px = tx, pw = NCP_MEDIA_CARD_X + NCP_MEDIA_CARD_W - 12 - px, py = cy + NCP_MEDIA_PROG_Y;
if (pw > 0) {
fill_rect(px, py, pw, 4, th->outline);
int fw = (s->dur_ms > 0) ? (int)((uint64_t)pos * pw / s->dur_ms) : 0;
if (fw > pw) fw = pw;
if (fw > 0) fill_rect(px, py, fw, 4, th->primary);
}
}
/* 卡片命中:按钮/进度(含 seek)。返回 1=已处理(内部已下发 PLAYCTL);0=未命中。 */
static int ncp_media_card_tap(int tx, int ty)
{
const meow_media_session_t *s = meow_media_current();
if (!s || s->state == MEOW_MEDIA_STOPPED) return 0;
int cy = ncp_media_y();
if (ty < cy || ty >= cy + NCP_MEDIA_H) return 0;
int by = cy + NCP_MEDIA_BTN_Y;
int cmd = 0; int64_t seek = 0;
if (tx >= NCP_MEDIA_PREV_CX - NCP_MEDIA_BTN_R && tx <= NCP_MEDIA_PREV_CX + NCP_MEDIA_BTN_R &&
ty >= by - NCP_MEDIA_BTN_R && ty <= by + NCP_MEDIA_BTN_R) cmd = 3; /* 上一首 */
else if (tx >= NCP_MEDIA_PLAY_CX - NCP_MEDIA_BTN_R && tx <= NCP_MEDIA_PLAY_CX + NCP_MEDIA_BTN_R &&
ty >= by - NCP_MEDIA_BTN_R && ty <= by + NCP_MEDIA_BTN_R)
cmd = (s->state == MEOW_MEDIA_PLAYING) ? 2u : 1u; /* 播放/暂停 */
else if (tx >= NCP_MEDIA_NEXT_CX - NCP_MEDIA_BTN_R && tx <= NCP_MEDIA_NEXT_CX + NCP_MEDIA_BTN_R &&
ty >= by - NCP_MEDIA_BTN_R && ty <= by + NCP_MEDIA_BTN_R) cmd = 4; /* 下一首 */
else if (s->dur_ms > 0) { /* 进度条 seek */
int px = NCP_MEDIA_CARD_X + 12 + 88 + 14;
int pw = NCP_MEDIA_CARD_X + NCP_MEDIA_CARD_W - 12 - px;
int py = cy + NCP_MEDIA_PROG_Y;
if (pw > 0 && ty >= py - 12 && ty <= py + 16 && tx >= px && tx <= px + pw) {
int frac = (tx - px) * 1000 / pw;
seek = (int64_t)s->dur_ms * frac / 1000;
cmd = 5;
}
}
if (!cmd) return 0;
int rc = meow_media_playctl(cmd, seek);
logmsg("notif-card: media tap cmd=%u seek=%lld rc=%d", cmd, (long long)seek, rc);
return 1;
}
/* 仅重绘媒体卡片带(进度/封面/按钮;不整面板、不刷状态栏)——局部刷新用。 */
static void ncp_repaint_media_card(void)
{
const meow_ui_theme_t *th = s_ui_theme();
if (!meow_media_has_source()) return;
int cy = ncp_media_y();
fill_rect(NCP_MEDIA_CARD_X, cy, NCP_MEDIA_CARD_W, NCP_MEDIA_H, th->surface_variant);
ncp_draw_media_card(th);
meow_dirty_add(NCP_MEDIA_CARD_X, cy, NCP_MEDIA_CARD_W, NCP_MEDIA_H);
meow_ui_flush();
}
static void draw_notif_center(int scroll, int redraw_sb)
{
const meow_ui_theme_t *th = s_ui_theme();
@@ -657,6 +820,9 @@ static void draw_notif_center(int scroll, int redraw_sb)
/* ⑧ 一键清除按钮(勿扰对称处的右半段) */
ncp_draw_clear(th);
/* 阶段D附录B:顶部媒体卡片(活动媒体会话时显示,固定于列表上方;列表可视区随之下移) */
ncp_draw_media_card(th);
/* 通知列表 */
ncp_draw_list(scroll);
@@ -722,7 +888,7 @@ static int ncp_slider_apply(int which, int tx, int save_now)
static int ncp_row_tap(int tx, int ty, int scroll, notif_click_t *click)
{
(void)tx; /* 行命中仅依赖 ty/scroll;tx 保留作接口一致(0-warning) */
int eff = ty - NCP_LIST_TOP + scroll;
int eff = ty - ncp_list_top() + scroll;
int row = mnp_row_at(eff, 0, g_nrows * NCP_ROW_H, NCP_ROW_H);
if (row < 0 || row >= g_nrows) return 0;
const ncp_row_t *r = &g_rows[row];
@@ -849,7 +1015,7 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
/* 首次绘制:先拍背景(应用列表)→ 渲染最终位置整面板 → 拍面板快照 → 还原背景到 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);
list_scroll = mnp_scroll_clamp(0, g_nrows * NCP_ROW_H, ncp_list_view());
ncp_snap_init();
if (g_ncp_ph > 0) {
anim_ok = 1;
@@ -869,6 +1035,8 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
anim_last = now_ms();
int need_paint = 0; /* 持久:跨 tick 保持(动画进行中延迟到稳态再画) */
int media_prev = -1; /* 阶段D附录B:上次媒体卡片显示态(检测 出现/消失 → 整面板重排) */
int media_paint = 0; /* 阶段D附录B:仅重绘媒体卡片带(进度/封面变化,局部刷新) */
for (;;) {
unsigned long long now = now_ms();
@@ -1030,10 +1198,10 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
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 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 &&
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);
@@ -1057,8 +1225,8 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
int ady2 = (tly - tdy) < 0 ? (tdy - tly) : (tly - tdy);
if (ady2 >= S_SCROLL_SLOP) {
int av = g_nrows * NCP_ROW_H;
int vw = NCP_LIST_BOT - NCP_LIST_TOP;
int in_over = (tdy >= NCP_LIST_TOP && tdy < NCP_LIST_BOT &&
int vw = ncp_list_view();
int in_over = (tdy >= ncp_list_top() && tdy < NCP_LIST_BOT &&
mnp_scroll_max(av, vw) > 0);
if (!in_over) {
int cur_off = mnp_anim_off(anim_p, NCP_ANIM_FIX, g_ncp_ph);
@@ -1081,9 +1249,9 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
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;
int view = ncp_list_view();
if (mnp_scroll_max(total, view) > 0 &&
tdy >= NCP_LIST_TOP && tdy < NCP_LIST_BOT) {
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);
@@ -1093,7 +1261,7 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
}
if (scroll_drag && td_ok) {
int total = g_nrows * NCP_ROW_H;
int view = NCP_LIST_BOT - NCP_LIST_TOP;
int view = ncp_list_view();
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),
@@ -1147,7 +1315,7 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
* 绝不把「滑列表」误判成「上滑关闭」——scroll_drag 一旦置位即跳过
* 下方 mnp_vertical_up 关闭判定。 */
int total = g_nrows * NCP_ROW_H;
int view = NCP_LIST_BOT - NCP_LIST_TOP;
int view = ncp_list_view();
int target = mnp_scroll_target(scroll_base, tly - scroll_y0, scroll_invert);
list_scroll = mnp_scroll_clamp(target, total, view);
if (scroll_rebound) {
@@ -1205,7 +1373,7 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
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);
ncp_list_view());
hit = 1; need_paint = 1;
}
/* ⑧ 一键清除(勿扰对称处右半段):先转储留档 → 再清空显示(留 ongoing/media/progress)。 */
@@ -1218,10 +1386,14 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
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);
ncp_list_view());
hit = 1; need_paint = 1;
}
if (!hit && ty >= NCP_LIST_TOP && ty < NCP_LIST_BOT) {
/* 阶段D附录B:顶部媒体卡片按钮/进度(含 seek)→ 下发 PLAYCTL。
* 卡片命中固定于列表上方,先于列表行命中处理。 */
if (!hit && meow_media_has_source() && ncp_media_card_tap(tx, ty)) {
hit = 1; need_paint = 1;
} else if (!hit && ty >= ncp_list_top() && ty < NCP_LIST_BOT) {
if (ncp_row_tap(tx, ty, list_scroll, click)) {
hit = 1;
if (anim_ok) anim_target = 0; /* 点击通知:dismiss + 记录启动 → 滑出收回 */
@@ -1253,7 +1425,18 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
/* 列表变化检测:~500ms 对比 count;变化重采集 + 重绘列表区 */
if (now - last_listchk >= 500) {
last_listchk = now;
/* 阶段D附录B:面板驻留时排空 IPC(App 上报 META 更新媒体会话)+ 媒体会话 TTL,再做展示刷新 */
meow_ipc_process();
meow_notif_tick();
meow_media_tick();
int m_on = meow_media_has_source() ? 1 : 0;
if (m_on != media_prev) {
media_prev = m_on; /* 卡片出现/消失 → 列表顶缘变化,需整面板重排 */
need_paint = 1;
list_scroll = mnp_scroll_clamp(list_scroll, g_nrows * NCP_ROW_H, ncp_list_view());
} else if (m_on) {
media_paint = 1; /* 已有源:进度/封面随 META 变化 → 仅重绘卡片带 */
}
if (ncp_rows_changed()) {
/* ⑦ 列表已变(新条/撤下/TTL):取消当前行左滑,避免行下标错位。⑨ 同时取消面板跟手。 */
row_swipe_row = -1; row_off = 0; row_settle = 0;
@@ -1262,10 +1445,11 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
panel_drag = 0; drag_seed_reveal = 0; drag_y0 = 0; drag_paint = 0;
ncp_refresh_rows();
list_scroll = mnp_scroll_clamp(list_scroll, g_nrows * NCP_ROW_H,
NCP_LIST_BOT - NCP_LIST_TOP);
ncp_list_view());
need_paint = 1;
}
}
if (media_paint) { ncp_repaint_media_card(); media_paint = 0; }
/* 开关动画推进(§9:状态时间轴 + 快照 blit 平移;present 尊重 SDK 帧率门控,动画不更高频)。
* 每 tick 依壁钟差推进 p;改 anim_target 即平滑转向(可打断,不跳变)。 */
@@ -1321,7 +1505,7 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
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);
ncp_list_view());
need_paint = 1; /* 行已移除/重排:整面板(列表区)重画 */
}
}