- 开关平移动画(时间轴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)
197 lines
8.9 KiB
C
197 lines
8.9 KiB
C
/* 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 */
|