Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/meow_notif_panel.h
T
MeowOS a8ec8377ed 通知中心增强(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)
2026-08-30 17:01:47 +08:00

197 lines
8.9 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* meow_notif_panel.h — 通知中心面板的纯逻辑(header-only,宿主可单测)。
*
* 定位:把通知中心面板里「无副作用、可独立判定」的部分抽成 static inline,
* 供 launcher(meow_launcherd.c 内嵌面板)与宿主单测(test_notif_panel.c)共用同一份实现,
* 避免两端漂移;绘制/触摸循环等依赖 launcher static 符号的部分留在 meow_launcherd.c 内。
*
* 不依赖 launcher 头 / SDK 头,仅 <stdint.h> <string.h> <stdio.h> <time.h> —— 纯宿主可编。
*/
#ifndef MEOW_NOTIF_PANEL_H
#define MEOW_NOTIF_PANEL_H
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
/* ---- 手势判定 ---- */
/* 左缘宽度:与 launcher meow_touch.h MEOW_EDGE_W(24) / SDK 全局左缘返回同口径。
* 顶部下滑打开通知中心时,左缘起点保留给「左缘返回」,不与其抢占(spec-ref §5.1)。 */
#ifndef MNP_EDGE_W
#define MNP_EDGE_W 24
#endif
/* 顶部热区下滑 → 打开通知中心(纯函数)。
* down_x/down_y=抬手原点;dx/dy=抬手位移(当前-原点,即 t_last - t_down);hotzone=顶部热区高
* (launcher=SB_BAR_H=28);thresh=下滑触发位移阈值(launcher=MENU_SWIPE_THRESH=65)。
* 返回 1=应打开。约束:起点不在左缘(down_x>=MNP_EDGE_W,左缘保留给返回)+ 起点落在顶部热区
* (down_y ∈ [0,hotzone))+ 向下主导(dy>=thresh 且 |dy|>|dx|)。调用方通常先判左缘返回
* (meow_edge_back)+ 本函数,两者互斥。 */
static inline int mnp_top_swipe_down(int down_x, int down_y, int dx, int dy,
int hotzone, int thresh)
{
if (down_x < MNP_EDGE_W) return 0; /* 左缘保留给返回手势 */
if (down_y < 0 || down_y >= hotzone) return 0;
if (dy < thresh) return 0; /* 未达下滑阈值 / 向上 */
int ax = dx < 0 ? -dx : dx;
return ax < dy; /* 向下主导(横向位移小于竖向) */
}
/* 竖向主导上滑(面板内关闭):dy<=-thresh 且 |dy|>|dx|。返回 1=应关闭。 */
static inline int mnp_vertical_up(int dx, int dy, int thresh)
{
if (dy > -thresh) return 0;
int ax = dx < 0 ? -dx : dx;
return ax < -dy;
}
/* ---- 滑条 ---- */
/* 滑条值映射:px(屏幕坐标)→ 值,clamp 到 [min,max]。sx=track 左缘、sw=track 宽。
* 与 launcher 设置页 s_slider_val_from_x 同构(此处参数化,供面板亮度/音量滑条与单测用)。 */
static inline int mnp_slider_val_from_x(int px, int sx, int sw, int min, int max)
{
int range = max - min;
int v = min + (range ? (px - sx) * range / sw : 0);
if (v < min) v = min;
if (v > max) v = 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(防闪屏)。
* vmin<1 时钳到 1。与 SYSTEM_SETTINGS §1 / spec-ref A-3 一致(旧文档 25 为陈旧值)。 */
static inline int mnp_brightness_clamp(int v, int vmin)
{
if (vmin < 1) vmin = 1;
if (v < vmin) v = vmin;
if (v > 100) v = 100;
return v;
}
/* ---- 时间显示 ---- */
/* 通知时间(created_ms 墙钟 epoch ms)→ 显示串。
* <60s「刚刚」;<60min「N分钟前」;否则本地「HH:MM」。now_ms=当前墙钟 epoch ms。 */
static inline void mnp_time_ago(uint64_t created_ms, uint64_t now_ms, char *buf, int len)
{
if (!buf || len <= 0) return;
if (now_ms < created_ms) now_ms = created_ms; /* 时钟回拨防御 */
uint64_t diff = now_ms - created_ms;
if (diff < 60000u) { if (len >= 1) snprintf(buf, (size_t)len, "刚刚"); return; }
if (diff < 3600000u) {
snprintf(buf, (size_t)len, "%llu分钟前", (unsigned long long)(diff / 60000u));
return;
}
time_t t = (time_t)(created_ms / 1000u);
struct tm tm;
if (localtime_r(&t, &tm))
snprintf(buf, (size_t)len, "%02d:%02d", tm.tm_hour, tm.tm_min);
else if (len >= 1) snprintf(buf, (size_t)len, "--:--");
}
/* ---- 列表命中 ---- */
/* 通知列表行命中:y 为绝对纵坐标;list_top/list_bot 为列表区上下缘;row_h 为行高。
* 返回行号(0 起);越界返回 -1。 */
static inline int mnp_row_at(int y, int list_top, int list_bot, int row_h)
{
if (y < list_top || y >= list_bot) return -1;
int r = (y - list_top) / row_h;
return r;
}
/* 列表滚动 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;
int mx = total - view;
if (scroll < 0) scroll = 0;
if (scroll > mx) scroll = mx;
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 */