feat: M6.10–M6.19 批量收编 + M6.19 推卡验收
- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘) - OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复 - ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640 - 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫) - 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
This commit is contained in:
1 parent
c2ee7626a4
commit
3d70a3dd2c
176 files changed
+15507
-597
No files matched your search
@@ -2171,6 +2171,12 @@ static int launch_app_chain(struct app *a, const char *chain)
|
||||
/* §10.5:App 前台会话结束,清会话标记(ipc_dispatch 不再受理 MSG_NOTIF_OPEN) */
|
||||
g_app_session_active = 0;
|
||||
meow_notif_open_clear();
|
||||
/* M2.17 兜底:App 会话结束(waitpid 回收/崩溃/被杀,回主界面)清 fg_act ——
|
||||
* App 在 keep_awake activity(如 ncm 播放页)被 OOM/崩溃杀死且未弹回 home 时,g_fg_act 会
|
||||
* 滞留 "<act>",使主页 fg_keep_awake()==1 不再自动息屏。此处兜底清空(App 已退出/死亡,
|
||||
* 主界面非 keep_awake);App 存活时 fg_act 保持不清,仅会话整体结束才清。正常路径 App
|
||||
* 弹回 home 已由 ACT_FOCUS(home) 更新(非 keep_awake),此兜底与其幂等不冲突。 */
|
||||
meow_fg_act_back();
|
||||
/* AMSD_DESIGN §5.1:崩溃尾巴不丢 —— App 已死,终局排空剩余 stdout/stderr(含未完结行)
|
||||
* 先转发 amsd,再报 EXIT / crash。仅当有读端(管道建立成功)才 drain。 */
|
||||
if (out_fd >= 0) {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
PASS S1 drain required (app was SIGSTOP/frozen)
|
||||
PASS S1 no drain needed (media keep-alive, never stopped)
|
||||
PASS S1 drain window > SDK 200ms throttle
|
||||
PASS S1b keep-alive app needs EVIOCGRAB isolation
|
||||
PASS S1b frozen app -> SIGCONT drain, not grab
|
||||
PASS S1b keep-alive: grab path (drain==0)
|
||||
PASS S1b frozen: drain path (grab==0)
|
||||
PASS S2 force status bar re-sync after unlock
|
||||
PASS S2b lock re-cover ms > SDK suppression window (200ms)
|
||||
PASS S2b lock re-cover ms = 300 (marker-window margin)
|
||||
PASS S1c wake, no sleep -> no lock
|
||||
PASS S1c timeout sleep (auto) -> lock
|
||||
PASS S1c manual power sleep (manual) -> lock (was missing)
|
||||
PASS S1c both -> lock
|
||||
PASS S4 system 0 (never) -> lock caps 30s
|
||||
PASS S4 system absent -> lock caps 30s
|
||||
PASS S4 system 30 -> 30
|
||||
PASS S4 system 60 -> 30 (cap, shorter)
|
||||
PASS S4 system 10 -> 10 (respect shorter)
|
||||
PASS S4 system 1 -> 1 (respect shorter)
|
||||
PASS S4 idle>=off & screen on -> self sleep
|
||||
PASS S4 idle<off -> not yet
|
||||
PASS S4 screen off -> no re-sleep
|
||||
PASS S4 power edge -> wake to lock
|
||||
PASS S4 touch -> wake to lock
|
||||
PASS S4 no wake input -> stay asleep
|
||||
PASS S3 app died -> unified exit path (redraw home)
|
||||
PASS S3 app alive -> normal restore
|
||||
PASS S3 abort=0 (menu lock, no overlay pid)
|
||||
PASS S3 abort=0 (no overlay pid, zombie ignored)
|
||||
PASS S3 abort=0 (overlay pid, app alive)
|
||||
PASS S3 abort=1 (overlay pid + zombie -> abort lock)
|
||||
|
||||
ALL PASS (32 passed, 0 failed)
|
||||
@@ -58,7 +58,17 @@ void meow_panel_on(void)
|
||||
{
|
||||
int f = open(FB_DEV, O_RDWR);
|
||||
if (f >= 0) { ioctl(f, FBIOBLANK, 0); close(f); }
|
||||
write_brightness(meow_saved_brightness > 0 ? meow_saved_brightness : 60, "meow_panel_on");
|
||||
/* 亮度恢复以**用户设置**为准,而非 meow_saved_brightness(可能过期)。
|
||||
* watchdog 独立息屏(wd_panel_off 直接 write_brightness(0),见 meow_watchdog.c)**不会**
|
||||
* 更新本进程 meow_saved_brightness——若用其恢复会回错值(真机「PIN 亮屏亮度跳高」,
|
||||
* 2026-09-03)。settings 已从盘上读到→按 display.brightness(clamp)恢复;未读到(早启动)
|
||||
* →回退 meow_saved_brightness 现值(保持旧行为,防开机竞态)。 */
|
||||
if (meow_settings_loaded()) {
|
||||
int b = meow_settings_brightness_clamped();
|
||||
if (b > 0) { write_brightness(b, "meow_panel_on"); meow_saved_brightness = b; }
|
||||
} else {
|
||||
write_brightness(meow_saved_brightness > 0 ? meow_saved_brightness : 60, "meow_panel_on");
|
||||
}
|
||||
meow_screen_on = 1;
|
||||
/* M2.8 Bug 3-residual:亮屏必须用 fb_pan_buf0_force 无条件重发 pan。fb_norm_buf0 在
|
||||
* 缓存 fb_active_yoffset==0 时提前返回,而 FBIOBLANK(4)->(0) 后 LCDC 扫描地址可能已复位
|
||||
|
||||
@@ -38,6 +38,16 @@ static const int lk_keys[LK_ROWS][LK_COLS] = {
|
||||
{ LK_ACT_CLR, 0, LK_ACT_BS },
|
||||
};
|
||||
|
||||
/* ---- M7.1b 锁屏控件化:PIN 数字键盘 / 时钟 / 提示 / PIN 圆点 改用 SDK 控件 ----
|
||||
* 旧自绘锁屏 UI 保留在 #if 0(回退位,见 lk_draw/lk_key_hit),新 SDK 渲染默认启用;
|
||||
* run_lock_screen 会话骨架(EVIOCGRAB / 自睡 / 解锁脏区修复 / 物理键转发)不动,只替换
|
||||
* 它调用的绘制/命中函数体。控件 owner 钉 LK_SENTINEL_ACT(子页 0x52 之外的独立哨兵)。 */
|
||||
#define LK_SENTINEL_ACT 0x51 /* 锁屏控件 owner 哨兵(与子页 SUB_SENTINEL_ACT=0x52 区分) */
|
||||
#define LK_DOWN_NONE (-10) /* 无键按下哨兵(关键值范围 LK_ACT_CLR=-2/-1/0-9) */
|
||||
static meow_widget *lk_key_w[LK_ROWS][LK_COLS]; /* 12 键按钮(meow_widget_custom) */
|
||||
static int lk_widgets_ready = 0;
|
||||
static int g_lk_down_key = LK_DOWN_NONE; /* 当前按下的键(跟手指按压态) */
|
||||
|
||||
/* 键盘按钮 row*3+col 的命中矩形(x,y,w,h) */
|
||||
static void lk_key_rect(int row, int col, int *x, int *y, int *w, int *h)
|
||||
{
|
||||
@@ -79,6 +89,121 @@ static void lk_draw_pin(int entered, const meow_ui_theme_t *th)
|
||||
lk_pin_dot(x0 + i * gap, y, r, i < entered, th->primary, th->surface);
|
||||
}
|
||||
|
||||
/* ---- M7.1b 控件化:SDK meow_widget 键盘 + meow_font 文字 ---- */
|
||||
|
||||
/* 数字键标签(1-9/0 单字符;static 缓冲各调用立即消费,无跨调用残留) */
|
||||
static const char *lk_key_digit(int key)
|
||||
{
|
||||
static char d[2];
|
||||
d[0] = (char)('0' + key);
|
||||
d[1] = 0;
|
||||
return d;
|
||||
}
|
||||
|
||||
/* 键渲染:surfaceContainer 圆角底(MD3)+ 按压态 primary 26% α + 居中标签(SDK meow_font)。
|
||||
* 颜色全部取 meow_theme_get()(SDK 单一权威:MD3/MIUIx × 明暗自动);读 w->state 的
|
||||
* MEOW_WS_PRESSED 位(跟手指,由 lk_set_down_key 置/清)。 */
|
||||
static void lk_key_render(meow_widget *w, const meow_theme *th, void *ctx)
|
||||
{
|
||||
int key = (int)(intptr_t)ctx;
|
||||
int rc[4]; meow_widget_rect(w, rc); /* 绝对坐标 x,y,w,h(控件不透明,用 SDK 只读几何) */
|
||||
int x = rc[0], y = rc[1], ww = rc[2], hh = rc[3];
|
||||
unsigned st = meow_widget_state(w);
|
||||
int rad = (th->radius > 0) ? th->radius : 10;
|
||||
meow_gfx_fill_round(x, y, ww, hh, rad, th->surfaceContainer);
|
||||
if (st & MEOW_WS_PRESSED)
|
||||
meow_gfx_fill_round_alpha(x, y, ww, hh, rad, th->primary, 66);
|
||||
const char *label;
|
||||
int size;
|
||||
if (key >= 0) { size = 30; label = lk_key_digit(key); }
|
||||
else if (key == LK_ACT_CLR) { size = 22; label = mfont_ready ? "清空" : "C"; }
|
||||
else { size = 22; label = mfont_ready ? "退格" : "DEL"; }
|
||||
int cx = x + ww / 2;
|
||||
int cy = y + hh / 2 - meow_ui_text_height(size) / 2;
|
||||
meow_ui_text_center(cx, cy, label, size, th->onSurface);
|
||||
}
|
||||
|
||||
/* 懒创建 12 键(owner=LK_SENTINEL_ACT;仅首次调用建一次)。布局几何沿用 lk_key_rect。 */
|
||||
static void lk_widgets_build(void)
|
||||
{
|
||||
if (lk_widgets_ready) return;
|
||||
meow_ui_activity_push(LK_SENTINEL_ACT);
|
||||
for (int r = 0; r < LK_ROWS; r++) {
|
||||
for (int c = 0; c < LK_COLS; c++) {
|
||||
int x, y, w, h; lk_key_rect(r, c, &x, &y, &w, &h);
|
||||
lk_key_w[r][c] = meow_widget_custom_create(x, y, w, h, lk_key_render, NULL,
|
||||
(void *)(intptr_t)lk_keys[r][c]);
|
||||
}
|
||||
}
|
||||
meow_ui_activity_pop();
|
||||
lk_widgets_ready = 1;
|
||||
}
|
||||
|
||||
/* 键值 -> 按钮(按压态置/清位用)。 */
|
||||
static meow_widget *lk_widget_for_key(int key)
|
||||
{
|
||||
for (int r = 0; r < LK_ROWS; r++)
|
||||
for (int c = 0; c < LK_COLS; c++)
|
||||
if (lk_keys[r][c] == key) return lk_key_w[r][c];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 坐标 -> 键值(SDK meow_widget_hit;owner==active 已由调用方保证)。未命中返回 LK_DOWN_NONE。 */
|
||||
static int lk_key_value_at(int x, int y)
|
||||
{
|
||||
for (int r = 0; r < LK_ROWS; r++)
|
||||
for (int c = 0; c < LK_COLS; c++)
|
||||
if (lk_key_w[r][c] && meow_widget_hit(lk_key_w[r][c], x, y))
|
||||
return lk_keys[r][c];
|
||||
return LK_DOWN_NONE;
|
||||
}
|
||||
|
||||
/* 更新按压态(跟手指):清旧键状态位、置新键;返回 1=按键变化(调用方据此重绘)。 */
|
||||
static int lk_set_down_key(int key)
|
||||
{
|
||||
if (key == g_lk_down_key) return 0;
|
||||
meow_widget *old = lk_widget_for_key(g_lk_down_key);
|
||||
if (old) meow_widget_set_state(old, MEOW_WS_PRESSED, 0);
|
||||
meow_widget *nw = lk_widget_for_key(key);
|
||||
if (nw) meow_widget_set_state(nw, MEOW_WS_PRESSED, 1);
|
||||
g_lk_down_key = key;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* PIN 输入圆点(● 遮罩:已输入=primary 实心;未输入=primary 环 + surface 孔)。 */
|
||||
static void lk_draw_pin_dots(int entered, const meow_theme *mt)
|
||||
{
|
||||
int cx = fb_xres / 2;
|
||||
int y = 210;
|
||||
int gap = 48, r = 20;
|
||||
int x0 = cx - (3 * gap) / 2;
|
||||
for (int i = 0; i < LCK_PIN_LEN; i++) {
|
||||
int dx = x0 + i * gap;
|
||||
fill_circle(dx, y, r, mt->primary);
|
||||
if (i >= entered) fill_circle(dx, y, r - 3, mt->surface);
|
||||
}
|
||||
}
|
||||
|
||||
/* 键命中:旧自绘(lk_key_rect+mt_in)保留在 #if 0(回退位);M7.1b 走 SDK meow_widget_hit
|
||||
* (owner==active + enabled 双闸门在 SDK 内;命中点=按下原点 mt->down_x/down_y,与原 mt_in 同源)。 */
|
||||
#if 0
|
||||
static int lk_key_hit(const meow_touch *mt, int row, int col)
|
||||
{
|
||||
int x, y, w, h; lk_key_rect(row, col, &x, &y, &w, &h);
|
||||
return mt_in(mt, x, y, w, h);
|
||||
}
|
||||
#else
|
||||
static int lk_key_hit(const meow_touch *mt, int row, int col)
|
||||
{
|
||||
if (!lk_key_w[row][col]) return 0;
|
||||
return meow_widget_hit(lk_key_w[row][col], mt->down_x, mt->down_y);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
/* ============ 旧自绘锁屏整帧(M7.1b 回退位)============
|
||||
* 原 lk_draw:fill_rect 全屏底 + lk_text(时钟/状态)+ lk_draw_pin(●)+ fill_round_rect 键盘。
|
||||
* 一键回退:把下面 #if 0 改成 #if 1(并把新 lk_draw 的 #else 分支由 #else 改走旧轮廓),设备即回旧观感。 */
|
||||
static void lk_draw(int entered, const char *status, time_t locked_until)
|
||||
{
|
||||
const meow_ui_theme_t *th = s_ui_theme();
|
||||
@@ -116,6 +241,36 @@ static void lk_draw(int entered, const char *status, time_t locked_until)
|
||||
fb_pan_buf0_force();
|
||||
meow_ui_flush();
|
||||
}
|
||||
#else
|
||||
/* ============ M7.1b 锁屏控件化整帧(默认启用)============
|
||||
* 整帧不透明 surface 底(§310 脏区修复关键:全屏覆盖,无 App 帧透出)+ SDK 字体时钟/提示
|
||||
* + 主题色 PIN 圆点 + meow_widget 键盘(surfaceContainer 圆角 / 按压态 primary 26% α / MD3 主题)。
|
||||
* 颜色全部取 meow_theme_get()(SDK 单一权威:MD3/MIUIx × 明暗自动)。 */
|
||||
static void lk_draw(int entered, const char *status, time_t locked_until)
|
||||
{
|
||||
const meow_theme *mt = meow_theme_get();
|
||||
meow_ui_fill_rect(0, 0, fb_xres, fb_yres, mt->surface); /* 全屏不透明表面底 */
|
||||
lk_widgets_build(); /* 懒创建 12 键 */
|
||||
/* 时钟/提示(居中,SDK meow_font):锁定倒计时标题 */
|
||||
meow_ui_text_center(fb_xres / 2, 120, locked_until > 0 ? "已锁定" : "输入 PIN 解锁",
|
||||
24, mt->onSurface);
|
||||
/* PIN 输入圆点(● 遮罩) */
|
||||
lk_draw_pin_dots(entered, mt);
|
||||
/* 状态行:错误提示(PIN 错误)着 error;倒计时/其它信息用 onSurfaceVariant */
|
||||
int is_err = status[0] && (strstr(status, "PIN 错误") != NULL ||
|
||||
strstr(status, "WRONG PIN") != NULL);
|
||||
uint32_t status_col = status[0] ? (is_err ? mt->error : mt->onSurfaceVariant)
|
||||
: mt->onSurfaceVariant;
|
||||
meow_ui_text_center(fb_xres / 2, 260, status, 18, status_col);
|
||||
/* 12 键(SDK meow_widget;命中走 lk_key_hit → meow_widget_hit) */
|
||||
for (int r = 0; r < LK_ROWS; r++)
|
||||
for (int c = 0; c < LK_COLS; c++)
|
||||
if (lk_key_w[r][c]) meow_widget_draw(lk_key_w[r][c]);
|
||||
meow_dirty_full();
|
||||
fb_pan_buf0_force();
|
||||
meow_ui_flush();
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 阻塞式锁屏页。返回 1=已解锁,0=锁屏期间电源键息屏。 */
|
||||
int run_lock_screen(void)
|
||||
@@ -159,7 +314,26 @@ int run_lock_screen(void)
|
||||
return 1;
|
||||
}
|
||||
|
||||
lk_draw(entered, status, lock_until); /* 首帧(fill_rect 全屏不透明主题底 + 键盘) */
|
||||
/* M7.1 解锁恢复:锁屏是整帧不透明覆盖(lk_draw fill_rect 全屏),退出后底层界面不会自动重绘
|
||||
* —— 只有 App 自绘的控件区域刷新,未刷新区残留锁屏 PIN 最后一帧(解锁后整屏脏区根因)。此处
|
||||
* 在锁屏首扑前把底层帧(SDK back_buf = 进入前的 App 帧/主界面帧)快照下来;解锁(unlocked=1)
|
||||
* 时整帧回写 back_buf + present,强制底层界面完整重绘(app_launcher.c 的 lock-overlay 关闭路径
|
||||
* 随后 pm_capture_app_frame 读 fb0 即得到正确 App 帧,不再读到锁帧)。 */
|
||||
unsigned char *lk_bg_snap = NULL;
|
||||
size_t lk_bg_len = 0;
|
||||
if (back_buf_ok && fb && fb_line > 0 && fb_yres > 0) {
|
||||
lk_bg_len = (size_t)fb_line * (size_t)fb_yres;
|
||||
lk_bg_snap = malloc(lk_bg_len);
|
||||
if (lk_bg_snap) memcpy(lk_bg_snap, fb, lk_bg_len);
|
||||
else { lk_bg_len = 0; logmsg("lock: bg snapshot malloc fail (len=%zu)", lk_bg_len); }
|
||||
}
|
||||
|
||||
/* M7.1b:锁屏控件 owner 闸门打开(meow_widget_hit / 按压态需 active==LK_SENTINEL_ACT)。
|
||||
* 会话骨架(EVIOCGRAB/自睡/解锁脏区修复)不受影响;done/died 处对称 pop。 */
|
||||
meow_ui_activity_push(LK_SENTINEL_ACT);
|
||||
g_lk_down_key = LK_DOWN_NONE;
|
||||
|
||||
lk_draw(entered, status, lock_until); /* 首帧(全屏不透明主题底 + 控件化键盘) */
|
||||
|
||||
int unlocked = 0;
|
||||
unsigned long long entry_ms = now_ms();
|
||||
@@ -173,6 +347,9 @@ int run_lock_screen(void)
|
||||
unsigned long long last_act = now_ms();
|
||||
int asleep = 0;
|
||||
int prev_pwr_down = 0;
|
||||
/* M7.1 锁屏期物理键保留语义(音量/媒体键):边沿跟踪基线(锁屏是 EVIOCGRAB 独占真模态,
|
||||
* 这些键只能在本层显式处理,不会转发到系统)。 */
|
||||
int prev_vold = 0, prev_volu = 0, prev_play = 0, prev_prv = 0, prev_nxt = 0;
|
||||
for (;;) {
|
||||
time_t now = time(NULL);
|
||||
int redraw = 0;
|
||||
@@ -246,6 +423,7 @@ int run_lock_screen(void)
|
||||
meow_panel_on();
|
||||
asleep = 0;
|
||||
last_act = now_ms();
|
||||
g_lk_down_key = LK_DOWN_NONE; /* M7.1b:唤醒复位按压态基线 */
|
||||
lk_draw(entered, status, lock_until); /* 重画锁屏页回锁 */
|
||||
continue;
|
||||
}
|
||||
@@ -256,6 +434,50 @@ int run_lock_screen(void)
|
||||
meow_panel_off();
|
||||
break; /* return 0 */
|
||||
}
|
||||
/* M7.1 锁屏期物理键仍执行其语义(用户定案:音量键调音量、媒体键控播放/上下曲)。
|
||||
* 锁屏层 EVIOCGRAB 独占这些输入(真模态),不转发系统,故必须在此显式处理,不能依赖别处。
|
||||
* 音量沿用 launcher 既有递增/递减路径(settings_volume_adj —— 起 1% 线性渐变,
|
||||
* 长按由下方 vol_ramp_tick 每圈推进);播放/上下曲经 meow_media_playctl 下发媒体指令。 */
|
||||
int vold = (kp_keys.ok) && kp_is_down(&kp_keys, KEY_VOLUMEDOWN);
|
||||
int volu = (kp_keys.ok) && kp_is_down(&kp_keys, KEY_VOLUMEUP);
|
||||
int play = (kp_keys.ok) && kp_is_down(&kp_keys, KEY_PLAYPAUSE);
|
||||
int prv = (kp_keys.ok && kp_is_down(&kp_keys, KEY_PREVIOUSSONG)) ||
|
||||
(kp_power.ok && kp_is_down(&kp_power, KEY_PREVIOUSSONG));
|
||||
int nxt = (kp_keys.ok && kp_is_down(&kp_keys, KEY_NEXT)) ||
|
||||
(kp_power.ok && kp_is_down(&kp_power, KEY_NEXT));
|
||||
if (vold && !prev_vold) { settings_volume_adj(-settings_volume_step); last_act = now_ms(); }
|
||||
if (volu && !prev_volu) { settings_volume_adj(+settings_volume_step); last_act = now_ms(); }
|
||||
if (play && !prev_play) {
|
||||
const meow_media_session_t *ms = meow_media_current();
|
||||
if (ms) {
|
||||
uint32_t c = (ms->state == MEOW_MEDIA_PLAYING) ? 2u : 1u; /* 暂停/播放 */
|
||||
int rc = meow_media_playctl(c, 0);
|
||||
logmsg("lock: play key -> media cmd=%u rc=%d (%s)", c, rc, ms->app_id);
|
||||
} else {
|
||||
logmsg("lock: play key -> media-reserved (no media source)");
|
||||
}
|
||||
last_act = now_ms();
|
||||
}
|
||||
if (prv && !prev_prv) {
|
||||
if (meow_media_current()) { int rc = meow_media_playctl(3, 0); logmsg("lock: prev key -> cmd=3 rc=%d", rc); }
|
||||
else logmsg("lock: prev key -> media-reserved (no media source)");
|
||||
last_act = now_ms();
|
||||
}
|
||||
if (nxt && !prev_nxt) {
|
||||
if (meow_media_current()) { int rc = meow_media_playctl(4, 0); logmsg("lock: next key -> cmd=4 rc=%d", rc); }
|
||||
else logmsg("lock: next key -> media-reserved (no media source)");
|
||||
last_act = now_ms();
|
||||
}
|
||||
prev_vold = vold; prev_volu = volu; prev_play = play; prev_prv = prv; prev_nxt = nxt;
|
||||
/* 长按音量键线性渐变(同主菜单 vol_ramp 路径,每圈推进 1%)。锁屏态不刷状态栏
|
||||
* (状态栏被锁屏页覆盖),仅保持真实音量/DAC 同步。 */
|
||||
if (vol_ramp_dir != 0) {
|
||||
int held = (vol_ramp_dir > 0)
|
||||
? (kp_keys.ok && kp_is_down(&kp_keys, KEY_VOLUMEUP))
|
||||
: (kp_keys.ok && kp_is_down(&kp_keys, KEY_VOLUMEDOWN));
|
||||
int going = vol_ramp_tick(held);
|
||||
if (!going) logmsg("lock: volume ramp end -> %d", settings_volume_get());
|
||||
}
|
||||
/* 空闲超时 → 自睡(锁态保持,仍在锁屏循环,唤醒回锁屏页) */
|
||||
if (meow_screen_on && now_ms() - last_act >= lk_off_ms) {
|
||||
logmsg("lock: idle %ds -> self screen off (stay locked)", lk_off_sec);
|
||||
@@ -265,8 +487,6 @@ int run_lock_screen(void)
|
||||
}
|
||||
}
|
||||
}
|
||||
(void)kp_is_down(&kp_keys, KEY_VOLUMEDOWN);
|
||||
(void)kp_is_down(&kp_keys, KEY_VOLUMEUP);
|
||||
|
||||
/* 触摸:只认键盘 tap(滑动手势忽略:不左缘返回、不通知中心下拉);触摸即输入活动,重置自睡计时 */
|
||||
if (tfd >= 0 && !asleep) {
|
||||
@@ -280,17 +500,24 @@ int run_lock_screen(void)
|
||||
if (type == EV_ABS) {
|
||||
if (code == ABS_X || code == ABS_MT_POSITION_X) mt_abs(&mt, val, mt.last_y);
|
||||
else if (code == ABS_Y || code == ABS_MT_POSITION_Y) mt_abs(&mt, mt.last_x, val);
|
||||
/* M7.1b 跟手指按压态:down 且两轴有效才定键(goodix 时序免疫);
|
||||
* 键变化(滑动换键)即时重绘按压高亮。 */
|
||||
if (mt.down && mt.last_x >= 0 && mt.last_y >= 0) {
|
||||
if (lk_set_down_key(lk_key_value_at(mt.last_x, mt.last_y)))
|
||||
redraw = 1;
|
||||
}
|
||||
} else if (type == EV_KEY && code == BTN_TOUCH) {
|
||||
if (val && !mt.down) {
|
||||
mt_down(&mt);
|
||||
} else if (!val && mt.down) {
|
||||
lk_set_down_key(LK_DOWN_NONE); /* M7.1b:抬手清按压态 */
|
||||
redraw = 1;
|
||||
int dx, dy;
|
||||
if (mt_up(&mt, &dx, &dy)) {
|
||||
int handled = 0;
|
||||
for (int r = 0; r < LK_ROWS && !handled; r++) {
|
||||
for (int c = 0; c < LK_COLS && !handled; c++) {
|
||||
int x, y, w, h; lk_key_rect(r, c, &x, &y, &w, &h);
|
||||
if (!mt_in(&mt, x, y, w, h)) continue;
|
||||
if (!lk_key_hit(&mt, r, c)) continue; /* M7.1b SDK 控件命中 */
|
||||
handled = 1;
|
||||
int key = lk_keys[r][c];
|
||||
if (lock_until > 0) break; /* 锁定期间键盘不响应 */
|
||||
@@ -339,6 +566,17 @@ int run_lock_screen(void)
|
||||
usleep(POLL_MS * 1000);
|
||||
}
|
||||
done:
|
||||
meow_ui_activity_pop(); /* M7.1b:锁屏控件 owner 闸门关闭(与进场 push 对称) */
|
||||
g_lk_down_key = LK_DOWN_NONE;
|
||||
/* M7.1 解锁恢复:把锁屏首扑前快照的底层帧整帧回写 back_buf + present,强制底层 App/主界面
|
||||
* 完整重绘(消除「解锁后整屏 = PIN 最后一帧,仅 App 控件自绘区刷新」的脏区残留)。 */
|
||||
if (unlocked && lk_bg_snap && back_buf_ok && fb && lk_bg_len > 0) {
|
||||
memcpy(fb, lk_bg_snap, lk_bg_len);
|
||||
meow_dirty_full();
|
||||
meow_ui_flush();
|
||||
logmsg("lock: unlock -> restore underlying frame (full repaint)");
|
||||
}
|
||||
if (lk_bg_snap) { free(lk_bg_snap); lk_bg_snap = NULL; lk_bg_len = 0; }
|
||||
if (tfd >= 0) {
|
||||
int grab = 0; /* 显式释放 EVIOCGRAB(关闭 tfd 也会自动解除,双保险) */
|
||||
ioctl(tfd, EVIOCGRAB, &grab);
|
||||
@@ -349,6 +587,9 @@ done:
|
||||
logmsg("lock: exit unlocked=%d screen_on=%d", unlocked, meow_screen_on);
|
||||
return unlocked;
|
||||
died:
|
||||
meow_ui_activity_pop(); /* M7.1b:锁屏控件 owner 闸门关闭(与进场 push 对称) */
|
||||
g_lk_down_key = LK_DOWN_NONE;
|
||||
if (lk_bg_snap) { free(lk_bg_snap); lk_bg_snap = NULL; lk_bg_len = 0; }
|
||||
if (tfd >= 0) {
|
||||
int grab = 0;
|
||||
ioctl(tfd, EVIOCGRAB, &grab);
|
||||
|
||||
@@ -20,7 +20,7 @@ int menu_opened_blank = 0;
|
||||
static void launch_fail_toast(char *toast, size_t n, const char *name)
|
||||
{
|
||||
const char *bd = meow_dep_block_reason();
|
||||
if (bd && bd[0]) snprintf(toast, n, "缺依赖: %s", bd);
|
||||
if (bd && bd[0]) snprintf(toast, n, meow_str_zh("menu.missing_dep", "缺依赖: %s"), bd);
|
||||
else snprintf(toast, n, "CANNOT START: %s", name);
|
||||
}
|
||||
|
||||
@@ -102,7 +102,8 @@ static void draw_header_content(void)
|
||||
int tsize = menu_title_size(); /* 标题字号接 font.size.title */
|
||||
/* M2.6 标题真字体(字号接 font.size.title;回退点阵3x);MD3 on_surface 主文字色 */
|
||||
if (mfont_ready) {
|
||||
meow_font_drawtext((uint16_t *)fb, 24, 48, "MeowOS 应用", rgb565(th->on_surface), tsize);
|
||||
meow_font_drawtext((uint16_t *)fb, 24, 48, meow_str_zh("menu.title", "MeowOS 应用"),
|
||||
rgb565(th->on_surface), tsize);
|
||||
fill_rect(0, 48 + meow_font_line_height(tsize) + 2, fb_xres, 2, th->outline);
|
||||
} else {
|
||||
draw_text(24, 28, "MEOWAPPS", 3, th->on_surface);
|
||||
@@ -171,6 +172,37 @@ static meow_img *s_app_icons[MAX_APPS];
|
||||
/* 当前按压命中的应用格(-1=无);触摸按下置位、抬起清除(按压态视觉) */
|
||||
static int g_menu_pressed = -1;
|
||||
|
||||
/* ================================================================== */
|
||||
/* M7.4:主界面应用区迁 SDK 虚拟化控件(meow_lazygrid 宫格 + meow_listview 列表 + meow_scroll)。
|
||||
*
|
||||
* 铁律(M7 §4/§5):开机必经主界面,最高风险。本块只替换「绘制函数体 + 命中函数体」——
|
||||
* draw_menu 应用区体、menu_hit_app 体、menu_cell_rect 体;run_menu 会话骨架(触摸/热键/IPC/
|
||||
* 息屏/自动超时/跨会话)逐字保留。旧手绘路径(draw_menu 手绘循环/menu_draw_app_cell/menu_cell_rect
|
||||
* 旧体/menu_hit_app 旧体)各以 #if 0 保留为回退位;新 SDK 路径默认启用。
|
||||
*
|
||||
* 控件实例惰性创建一次(首跑 ensure),跨多次 run_menu 复用;热路径(draw/hit/scroll)零 malloc
|
||||
* (SDK lazygrid/listview 均静态有界池)。布局切换(ui.home_layout)在绘/命中时按 menu_layout()
|
||||
* 选择对应控件(两者同存,互不干扰);主题色全取 s_ui_theme()(= SDK meow_theme 单一权威)。
|
||||
*
|
||||
* 几何同源:应用区视口菜单由 menu_app_region() 计算(与 menu_cell_rect/menu_hit_app/draw_menu
|
||||
* 共用),保证虚拟化控件绘制的格/行位置 = 命中所用几何 = 脏区标脏几何,三者不错位。 */
|
||||
#define MENU_SDK_ACT 0x55 /* 主界面控件 owner 哨兵(不与电源菜单0x50/NowPlaying0x51/子页0x52/
|
||||
* 设置页0x53/通知中心0x54 冲突) */
|
||||
|
||||
/* 数据源 ctx(bind/count/draw 回调读应用数组;run_menu 的 apps[]/napps 每帧经此对齐)。 */
|
||||
typedef struct { struct app *apps; int napps; } menu_sdk_ctx_t;
|
||||
static menu_sdk_ctx_t s_menu_ctx; /* 应用数据源(count/bind/draw 读) */
|
||||
static meow_lazygrid *s_menu_grid = NULL; /* 宫格(layout=0) */
|
||||
static meow_scroll *s_menu_grid_sc = NULL;/* 宫格滚动物理(scroll_attach) */
|
||||
static meow_widget *s_menu_list = NULL; /* 列表(layout=1) */
|
||||
static meow_scroll *s_menu_list_sc = NULL;/* 列表滚动物理 */
|
||||
static int s_menu_sdk_init = 0; /* 控件集已建(首跑建一次,跨次复用) */
|
||||
|
||||
/* 前置声明(menu_cell_rect 定义在下方 451 行,早于本块函数定义;此处仅给签名)。 */
|
||||
static void menu_app_region(int apps_top, int layout, int *vx, int *vy, int *vw, int *vh);
|
||||
static int menu_grid_cell_w(void);
|
||||
static void menu_sdk_ensure(int apps_top);
|
||||
|
||||
/* 背景状态(display.background:纯色名 or 图片路径) */
|
||||
static meow_img *s_bg_img = NULL; /* 解码后的背景图(缓存,NULL=纯色) */
|
||||
static int s_bg_is_img = 0; /* 1=图片背景 */
|
||||
@@ -334,6 +366,27 @@ static void menu_bg_paint(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* 局部重铺背景(x,y,w,h 区域):图片缓存 blit 对应行或纯色填充。供宫格滚动局部刷时重铺背景区,
|
||||
* 修「自定义图片背景 + 超屏滚动」残影(M7.4 遗留②)——lazygrid_scroll_attach 的 redraw 只重画
|
||||
* 格而不重铺背景,超屏滚动后视口内遗留旧帧像素。与 menu_bg_paint 同源(s_bg_cache/menu_bg)。 */
|
||||
static void menu_bg_paint_region(int x, int y, int w, int h)
|
||||
{
|
||||
int W = fb_xres, H = fb_yres;
|
||||
if (x < 0) { w += x; x = 0; }
|
||||
if (y < 0) { h += y; y = 0; }
|
||||
if (x + w > W) w = W - x;
|
||||
if (y + h > H) h = H - y;
|
||||
if (w <= 0 || h <= 0) return;
|
||||
if (s_bg_is_img && s_bg_img && s_bg_cache) {
|
||||
int pitch = meow_ui_back_buf_pitch();
|
||||
for (int r = 0; r < h; r++)
|
||||
memcpy(fb + (size_t)(y + r) * pitch + (size_t)x * 2,
|
||||
s_bg_cache + (size_t)(y + r) * W + x, (size_t)w * 2);
|
||||
} else {
|
||||
fill_rect(x, y, w, h, menu_bg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 应用图标路径:app.json icon 相对应用目录(dirname(exec))/ 绝对;失败回退卡根相对。 */
|
||||
static void menu_icon_path(const struct app *a, char *out, int olen)
|
||||
{
|
||||
@@ -447,10 +500,30 @@ static void menu_draw_icon(const struct app *a, meow_img *icon, int x, int y, in
|
||||
if (icon) { meow_ui_img_cover(x, y, sz, icon); }
|
||||
}
|
||||
|
||||
/* 应用格几何(grid/list 统一入口;供 draw 与 touch 命中共用)。 */
|
||||
/* 应用格几何(grid/list 统一入口;供 draw 与 touch 命中共用)。
|
||||
* M7.4:改从 SDK 控件取当前滚动偏移(虚拟化后格/行位置随 offset 移动),并用 menu_app_region
|
||||
* 同源几何 —— 保证与 meow_lazygrid/listview 绘制、命中的坐标一致。 */
|
||||
static void menu_cell_rect(int i, int layout, int apps_top,
|
||||
int *cx, int *cy, int *cw, int *ch)
|
||||
{
|
||||
int vx, vy, vw, vh;
|
||||
menu_app_region(apps_top, layout, &vx, &vy, &vw, &vh);
|
||||
(void)vh;
|
||||
int sc = (layout == 0) ? (s_menu_grid ? meow_lazygrid_scroll(s_menu_grid) : 0)
|
||||
: (s_menu_list ? meow_listview_scroll(s_menu_list) : 0);
|
||||
if (layout == 0) { /* card grid */
|
||||
*cw = menu_grid_cell_w();
|
||||
*ch = MENU_GRID_CARD_H;
|
||||
int row = i / MENU_GRID_COLS, col = i % MENU_GRID_COLS;
|
||||
*cx = vx + col * (*cw + MENU_GRID_GAP);
|
||||
*cy = vy + row * (*ch + MENU_GRID_GAP) - sc;
|
||||
} else { /* list */
|
||||
*cw = vw;
|
||||
*ch = MENU_LIST_ROW_H;
|
||||
*cx = vx;
|
||||
*cy = vy + i * MENU_LIST_ROW_H - sc;
|
||||
}
|
||||
#if 0 /* ===== M7.4 回退位:旧手绘几何(无滚动;删 #if 0 行切回)===== */
|
||||
if (layout == 0) { /* card grid */
|
||||
int row = i / MENU_GRID_COLS, col = i % MENU_GRID_COLS;
|
||||
*cw = (fb_xres - 2 * MENU_GRID_PAD - (MENU_GRID_COLS - 1) * MENU_GRID_GAP) / MENU_GRID_COLS;
|
||||
@@ -463,13 +536,22 @@ static void menu_cell_rect(int i, int layout, int apps_top,
|
||||
*cx = 8;
|
||||
*cy = apps_top + i * MENU_LIST_ROW_H;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* 触摸命中应用格;未命中返回 -1。 */
|
||||
/* 触摸命中应用格;未命中返回 -1。
|
||||
* M7.4:手绘坐标循环替换为 SDK 控件命中(meow_lazygrid_hit / meow_listview_hit,含滚动换算 +
|
||||
* 禁用/越界不响应);入口/会话判断(state/napps 门)保留。 */
|
||||
static int menu_hit_app(int x, int y, struct app *apps, int napps, int state,
|
||||
int layout, int apps_top)
|
||||
{
|
||||
(void)apps; /* 命中判定只依赖几何;apps 保留作接口(0-warning) */
|
||||
(void)apps; /* 命中交由 SDK 控件(滚动+网格映射);apps 保留作接口(0-warning) */
|
||||
(void)apps_top; /* SDK 控件自持视口几何;无需外部 apps_top */
|
||||
if (state != 0 || napps <= 0) return -1;
|
||||
if (layout == 0) return s_menu_grid ? meow_lazygrid_hit(s_menu_grid, x, y) : -1;
|
||||
else return s_menu_list ? meow_listview_hit(s_menu_list, x, y) : -1;
|
||||
#if 0 /* ===== M7.4 回退位:旧手绘命中循环(删 #if 0 行切回)===== */
|
||||
(void)apps;
|
||||
if (state != 0 || napps <= 0) return -1;
|
||||
for (int i = 0; i < napps; i++) {
|
||||
int cx, cy, cw, ch;
|
||||
@@ -477,6 +559,7 @@ static int menu_hit_app(int x, int y, struct app *apps, int napps, int state,
|
||||
if (x >= cx && x < cx + cw && y >= cy && y < cy + ch) return i;
|
||||
}
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* 宫格卡片:surface_container 底 + 圆角 + 选中/按压态 tonal 高亮 + 图标 + 名称。 */
|
||||
@@ -498,12 +581,14 @@ static void menu_draw_grid_cell(const struct app *a, meow_img *icon, int x, int
|
||||
int nw = ui_width(nm, 18);
|
||||
ui_draw(x + (w - nw) / 2, name_y, nm, a->ok ? th->on_surface : th->error, 18);
|
||||
if (!a->ok) {
|
||||
int ew = ui_width("[缺失]", 14);
|
||||
ui_draw(x + (w - ew) / 2, name_y + meow_font_line_height(18) + 2, "[缺失]", th->error, 14);
|
||||
const char *miss = meow_str_zh("menu.missing", "[缺失]");
|
||||
int ew = ui_width(miss, 14);
|
||||
ui_draw(x + (w - ew) / 2, name_y + meow_font_line_height(18) + 2, miss, th->error, 14);
|
||||
} else if (meow_bg_is_backgrounded(a->id)) {
|
||||
/* M4:后台保活中标注(播放中转后台的 App;run_menu 1s tick 按 change_seq 刷新) */
|
||||
int bw = ui_width("·后台", 14);
|
||||
ui_draw(x + (w - bw) / 2, name_y + meow_font_line_height(18) + 2, "·后台",
|
||||
const char *bg = meow_str_zh("menu.background", "·后台");
|
||||
int bw = ui_width(bg, 14);
|
||||
ui_draw(x + (w - bw) / 2, name_y + meow_font_line_height(18) + 2, bg,
|
||||
th->on_surface_variant, 14);
|
||||
}
|
||||
}
|
||||
@@ -537,7 +622,7 @@ static void menu_draw_list_cell(const struct app *a, meow_img *icon, int x, int
|
||||
/* 副标题:package(无则 id),on_surface_variant;M4 后台保活中前缀「·后台 」 */
|
||||
char sub[48];
|
||||
snprintf(sub, sizeof sub, "%s%s",
|
||||
meow_bg_is_backgrounded(a->id) ? "·后台 " : "",
|
||||
meow_bg_is_backgrounded(a->id) ? meow_str_zh("menu.background_sp", "·后台 ") : "",
|
||||
a->package[0] ? a->package : (a->id[0] ? a->id : ""));
|
||||
if (sub[0]) {
|
||||
char subf[44];
|
||||
@@ -546,6 +631,7 @@ static void menu_draw_list_cell(const struct app *a, meow_img *icon, int x, int
|
||||
}
|
||||
}
|
||||
|
||||
#if 0 /* ===== M7.4 回退位:旧应用格分派器(新路径由 menu_sdk_*_draw 适配器经 SDK self-draw 分派)===== */
|
||||
/* 应用格绘制入口:按布局分派。 */
|
||||
static void menu_draw_app_cell(const struct app *a, meow_img *icon, int x, int y,
|
||||
int w, int h, int sel, int pressed, int layout,
|
||||
@@ -554,6 +640,145 @@ static void menu_draw_app_cell(const struct app *a, meow_img *icon, int x, int y
|
||||
if (layout == 0) menu_draw_grid_cell(a, icon, x, y, w, h, sel, pressed, th);
|
||||
else menu_draw_list_cell(a, icon, x, y, w, h, sel, pressed, th);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* ================================================================== */
|
||||
/* M7.4:主界面 SDK 控件辅助(menu_app_region 几何 / menu_grid_cell_w / 数据源 bind / SDK 自绘
|
||||
* 适配器 / meow_scroll 回调 / menu_sdk_ensure 惰性创建)。与 draw_menu 同放,故可引用其上的
|
||||
* menu_apps_top/menu_draw_grid_cell/menu_draw_list_cell/menu_name_fit 等。 */
|
||||
/* 应用区视口矩形(与 menu_cell_rect/menu_hit_app/draw_menu 共用);layout 0=宫格 1=列表。
|
||||
* 顶=apps_top,底=设置行上方;宫格视口 x=12(PAD)、宽=xres-2*PAD;列表视口 x=8、宽=xres-16。 */
|
||||
static void menu_app_region(int apps_top, int layout, int *vx, int *vy, int *vw, int *vh)
|
||||
{
|
||||
int bottom = EXIT_ROW - ROW_H - 4; /* 设置行顶(draw_menu fill_round(8, sy-4)) */
|
||||
*vy = apps_top;
|
||||
if (layout == 0) { *vx = MENU_GRID_PAD; *vw = fb_xres - 2 * MENU_GRID_PAD; }
|
||||
else { *vx = 8; *vw = fb_xres - 16; }
|
||||
*vh = bottom - apps_top;
|
||||
if (*vh < 0) *vh = 0;
|
||||
}
|
||||
/* 宫格单格宽(与旧 menu_cell_rect 同式:总宽-左右 PAD-内 gap,均分到 cols) */
|
||||
static int menu_grid_cell_w(void)
|
||||
{
|
||||
return (fb_xres - 2 * MENU_GRID_PAD - (MENU_GRID_COLS - 1) * MENU_GRID_GAP) / MENU_GRID_COLS;
|
||||
}
|
||||
|
||||
/* ---- 数据源(count 每帧实时查;bind 仅进视口时填一次 item;阅读 s_menu_ctx)---- */
|
||||
static int menu_grid_count(void *ctx) { menu_sdk_ctx_t *c = (menu_sdk_ctx_t *)ctx; return c ? c->napps : 0; }
|
||||
static void menu_grid_bind(int index, meow_lazygrid_item *item, void *ctx)
|
||||
{
|
||||
menu_sdk_ctx_t *c = (menu_sdk_ctx_t *)ctx;
|
||||
memset(item, 0, sizeof *item);
|
||||
if (!c || index < 0 || index >= c->napps) return;
|
||||
struct app *a = &c->apps[index];
|
||||
item->title = a->name;
|
||||
item->subtitle = NULL; /* 名为主文本;状态/缺失/后台标注由自绘 draw 分色绘制 */
|
||||
item->icon = (index < MAX_APPS) ? s_app_icons[index] : NULL;
|
||||
item->enabled = 1; /* 命中即启动(含缺 app → 启动失败→toast,同旧行为) */
|
||||
item->user = (void *)a;
|
||||
}
|
||||
static int menu_list_count(void *ctx) { menu_sdk_ctx_t *c = (menu_sdk_ctx_t *)ctx; return c ? c->napps : 0; }
|
||||
static void menu_list_bind(int index, meow_lv_item *item, void *ctx)
|
||||
{
|
||||
menu_sdk_ctx_t *c = (menu_sdk_ctx_t *)ctx;
|
||||
memset(item, 0, sizeof *item);
|
||||
if (!c || index < 0 || index >= c->napps) return;
|
||||
struct app *a = &c->apps[index];
|
||||
item->title = a->name;
|
||||
item->subtitle = NULL; /* 自绘 draw 显示 package/id 副标题(含「·后台」前缀) */
|
||||
item->icon = (index < MAX_APPS) ? s_app_icons[index] : NULL;
|
||||
item->icon_kind = item->icon ? MEOW_LIST_ICON_IMG : MEOW_LIST_ICON_NONE;
|
||||
item->enabled = 1;
|
||||
item->user = (void *)a;
|
||||
}
|
||||
|
||||
/* ---- SDK 自绘适配器(把 SDK 的 (idx,x,y,w,h,state) 映射到旧格/行绘制内核,迁移其内容)---- */
|
||||
static void menu_sdk_grid_draw(int index, int x, int y, int w, int h, int state, void *ctx)
|
||||
{
|
||||
menu_sdk_ctx_t *c = (menu_sdk_ctx_t *)ctx;
|
||||
const meow_ui_theme_t *th = s_ui_theme();
|
||||
if (!c || index < 0 || index >= c->napps) return;
|
||||
int sel = (state & MEOW_LAZYGRID_ST_SELECTED) ? 1 : 0;
|
||||
int pressed = (g_menu_pressed == index) ? 1 : 0;
|
||||
menu_draw_grid_cell(&c->apps[index], (index < MAX_APPS) ? s_app_icons[index] : NULL,
|
||||
x, y, w, h, sel, pressed, th);
|
||||
}
|
||||
static void menu_sdk_list_draw(int index, int x, int y, int w, int h, int state, void *ctx)
|
||||
{
|
||||
menu_sdk_ctx_t *c = (menu_sdk_ctx_t *)ctx;
|
||||
const meow_ui_theme_t *th = s_ui_theme();
|
||||
if (!c || index < 0 || index >= c->napps) return;
|
||||
int sel = (state & MEOW_LISTVIEW_ST_SELECTED) ? 1 : 0;
|
||||
int pressed = ((state & MEOW_LISTVIEW_ST_PRESSED) || g_menu_pressed == index) ? 1 : 0;
|
||||
menu_draw_list_cell(&c->apps[index], (index < MAX_APPS) ? s_app_icons[index] : NULL,
|
||||
x, y, w, h, sel, pressed, th);
|
||||
}
|
||||
|
||||
/* ---- meow_scroll 回调(列表;包 listview。宫格用 meow_lazygrid_scroll_attach 自带回调)---- */
|
||||
static void menu_lv_sc_set(void *ctx, int off) { (void)ctx; if (s_menu_list) meow_listview_set_scroll(s_menu_list, off); }
|
||||
static int menu_lv_sc_max(void *ctx) { (void)ctx; return s_menu_list ? meow_listview_max_scroll(s_menu_list) : 0; }
|
||||
static int menu_lv_sc_get(void *ctx) { (void)ctx; return s_menu_list ? meow_listview_scroll(s_menu_list) : 0; }
|
||||
/* 列表区重刷(含内容底 + listview),供 fling/滚动局部刷;不整屏。 */
|
||||
static void menu_lv_sc_redraw(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
int vx, vy, vw, vh; menu_app_region(menu_apps_top(), 1, &vx, &vy, &vw, &vh);
|
||||
if (vh <= 0) return;
|
||||
meow_ui_fill_rect(vx, vy, vw, vh, s_ui_theme()->surface);
|
||||
if (s_menu_list) meow_widget_draw(s_menu_list);
|
||||
meow_dirty_add(vx, vy, vw, vh);
|
||||
meow_ui_flush();
|
||||
}
|
||||
|
||||
/* ---- 宫格滚动物理回调(M7.4 遗留②:不用 lazygrid_scroll_attach,改自定义 meow_scroll 会话,
|
||||
* 使 redraw 先重铺背景区再画格——attach 自带的 redraw 只刷视口 meow_gfx,自定义图片背景 +
|
||||
* 超屏滚动会产生残影。set/max/get 仍指回 lazygrid,滚动语义不变。---- */
|
||||
static void menu_grid_sc_set(void *ctx, int off) { (void)ctx; if (s_menu_grid) meow_lazygrid_set_scroll(s_menu_grid, off); }
|
||||
static int menu_grid_sc_max(void *ctx) { (void)ctx; return s_menu_grid ? meow_lazygrid_max_scroll(s_menu_grid) : 0; }
|
||||
static int menu_grid_sc_get(void *ctx) { (void)ctx; return s_menu_grid ? meow_lazygrid_scroll(s_menu_grid) : 0; }
|
||||
static void menu_grid_sc_redraw(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
int vx, vy, vw, vh; menu_app_region(menu_apps_top(), 0, &vx, &vy, &vw, &vh);
|
||||
if (vh <= 0) return;
|
||||
menu_bg_paint_region(vx, vy, vw, vh); /* 重铺背景区:防图片背景残影 */
|
||||
if (s_menu_grid) meow_lazygrid_draw(s_menu_grid, meow_theme_get());
|
||||
meow_dirty_add(vx, vy, vw, vh);
|
||||
meow_ui_flush();
|
||||
}
|
||||
|
||||
/* ---- 惰性创建一次(首跑 ensure;跨多次 run_menu 复用)。几何以当前 apps_top 定 baseline。---- */
|
||||
static void menu_sdk_ensure(int apps_top)
|
||||
{
|
||||
if (s_menu_sdk_init) return;
|
||||
meow_ui_activity_push(MENU_SDK_ACT); /* 控件 owner 钉哨兵(与设置页/通知中心同款) */
|
||||
/* 宫格 */
|
||||
int gx, gy, gw, gh; menu_app_region(apps_top, 0, &gx, &gy, &gw, &gh);
|
||||
s_menu_grid = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, menu_grid_cell_w(),
|
||||
MENU_GRID_CARD_H, MENU_GRID_GAP);
|
||||
if (s_menu_grid) {
|
||||
meow_lazygrid_set_vsource(s_menu_grid, menu_grid_count, menu_grid_bind, &s_menu_ctx);
|
||||
meow_lazygrid_set_item_draw(s_menu_grid, menu_sdk_grid_draw, &s_menu_ctx);
|
||||
meow_lazygrid_set_empty_text(s_menu_grid, meow_str_zh("menu.empty_app", "没有找到应用"));
|
||||
/* M7.4 遗留②:自定义会话(redraw 重铺背景),替代 lazygrid_scroll_attach(见 menu_grid_sc_redraw 注) */
|
||||
s_menu_grid_sc = meow_scroll_create(menu_grid_sc_set, menu_grid_sc_max, menu_grid_sc_get,
|
||||
menu_grid_sc_redraw, NULL);
|
||||
}
|
||||
/* 列表 */
|
||||
int lx, ly, lw, lh; menu_app_region(apps_top, 1, &lx, &ly, &lw, &lh);
|
||||
s_menu_list = meow_listview_create(lx, ly, lw, lh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE);
|
||||
if (s_menu_list) {
|
||||
meow_listview_set_vsource(s_menu_list, menu_list_count, menu_list_bind, &s_menu_ctx);
|
||||
meow_listview_set_item_draw(s_menu_list, menu_sdk_list_draw, &s_menu_ctx);
|
||||
meow_listview_set_item_h(s_menu_list, MENU_LIST_ROW_H);
|
||||
meow_listview_set_empty_text(s_menu_list, meow_str_zh("menu.empty_app", "没有找到应用"));
|
||||
s_menu_list_sc = meow_scroll_create(menu_lv_sc_set, menu_lv_sc_max, menu_lv_sc_get,
|
||||
menu_lv_sc_redraw, NULL);
|
||||
}
|
||||
meow_ui_activity_pop();
|
||||
s_menu_sdk_init = 1;
|
||||
}
|
||||
|
||||
|
||||
static void draw_menu(struct app apps[MAX_APPS], int napps, int state,
|
||||
@@ -572,6 +797,96 @@ static void draw_menu(struct app apps[MAX_APPS], int napps, int state,
|
||||
menu_bg_paint();
|
||||
draw_header_content();
|
||||
|
||||
if (state == 1) {
|
||||
if (mfont_ready) {
|
||||
ui_draw(30, 200, meow_str_zh("menu.no_card", "没有存储卡"), th->on_surface, bsize);
|
||||
ui_draw(30, 234, meow_str_zh("menu.no_card_hint", "请插入TF卡后重开菜单"),
|
||||
th->on_surface_variant, bsize);
|
||||
} else {
|
||||
draw_text(30, 200, "NO STORAGE CARD", 2, th->on_surface);
|
||||
draw_text(30, 240, "INSERT TF CARD", 2, th->on_surface_variant);
|
||||
draw_text(30, 272, "THEN RE-OPEN MENU", 2, th->on_surface_variant);
|
||||
}
|
||||
} else if (state == 2 || napps == 0) {
|
||||
if (mfont_ready) {
|
||||
ui_draw(30, 200, meow_str_zh("menu.empty_app", "没有找到应用"), th->on_surface, bsize);
|
||||
ui_draw(30, 234, meow_str_zh("menu.empty_app_hint", "请放入应用到"),
|
||||
th->on_surface_variant, bsize);
|
||||
ui_draw(30, 268, "MeowAPPs/<id>/app.json", th->on_surface_variant, bsize);
|
||||
} else {
|
||||
draw_text(30, 200, "NO APPS FOUND", 2, th->on_surface);
|
||||
draw_text(30, 240, "PUT APP IN", 2, th->on_surface_variant);
|
||||
draw_text(30, 272, "MeowAPPs/<id>/app.json", 2, th->on_surface_variant);
|
||||
}
|
||||
} else {
|
||||
/* M7.4:应用区迁 SDK 虚拟化控件(layout=0 → meow_lazygrid;layout=1 → meow_listview)。
|
||||
* 数据源 ctx 对齐当前 apps[]/napps;控件首跑建一次,跨次复用;绘制/命中/脏区几何同源
|
||||
* (menu_app_region)。超屏滚动由 meow_scroll 接管(宫格 scroll_attach / 列表手动会话)。 */
|
||||
s_menu_ctx.apps = apps; s_menu_ctx.napps = napps;
|
||||
menu_sdk_ensure(apps_top);
|
||||
int vx, vy, vw, vh; menu_app_region(apps_top, layout, &vx, &vy, &vw, &vh);
|
||||
if (layout == 0) {
|
||||
if (s_menu_grid) {
|
||||
meow_lazygrid_set_rect(s_menu_grid, vx, vy, vw, vh);
|
||||
meow_lazygrid_set_sel(s_menu_grid, (cursor >= 0 && cursor < napps) ? cursor : -1);
|
||||
meow_lazygrid_draw(s_menu_grid, meow_theme_get());
|
||||
}
|
||||
} else {
|
||||
if (s_menu_list) {
|
||||
meow_widget_set_rect(s_menu_list, vx, vy, vw, vh);
|
||||
meow_listview_set_sel(s_menu_list, (cursor >= 0 && cursor < napps) ? cursor : -1);
|
||||
meow_widget_draw(s_menu_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* M2.36 B2b:系统设置 builtin 行(过渡入口)。位于应用项之后、Exit 行之前;
|
||||
* 音量导航可把光标移到此处(cursor==napps),触摸点击该行进入设置页。 */
|
||||
{
|
||||
int sy = EXIT_ROW - ROW_H; /* 固定在 Exit 行上方 588 */
|
||||
int selset = (cursor == napps); /* 光标已移到设置行 */
|
||||
meow_gfx_fill_round(8, sy - 4, fb_xres - 16, ROW_H - 8, 12, th->surface_container);
|
||||
if (selset) meow_gfx_fill_round_alpha(8, sy - 4, fb_xres - 16, ROW_H - 8, 12, th->primary, 40);
|
||||
if (mfont_ready) ui_draw(24, sy, meow_str_zh("menu.settings", "系统设置"), th->on_surface, bsize);
|
||||
else draw_text(24, sy, "SETTINGS", 2, th->on_surface);
|
||||
s_draw_chevron(fb_xres - 22, sy + ROW_H / 2, th->on_surface_variant);
|
||||
}
|
||||
|
||||
/* exit row */
|
||||
meow_gfx_fill_round(8, EXIT_ROW - 4, fb_xres - 16, ROW_H - 8, 12, th->surface_container);
|
||||
/* M2.2/M2.6:MeowOS 常驻主界面(播放器已死)时改「重启返回播放器」。 */
|
||||
if (mfont_ready) ui_draw(24, EXIT_ROW, meow_mode ? meow_str_zh("menu.reboot_player", "重启返回播放器")
|
||||
: meow_str_zh("menu.back_player", "返回播放器"),
|
||||
th->on_surface, bsize);
|
||||
else draw_text(24, EXIT_ROW, meow_mode ? "REBOOT TO PLAYER" : "BACK TO PLAYER", 2, th->on_surface);
|
||||
|
||||
/* M2.18 Bug 3: 底部对齐,弃用硬编码 y=700。字号 20 真实行高 meow_font_line_height(20)。
|
||||
* 非 mfont 回退分支(5x7 点阵,字高=7*scale)同样底部对齐,两分支都处理。 */
|
||||
#define FOOTY_PAD 8
|
||||
{
|
||||
char ver[32];
|
||||
snprintf(ver, sizeof ver, "MeowOS v%s dev", g_meowos_version);
|
||||
int foot_y = fb_yres - meow_font_line_height(bsize) - FOOTY_PAD;
|
||||
ui_draw(fb_xres - 8 - ui_width(ver, bsize), foot_y, ver, th->on_surface_variant, bsize);
|
||||
}
|
||||
|
||||
if (toast_ms > 0 && toast && toast[0]) {
|
||||
fill_rect(20, TOAST_Y - 6, fb_xres - 40, 40, th->surface_variant);
|
||||
ui_draw(30, TOAST_Y, toast, th->error, bsize);
|
||||
}
|
||||
|
||||
#if 0 /* ===== M7.4 回退位:旧手绘主界面全屏绘制(应用区手绘宫格/列表)。
|
||||
* 回退 = 删本 #if 0 / #endif 行 + 注释上方新 SDK 路径;run_menu 会话骨架不在此。 ===== */
|
||||
const meow_ui_theme_t *th = s_ui_theme();
|
||||
int layout = menu_layout();
|
||||
int apps_top = menu_apps_top();
|
||||
int bsize = 20;
|
||||
meow_settings_get_int("font.size.body", &bsize);
|
||||
if (bsize < 12) bsize = 12;
|
||||
if (bsize > 28) bsize = 28;
|
||||
menu_bg_prepare(th);
|
||||
menu_bg_paint();
|
||||
draw_header_content();
|
||||
if (state == 1) {
|
||||
if (mfont_ready) {
|
||||
ui_draw(30, 200, "没有存储卡", th->on_surface, bsize);
|
||||
@@ -600,27 +915,23 @@ static void draw_menu(struct app apps[MAX_APPS], int napps, int state,
|
||||
layout, th);
|
||||
}
|
||||
}
|
||||
|
||||
/* M2.36 B2b:系统设置 builtin 行(过渡入口)。位于应用项之后、Exit 行之前;
|
||||
* 音量导航可把光标移到此处(cursor==napps),触摸点击该行进入设置页。 */
|
||||
/* M2.36 B2b:系统设置 builtin 行(同上方新路径) */
|
||||
{
|
||||
int sy = EXIT_ROW - ROW_H; /* 固定在 Exit 行上方 588 */
|
||||
int selset = (cursor == napps); /* 光标已移到设置行 */
|
||||
int sy = EXIT_ROW - ROW_H;
|
||||
int selset = (cursor == napps);
|
||||
meow_gfx_fill_round(8, sy - 4, fb_xres - 16, ROW_H - 8, 12, th->surface_container);
|
||||
if (selset) meow_gfx_fill_round_alpha(8, sy - 4, fb_xres - 16, ROW_H - 8, 12, th->primary, 40);
|
||||
if (mfont_ready) ui_draw(24, sy, "系统设置", th->on_surface, bsize);
|
||||
if (mfont_ready) ui_draw(24, sy, meow_str_zh("menu.settings", "系统设置"), th->on_surface, bsize);
|
||||
else draw_text(24, sy, "SETTINGS", 2, th->on_surface);
|
||||
s_draw_chevron(fb_xres - 22, sy + ROW_H / 2, th->on_surface_variant);
|
||||
}
|
||||
|
||||
/* exit row */
|
||||
meow_gfx_fill_round(8, EXIT_ROW - 4, fb_xres - 16, ROW_H - 8, 12, th->surface_container);
|
||||
/* M2.2/M2.6:MeowOS 常驻主界面(播放器已死)时改「重启返回播放器」。 */
|
||||
if (mfont_ready) ui_draw(24, EXIT_ROW, meow_mode ? "重启返回播放器" : "返回播放器", th->on_surface, bsize);
|
||||
if (mfont_ready) ui_draw(24, EXIT_ROW, meow_mode ? meow_str_zh("menu.reboot_player", "重启返回播放器")
|
||||
: meow_str_zh("menu.back_player", "返回播放器"),
|
||||
th->on_surface, bsize);
|
||||
else draw_text(24, EXIT_ROW, meow_mode ? "REBOOT TO PLAYER" : "BACK TO PLAYER", 2, th->on_surface);
|
||||
|
||||
/* M2.18 Bug 3: 底部对齐,弃用硬编码 y=700。字号 20 真实行高 meow_font_line_height(20)。
|
||||
* 非 mfont 回退分支(5x7 点阵,字高=7*scale)同样底部对齐,两分支都处理。 */
|
||||
/* footer(底部对齐) */
|
||||
#define FOOTY_PAD 8
|
||||
{
|
||||
char ver[32];
|
||||
@@ -628,11 +939,11 @@ static void draw_menu(struct app apps[MAX_APPS], int napps, int state,
|
||||
int foot_y = fb_yres - meow_font_line_height(bsize) - FOOTY_PAD;
|
||||
ui_draw(fb_xres - 8 - ui_width(ver, bsize), foot_y, ver, th->on_surface_variant, bsize);
|
||||
}
|
||||
|
||||
if (toast_ms > 0 && toast && toast[0]) {
|
||||
fill_rect(20, TOAST_Y - 6, fb_xres - 40, 40, th->surface_variant);
|
||||
ui_draw(30, TOAST_Y, toast, th->error, bsize);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -784,6 +1095,13 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
|
||||
long meow_last_poll = 0; /* M2.2:MeowOS 后台拔营(卡/网)轮询时间戳 */
|
||||
unsigned long long apps_sig = 0; /* M2.77:应用目录签名(0=未初始化,首圈建立基线) */
|
||||
unsigned long long fonts_sig = 0; /* M2.83:字库目录签名(门控 scan_presets,防 2s 轮询 sha256 风暴) */
|
||||
#if 0 /* ===== M7.4 遗留① 补位(默认关闭):主界面宫格/列表超屏滚动喂给。
|
||||
* 作用:应用数 > 视口(max_scroll>0)时,run_menu 触摸循环内喂 meow_scroll_track(拖动跟
|
||||
* 手)+ 松手 meow_scroll_fling(惯性)。当前 MAX_APPS=8 装得下(max_scroll==0),喂给为
|
||||
* no-op(安全);未来应用 >8 时删除下述三处 #if 0 行即启用,run_menu 会话骨架逐字不变
|
||||
* (只补喂、不删骨架)。------------------------------------- */
|
||||
int sg_track_y = -1; /* 本次手势上一 y(增量 dw 用;<=8 应用从不喂) */
|
||||
#endif
|
||||
|
||||
load_config(); /* meow.conf on the card: bg_color etc. */
|
||||
menu_icons_load(apps, napps); /* MD3:应用图标解码一次进缓存(不每帧解码) */
|
||||
@@ -1289,6 +1607,19 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
|
||||
need_menu_paint = 1;
|
||||
}
|
||||
}
|
||||
#if 0 /* M7.4 遗留① 补位:拖动超屏滚动喂 meow_scroll_track(增量 dw)。≤8 应用 max_scroll==0 → no-op。
|
||||
* 启用(删本 #if 0/#endif)后:移动手指即在应用区/列表区跟手滚动,骨架其它判定不变。 */
|
||||
if (t_down && t_down_valid && t_last_y >= 0) {
|
||||
if (sg_track_y < 0) sg_track_y = t_last_y;
|
||||
int dw = t_last_y - sg_track_y;
|
||||
if (dw != 0) {
|
||||
if (menu_layout() == 0) { if (s_menu_grid_sc) meow_scroll_track(s_menu_grid_sc, dw, now_ms()); }
|
||||
else { if (s_menu_list_sc) meow_scroll_track(s_menu_list_sc, dw, now_ms()); }
|
||||
sg_track_y = t_last_y;
|
||||
need_menu_paint = 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/* ⑨ 顶部热区「跟手下拉」打开通知中心(拖动中即进,真跟手):
|
||||
* 原点锁定 + 起点在顶部热区(y<SB_BAR_H) + 下拉达 NCP_DRAG_ENTER_SLOP →
|
||||
* 立即切入 run_notif_center(此时手指仍按下拖动),由面板内拖动路径把 progress
|
||||
@@ -1372,6 +1703,12 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state,
|
||||
}
|
||||
else if (!val && t_down) {
|
||||
t_down = 0;
|
||||
#if 0 /* M7.4 遗留① 补位:松手 fling(惯性滚动)。≤8 应用 max_scroll==0 → fling 无异动(no-op)。
|
||||
* 启用(删本 #if 0/#endif)后:快速滑动松开即惯性停,跟手已由上方 track 喂给。 */
|
||||
if (s_menu_grid_sc) meow_scroll_fling(s_menu_grid_sc);
|
||||
if (s_menu_list_sc) meow_scroll_fling(s_menu_list_sc);
|
||||
sg_track_y = -1;
|
||||
#endif
|
||||
if (!t_down_valid) {
|
||||
/* 本次按下从未锁到有效坐标(坐标滞后/ghost 空按下):丢本次。 */
|
||||
logmsg("menu tap dropped: no valid origin (last %d,%d)",
|
||||
|
||||
@@ -35,6 +35,12 @@
|
||||
/* 列表编码上限(count 个 NUL 字符串时,distinct 工作缓冲上限) */
|
||||
#define MEOW_IPC_CARGO 1024u /* 守护进程入队拷贝上限(可小于 MEOW_IPC_MAX) */
|
||||
|
||||
/* ---- page-cache 治理(OOM_PAGECACHE_GOVERNANCE.md,③混合)----
|
||||
* meow_svc_perf(C:/proc/meminfo Cached 监控降载)把「是否暂停下载/预取」写入此共享状态文件
|
||||
* (一行式 key=value;App/SDK meow_cachegov 只读,系统级单点,不重复起线程)。
|
||||
* 路径可被 env MEOW_CACHEGOV_C_FILE 覆盖(宿主测试隔离)。 */
|
||||
#define MEOW_CACHEGOV_C_FILE "/tmp/meow_cachegov_c.state"
|
||||
|
||||
/* ---- 消息类型(FRAMEWORK_DESIGN §1 表)---- */
|
||||
enum {
|
||||
MSG_NONE = 0x0000,
|
||||
|
||||
Binary file not shown.
@@ -23,13 +23,14 @@ int g_notif_ctrl_died = 0;
|
||||
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 加参数。 */
|
||||
/* ⑦ 行左滑删除的绘制态(供旧手绘 ncp_draw_list / ncp_repaint_row 画该行位移)——M7.3 已迁 SDK
|
||||
* meow_listview 左滑删除(SDK 内部持有 swipe_idx/swipe_dx,不再需要本组全局)。
|
||||
* 保留在 #if 0 回退区内(随旧手绘内层一起重出)。 */
|
||||
#if 0
|
||||
static int g_ncp_swipe_row = -1;
|
||||
|
||||
static int g_ncp_swipe_off = 0;
|
||||
#endif
|
||||
|
||||
|
||||
/* ---- 阶段D附录B:通知中心顶部媒体卡片(固定于通知列表上方,活动媒体会话时显示)---- */
|
||||
@@ -59,6 +60,58 @@ static int ncp_list_view(void) { int v = NCP_LIST_BOT - ncp_list_top(); return v
|
||||
#define NCP_MEDIA_BTN_R 22 /* play 半径;prev/next 略小 */
|
||||
#define NCP_MEDIA_PROG_Y 74 /* 相对卡片顶的进度条 top */
|
||||
|
||||
/* M7.0b:媒体卡片三控制按钮改用 SDK meow_iconbtn + sb_icons 矢量图标(与 Now Playing 同源),
|
||||
* 取代原先手绘三角/竖条(ncp_media_tri/ncp_media_btn,用户反馈手绘有 bug)。up: 圆钮尺寸由
|
||||
* meow_iconbtn 的 size 决定(ink=0.6*size),此处 prev/next 用 NCP_MEDIA_BTN_R-4、play 用
|
||||
* NCP_MEDIA_BTN_R(与旧几何一致,命中仍按三圆心——ncp_media_card_tap 不变)。控件 lazy 建一次,
|
||||
* owner=哨兵 0x52(不与电源菜单 0x50 / Now Playing 0x51 冲突);绘制经 meow_widget_draw。 */
|
||||
#define NCP_MEDIA_ACT 0x52
|
||||
#define NCP_MEDIA_PREV_R (NCP_MEDIA_BTN_R - 4) /* 18 */
|
||||
#define NCP_MEDIA_NEXT_R (NCP_MEDIA_BTN_R - 4) /* 18 */
|
||||
static meow_widget *g_ncp_btn_prev = NULL, *g_ncp_btn_play = NULL;
|
||||
static meow_widget *g_ncp_btn_pause = NULL, *g_ncp_btn_next = NULL;
|
||||
static int g_ncp_media_btn_init = 0;
|
||||
|
||||
/* 创建三控制按钮(owner 钉哨兵;play/pause 同位叠两钮,按状态只画其一——同 now_playing)。 */
|
||||
static void ncp_media_btns_create(void)
|
||||
{
|
||||
meow_ui_activity_push(NCP_MEDIA_ACT);
|
||||
int by = NCP_LIST_TOP + NCP_MEDIA_BTN_Y; /* 三钮圆心 y(与 ncp_draw_media_card/ncp_media_card_tap 同源) */
|
||||
g_ncp_btn_prev = meow_iconbtn_create(NCP_MEDIA_PREV_CX - NCP_MEDIA_PREV_R, by - NCP_MEDIA_PREV_R,
|
||||
NCP_MEDIA_PREV_R * 2, MEOW_ICON_SKIP_PREV);
|
||||
g_ncp_btn_play = meow_iconbtn_create(NCP_MEDIA_PLAY_CX - NCP_MEDIA_BTN_R, by - NCP_MEDIA_BTN_R,
|
||||
NCP_MEDIA_BTN_R * 2, MEOW_ICON_PLAY_ARROW);
|
||||
g_ncp_btn_pause = meow_iconbtn_create(NCP_MEDIA_PLAY_CX - NCP_MEDIA_BTN_R, by - NCP_MEDIA_BTN_R,
|
||||
NCP_MEDIA_BTN_R * 2, MEOW_ICON_PAUSE);
|
||||
g_ncp_btn_next = meow_iconbtn_create(NCP_MEDIA_NEXT_CX - NCP_MEDIA_NEXT_R, by - NCP_MEDIA_NEXT_R,
|
||||
NCP_MEDIA_NEXT_R * 2, MEOW_ICON_SKIP_NEXT);
|
||||
/* 主播放钮保持「强调动作」观感(与 Now Playing 一致):底色=primary、图标=onPrimary;其余默认。 */
|
||||
const meow_theme *th = meow_theme_get();
|
||||
meow_widget_opts op; memset(&op, 0, sizeof op);
|
||||
op.bg = th->primary; op.fg = th->onPrimary;
|
||||
if (g_ncp_btn_play) meow_widget_set_opts(g_ncp_btn_play, &op);
|
||||
if (g_ncp_btn_pause) meow_widget_set_opts(g_ncp_btn_pause, &op);
|
||||
meow_ui_activity_pop();
|
||||
g_ncp_media_btn_init = 1;
|
||||
}
|
||||
|
||||
/* 绘制三控制按钮(prev/next 固定;play/pause 按会话状态只画其一——两钮同位,改画时新圆底盖掉旧)。
|
||||
* 每次刷新播放钮底色/图标(同 now_playing np_refresh_theme:跨主题切换跟手)。 */
|
||||
static void ncp_draw_media_btns(const meow_media_session_t *s)
|
||||
{
|
||||
if (!g_ncp_media_btn_init) ncp_media_btns_create();
|
||||
const meow_theme *th = meow_theme_get();
|
||||
meow_widget_opts op; memset(&op, 0, sizeof op);
|
||||
op.bg = th->primary; op.fg = th->onPrimary;
|
||||
if (g_ncp_btn_play) meow_widget_set_opts(g_ncp_btn_play, &op);
|
||||
if (g_ncp_btn_pause) meow_widget_set_opts(g_ncp_btn_pause, &op);
|
||||
if (g_ncp_btn_prev) { meow_widget_set_selected(g_ncp_btn_prev, 0); meow_widget_draw(g_ncp_btn_prev); }
|
||||
if (g_ncp_btn_next) { meow_widget_set_selected(g_ncp_btn_next, 0); meow_widget_draw(g_ncp_btn_next); }
|
||||
int playing = (s && s->state == MEOW_MEDIA_PLAYING);
|
||||
meow_widget *pb = playing ? g_ncp_btn_pause : g_ncp_btn_play;
|
||||
if (pb) { meow_widget_set_selected(pb, 0); meow_widget_draw(pb); }
|
||||
}
|
||||
|
||||
|
||||
/* 短串拷贝(同 meow_notif.c 语义:保证 NUL + 截断) */
|
||||
static void ncp_strncpy(char *dst, const char *src, size_t len)
|
||||
@@ -340,7 +393,8 @@ static void ncp_chip_state(int chip, int *on, char *val, int vlen)
|
||||
} else if (chip == NCP_CHIP_THEME) {
|
||||
int v = 0; meow_settings_get_enum("display.theme", &v); /* dark=0, light=1 */
|
||||
*on = (v == 1);
|
||||
if (val && vlen > 0) snprintf(val, vlen, "%s", *on ? "浅色" : "深色");
|
||||
if (val && vlen > 0) snprintf(val, vlen, "%s", *on ? meow_str_zh("ncp.theme_light", "浅色")
|
||||
: meow_str_zh("ncp.theme_dark", "深色"));
|
||||
} else if (chip == NCP_CHIP_BRIGHT) {
|
||||
int v = 0; meow_settings_get_int("display.brightness", &v);
|
||||
int vmin = 1; meow_settings_get_int("display.brightness_min", &vmin);
|
||||
@@ -406,8 +460,199 @@ static int ncp_chip_apply(int chip)
|
||||
}
|
||||
|
||||
|
||||
/* ==================================================================== */
|
||||
/* M7.3:通知中心内层迁 SDK 控件(芯片/双滑条/勿扰开关/通知列表/左滑删除) */
|
||||
/* --------------------------------------------------------------------
|
||||
* 定位:只迁「内层绘制/命中」。叠加会话骨架(SIGSTOP / 输入移交 / 恢复帧 / 顶条热区——见
|
||||
* app_launcher.c run_app_notif_overlay)绝对不动,此处仅把 run_notif_center / draw_notif_center
|
||||
* 中面板体的绘制与行命中交给 SDK meow_widget。SDK meow_ui.a 已定稿(c3d6371b),勿重编直接用。
|
||||
*
|
||||
* owner 钉哨兵 NCP_WIDGET_ACT(不冲突:0x50 电源菜单 / 0x51 锁屏·NowPlaying / 0x52 子页·媒体卡 /
|
||||
* 0x53 设置页)。绘制经 meow_widget_draw / meow_listview_vs_draw;命中:通知列表走
|
||||
* meow_listview_hit / meow_listview_swipe_begin·move·end(SDK 手势判定+删除/回弹动画),
|
||||
* 芯片/滑条/勿扰开关沿用既有人工几何命中(与媒体卡 M7.0b 一致——叠加会话为手绘循环,不跑 SDK
|
||||
* 输入线程,故不依赖 meow_widget_hit 的 owner==active 闸门)。 */
|
||||
#define NCP_WIDGET_ACT 0x54
|
||||
static meow_widget *g_ncp_lv = NULL; /* 通知列表:listview 虚拟化 + 行自绘 + 空态 + 左滑删除 */
|
||||
static meow_widget *g_ncp_sl_bri = NULL; /* 亮度滑条 */
|
||||
static meow_widget *g_ncp_sl_vol = NULL; /* 音量滑条 */
|
||||
static meow_widget *g_ncp_sw_dnd = NULL; /* 勿扰开关 */
|
||||
static meow_widget *g_ncp_chip[NCP_CHIP_COUNT]; /* 5 个快捷 chip(自定义画布:圆角 + 图标 + 标签) */
|
||||
static int g_ncp_widget_init = 0;
|
||||
static int g_ncp_need_paint = 0; /* M7.3:行左滑删除回调置位 → 主循环整面板重画 */
|
||||
|
||||
/* ---- 通知列表虚拟化源:count=当前行数(动态,逐帧实时查);bind 仅填 enabled 供
|
||||
* meow_listview_hit 判定(行内容经 set_item_draw 自绘直读 g_rows[],无池缓存过期问题)。 ---- */
|
||||
static int ncp_lv_count(void *ctx) { (void)ctx; return g_nrows; }
|
||||
static void ncp_lv_bind(int idx, meow_lv_item *it, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
memset(it, 0, sizeof *it);
|
||||
it->enabled = (idx >= 0 && idx < g_nrows) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* 文本截断(SDK meow_ui_text_width/meow_ui_text;UTF-8 安全,与旧 ncp_text 同构)。 */
|
||||
static void ncp_mt_text(int x, int y, const char *s, int size, uint32_t col, int maxw)
|
||||
{
|
||||
if (!s) s = "";
|
||||
if (meow_ui_text_width(s, size) <= maxw) { meow_ui_text(x, y, s, size, col); return; }
|
||||
int ell = meow_ui_text_width("...", size);
|
||||
int limit = maxw - ell;
|
||||
char buf[128]; int len = 0;
|
||||
while (s[len] && len < (int)sizeof buf - 8) {
|
||||
unsigned char c0 = (unsigned char)s[len];
|
||||
int cp = 1;
|
||||
if ((c0 & 0xE0) == 0xC0) cp = 2;
|
||||
else if ((c0 & 0xF0) == 0xE0) cp = 3;
|
||||
else if ((c0 & 0xF8) == 0xF0) cp = 4;
|
||||
if (len + cp >= (int)sizeof buf - 1) break;
|
||||
for (int k = 0; k < cp; k++) buf[len + k] = s[len + k];
|
||||
buf[len + cp] = 0;
|
||||
if (meow_ui_text_width(buf, size) > limit) { buf[len] = 0; break; }
|
||||
len += cp;
|
||||
}
|
||||
if (len > 0) {
|
||||
if (len + 3 < (int)sizeof buf) { buf[len] = '.'; buf[len+1] = '.'; buf[len+2] = '.'; buf[len+3] = 0; }
|
||||
meow_ui_text(x, y, buf, size, col);
|
||||
} else {
|
||||
meow_ui_text(x, y, "...", size, col);
|
||||
}
|
||||
}
|
||||
|
||||
/* 通知行自绘(set_item_draw 覆盖默认 renderer)。x 已含左滑位移(SDK 先画右露删除区并把行 x 左移),
|
||||
* 逐元素对齐旧 ncp_draw_row;颜色全取 meow_theme 语义色(M7 定案 #2 单一权威)。 */
|
||||
static void ncp_lv_row_draw(int idx, int x, int y, int w, int h, int state, void *ctx)
|
||||
{
|
||||
(void)w; (void)state; (void)ctx; /* w=SDK 视口宽;行卡宽用 NCP_ROW_W(含 NCP_ROW_X 两侧留白) */
|
||||
if (idx < 0 || idx >= g_nrows) return;
|
||||
const ncp_row_t *r = &g_rows[idx];
|
||||
const meow_theme *th = meow_theme_get();
|
||||
meow_gfx_fill_round(x + NCP_ROW_X, y, NCP_ROW_W, h - 6, 12, th->surfaceContainer);
|
||||
/* 图标 */
|
||||
int icx = x + NCP_ICON_X, icy = y + (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 = meow_ui_text_width(cbuf, th->font_body);
|
||||
meow_ui_text(icx + 16 - cw / 2, icy + 16 - meow_ui_text_height(th->font_body) / 2,
|
||||
cbuf, th->font_body, th->onPrimary);
|
||||
}
|
||||
/* app 名 + 时间 */
|
||||
meow_ui_text(x + NCP_TEXT_X, y + 10, ncp_app_name(r->app_id), th->font_body, th->onSurfaceVariant);
|
||||
char tbuf[32]; mnp_time_ago(r->created_ms, (uint64_t)time(NULL) * 1000u, tbuf, sizeof tbuf);
|
||||
meow_ui_text(x + NCP_ROW_X + NCP_ROW_W - 8 - meow_ui_text_width(tbuf, th->font_body),
|
||||
y + 12, tbuf, th->font_body, th->onSurfaceVariant);
|
||||
/* 标题 + 内容(SDK 截断) */
|
||||
ncp_mt_text(x + NCP_TEXT_X, y + 30, r->summary, th->font_body, th->onSurface, NCP_TEXT_W);
|
||||
ncp_mt_text(x + NCP_TEXT_X, y + 56, r->body, th->font_body, th->onSurfaceVariant, NCP_TEXT_W);
|
||||
/* 进度(progress>=0):细条 */
|
||||
if (r->progress >= 0) {
|
||||
int px = x + NCP_TEXT_X, pw = NCP_TEXT_W, py = y + h - 14;
|
||||
meow_ui_fill_rect(px, py, pw, 4, th->surfaceVariant);
|
||||
int fw = r->progress * pw / 100;
|
||||
if (fw > 0) meow_ui_fill_rect(px, py, fw, 4, th->primary);
|
||||
}
|
||||
}
|
||||
|
||||
/* 左滑删除完成回调(SDK 删除/回弹动画结束后触发):撤下该通知 + 重采集 + 重排。 */
|
||||
static void ncp_lv_on_delete(int idx, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
logmsg("notif-center: swipe delete row %d", idx);
|
||||
if (idx >= 0 && idx < g_nrows) meow_notif_dismiss(g_rows[idx].id);
|
||||
ncp_refresh_rows();
|
||||
/* 行数变化 → 列表重排:置位让主循环整面板重画(含媒体卡出现/消失重排 + 列表滚动重钳)。 */
|
||||
g_ncp_need_paint = 1;
|
||||
}
|
||||
|
||||
/* 芯片自绘(自定义画布 render_cb:TONAL 观感圆角 + 位图图标 + 标签,与旧 draw_notif_center chip 循环一致)。
|
||||
* launcher 侧 meow_widget 不透明 → 用 meow_widget_rect 取绝对几何(同 power_menu pm_render_cb)。 */
|
||||
static void ncp_chip_render(meow_widget *w, const meow_theme *th, void *ctx)
|
||||
{
|
||||
int c = (int)(intptr_t)ctx;
|
||||
if (c < 0 || c >= NCP_CHIP_COUNT) return;
|
||||
int rc[4]; meow_widget_rect(w, rc);
|
||||
int x = rc[0], y = rc[1], wd = rc[2], ht = rc[3];
|
||||
int on = 0; char val[16] = ""; ncp_chip_state(c, &on, val, sizeof val);
|
||||
uint32_t bg = on ? th->primary : th->surfaceContainer;
|
||||
uint32_t fg = on ? th->onPrimary : th->onSurfaceVariant;
|
||||
meow_gfx_fill_round(x, y, wd, ht, 12, bg);
|
||||
int icon_cx = x + (wd - SB_ICON_SIZE) / 2;
|
||||
int icon_cy = y + 14;
|
||||
if (c == NCP_CHIP_WIFI) ncp_sb_icon(on ? SB_I_WIFI : SB_I_WIFI_OFF, icon_cx, icon_cy, fg, 22);
|
||||
else if (c == NCP_CHIP_BT) ncp_sb_icon(on ? SB_I_BT : SB_I_BT_DIS, icon_cx, icon_cy, fg, 22);
|
||||
else if (c == NCP_CHIP_THEME) {
|
||||
if (on) ncp_dot(icon_cx + 16, icon_cy, 11, fg);
|
||||
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) {
|
||||
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); /* 顶部短柄 */
|
||||
}
|
||||
static const char *chip_lbl[NCP_CHIP_COUNT] = { "WiFi", "蓝牙", "主题", "亮度", "电源菜单" };
|
||||
static const char *chip_id[NCP_CHIP_COUNT] = { "ncp.chip_wifi", "ncp.chip_bt",
|
||||
"ncp.chip_theme", "ncp.chip_bright",
|
||||
"ncp.chip_power" };
|
||||
char label[24]; snprintf(label, sizeof label, "%s", meow_str_zh(chip_id[c], chip_lbl[c]));
|
||||
if (val[0] && c == NCP_CHIP_BRIGHT) snprintf(label, sizeof label, "%.9s", val);
|
||||
ui_draw(x + (wd - ui_width(label, 14)) / 2, y + ht - 18, label, fg, 14);
|
||||
}
|
||||
|
||||
/* 芯片自定义画布命中(矩形;未被主循环使用——命中仍走 ncp_chip_hit 人工几何,此处仅满足 W_CUSTOM 契约)。 */
|
||||
static int ncp_chip_widget_hit(meow_widget *w, int x, int y, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
int rc[4]; meow_widget_rect(w, rc);
|
||||
return (x >= rc[0] && x < rc[0] + rc[2] && y >= rc[1] && y < rc[1] + rc[3]);
|
||||
}
|
||||
|
||||
/* 创建 M7.3 内层控件集(owner 钉哨兵;仅首进创建一次,跨次复用——同媒体卡 M7.0b 惯用法)。 */
|
||||
static void ncp_widgets_create(void)
|
||||
{
|
||||
meow_ui_activity_push(NCP_WIDGET_ACT);
|
||||
/* 通知列表(视口 = 列表区;行高 NCP_ROW_H;空态 + 左滑删除) */
|
||||
int lvy = ncp_list_top(), lvh = ncp_list_view();
|
||||
g_ncp_lv = meow_listview_create(0, lvy, fb_xres, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
if (g_ncp_lv) {
|
||||
meow_listview_set_vsource(g_ncp_lv, ncp_lv_count, ncp_lv_bind, NULL);
|
||||
meow_listview_set_item_draw(g_ncp_lv, ncp_lv_row_draw, NULL);
|
||||
meow_listview_set_item_h(g_ncp_lv, NCP_ROW_H);
|
||||
meow_listview_set_empty_text(g_ncp_lv, meow_str_zh("ncp.empty", "暂无通知"));
|
||||
meow_listview_set_swipe_delete(g_ncp_lv, 1, ncp_lv_on_delete, NULL);
|
||||
}
|
||||
/* 亮度/音量双滑条(track 中心对齐旧 s_draw_slider:NCP_SL*_Y+3) */
|
||||
int bmin = 1; meow_settings_get_int("display.brightness_min", &bmin);
|
||||
int bval = 0; meow_settings_get_int("display.brightness", &bval);
|
||||
bval = mnp_brightness_clamp(bval, bmin);
|
||||
g_ncp_sl_bri = meow_slider_create(NCP_SLIDER_X, NCP_SL1_Y - 14, NCP_SLIDER_W, bmin, 100, bval);
|
||||
int vmax = vol_clamp(settings_volume_max);
|
||||
int vval = vol_clamp(settings_volume_get());
|
||||
g_ncp_sl_vol = meow_slider_create(NCP_SLIDER_X, NCP_SL2_Y - 14, NCP_SLIDER_W, 0, vmax, vval);
|
||||
/* 勿扰开关 */
|
||||
g_ncp_sw_dnd = meow_switch_create(0, 0, g_ncp_dnd ? 1 : 0);
|
||||
if (g_ncp_sw_dnd) meow_widget_set_rect(g_ncp_sw_dnd, NCP_DND_SPLIT_X - 6 - 48,
|
||||
NCP_DND_Y + NCP_DND_H / 2 - 14, 48, 28);
|
||||
/* 5 个快捷 chip(自定义画布;坐标与旧 ncp_chip_x / NCP_CHIP_Y 一致) */
|
||||
for (int c = 0; c < NCP_CHIP_COUNT; c++)
|
||||
g_ncp_chip[c] = meow_widget_custom_create(ncp_chip_x(c), NCP_CHIP_Y, NCP_CHIP_W, NCP_CHIP_H,
|
||||
ncp_chip_render, ncp_chip_widget_hit,
|
||||
(void *)(intptr_t)c);
|
||||
meow_ui_activity_pop();
|
||||
g_ncp_widget_init = 1;
|
||||
}
|
||||
|
||||
|
||||
/* ⑦ 画删除底:行右缘被空出的条(宽=-off)铺 error 红底 + 白「删除」字(条宽≥44 才显字,否则纯红渐进)。
|
||||
* off<=0(负得多=卡滑出多、露出右条多);off=0 不画。 */
|
||||
#if 0 /* M7.3 回退位:旧手绘行删除底(SDK 左滑删除已接管此绘制,见 meow_listview swipe 渲染)。 */
|
||||
static void ncp_draw_delete_bg(int ry, int off, const meow_ui_theme_t *th)
|
||||
{
|
||||
if (off >= 0) return;
|
||||
@@ -520,6 +765,35 @@ static void ncp_repaint_row(int scroll, int row, int off)
|
||||
meow_dirty_add(0, ry, fb_xres, NCP_ROW_H);
|
||||
meow_ui_flush();
|
||||
}
|
||||
#endif /* M7.3 回退位结束(上述旧手绘 ncp_draw_delete_bg/ncp_draw_row/ncp_draw_list/
|
||||
* ncp_repaint_list/ncp_repaint_row 重开本区即回退;新路径由下方 SDK 版接管)。 */
|
||||
|
||||
|
||||
/* ---- M7.3:通知列表绘制(SDK listview 版,替换上方 #if 0 的手绘版)---- */
|
||||
|
||||
/* 绘制通知列表可见区(scroll=像素偏移)。同步 listview 视口矩形(媒体卡出现/消失时列表顶缘
|
||||
* 变化)+ 滚动偏移,再画整个列表控件(虚拟化 + set_item_draw 行自绘 + 空态 + 左滑删除渲染
|
||||
* 全由 SDK 完成)。 */
|
||||
static void ncp_draw_list(int scroll)
|
||||
{
|
||||
if (!g_ncp_widget_init || !g_ncp_lv) return;
|
||||
int lt = ncp_list_top(), lv = ncp_list_view();
|
||||
meow_widget_set_rect(g_ncp_lv, 0, lt, fb_xres, lv);
|
||||
meow_listview_set_scroll(g_ncp_lv, mnp_scroll_clamp(scroll, g_nrows * NCP_ROW_H, lv));
|
||||
meow_widget_draw(g_ncp_lv);
|
||||
}
|
||||
|
||||
/* 仅重绘列表可见区(滚动专用;不整面板、不刷状态栏)。先清列表底,再画 SDK listview;
|
||||
* 只标列表带脏([NCP_LIST_TOP, NCP_LIST_BOT))——「滚动只重绘列表可视区,绝不整屏」。 */
|
||||
static void ncp_repaint_list(int scroll)
|
||||
{
|
||||
const meow_ui_theme_t *th = s_ui_theme();
|
||||
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, lt, fb_xres, lv);
|
||||
meow_ui_flush();
|
||||
}
|
||||
|
||||
|
||||
/* ⑥/⑧ 勿扰开关带命中(面板内小 toggle,⑧ 收窄为左半段):
|
||||
@@ -555,14 +829,20 @@ static void ncp_draw_dnd(const meow_ui_theme_t *th)
|
||||
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);
|
||||
const char *dnd_lbl = meow_str_zh("ncp.dnd", "勿扰");
|
||||
ui_draw(bx + 22, cy - meow_font_line_height(18) / 2 + 2, dnd_lbl, th->on_surface, 18);
|
||||
char st[16];
|
||||
snprintf(st, sizeof st, "%s", dnd_on ? "开" : "关");
|
||||
int lw = ui_width("勿扰", 18);
|
||||
snprintf(st, sizeof st, "%s", dnd_on ? meow_str_zh("common.on", "开") : meow_str_zh("common.off", "关"));
|
||||
int lw = ui_width(dnd_lbl, 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);
|
||||
/* 左半段右侧开关(⑧:从原 fb_xres-24-48 左移到分割线左侧,48×28 垂直居中) */
|
||||
s_draw_switch(NCP_DND_SPLIT_X - 6 - 48, cy - 14, dnd_on, th);
|
||||
/* 左半段右侧开关(⑧:从原 fb_xres-24-48 左移到分割线左侧,48×28 垂直居中)。
|
||||
* M7.3:迁 SDK meow_switch(绘制经 meow_widget_draw;命中仍走 ncp_dnd_hit 整带)。 */
|
||||
if (g_ncp_widget_init && g_ncp_sw_dnd) {
|
||||
meow_widget_set_rect(g_ncp_sw_dnd, NCP_DND_SPLIT_X - 6 - 48, cy - 14, 48, 28);
|
||||
meow_switch_set(g_ncp_sw_dnd, dnd_on ? 1 : 0);
|
||||
meow_widget_draw(g_ncp_sw_dnd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -576,7 +856,7 @@ static void ncp_draw_clear(const meow_ui_theme_t *th)
|
||||
if (cw <= 0) return;
|
||||
meow_gfx_fill_round(cx0, y + 4, cw, NCP_DND_H - 8, 12, th->surface_container);
|
||||
/* 垃圾桶字形(简化:顶盖横条 + 桶身圆角 + 侧提手)+「清除」文字,整体在按钮内居中(美观对称)。 */
|
||||
const char *lbl = "清除";
|
||||
const char *lbl = meow_str_zh("ncp.clear", "清除");
|
||||
int lw = ui_width(lbl, 18);
|
||||
int unit = 22; /* 图标占位宽 + 与文字的间距 */
|
||||
int total = unit + lw;
|
||||
@@ -629,38 +909,6 @@ static void ncp_media_cover_load(const meow_media_session_t *s)
|
||||
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)
|
||||
{
|
||||
@@ -678,19 +926,15 @@ static void ncp_draw_media_card(const meow_ui_theme_t *th)
|
||||
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);
|
||||
t, meow_theme_get()->onPrimary, 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);
|
||||
/* 控制按钮(SDK meow_iconbtn + sb_icons,与 Now Playing 一致;命中仍走 ncp_media_card_tap 三圆心) */
|
||||
ncp_draw_media_btns(s);
|
||||
/* 进度条 + 时间 */
|
||||
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;
|
||||
@@ -762,61 +1006,34 @@ static void draw_notif_center(int scroll, int redraw_sb)
|
||||
int grab_w = 44, grab_x = (W - grab_w) / 2;
|
||||
meow_gfx_fill_round(grab_x, NCP_GRAB_Y, grab_w, 6, 3, th->outline);
|
||||
/* 标题 + 「上滑/点空白关闭」 提示 */
|
||||
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);
|
||||
ui_draw(24, NCP_TITLE_Y, meow_str_zh("ncp.title", "通知中心"), th->on_surface, 22);
|
||||
ui_draw(W - 24 - ui_width(meow_str_zh("ncp.tap_close", "点空白关闭"), 16), NCP_TITLE_Y + 2,
|
||||
meow_str_zh("ncp.tap_close", "点空白关闭"), th->on_surface_variant, 16);
|
||||
|
||||
/* 快捷开关行(⑥: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++) {
|
||||
int cx = chip_x0 + c * (NCP_CHIP_W + NCP_CHIP_GAP);
|
||||
int on = 0; char val[16] = ""; ncp_chip_state(c, &on, val, sizeof val);
|
||||
uint32_t bg = on ? th->primary : th->surface_container;
|
||||
uint32_t fg = on ? 0xFFFFFF : th->on_surface_variant;
|
||||
meow_gfx_fill_round(cx, NCP_CHIP_Y, NCP_CHIP_W, NCP_CHIP_H, 12, bg);
|
||||
int icon_cx = cx + (NCP_CHIP_W - SB_ICON_SIZE) / 2;
|
||||
int icon_cy = NCP_CHIP_Y + 14;
|
||||
if (c == NCP_CHIP_WIFI) ncp_sb_icon(on ? SB_I_WIFI : SB_I_WIFI_OFF, icon_cx, icon_cy, fg, 22);
|
||||
else if (c == NCP_CHIP_BT) ncp_sb_icon(on ? SB_I_BT : SB_I_BT_DIS, icon_cx, icon_cy, fg, 22);
|
||||
else if (c == NCP_CHIP_THEME) {
|
||||
if (on) ncp_dot(icon_cx + 16, icon_cy, 11, fg);
|
||||
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)
|
||||
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);
|
||||
}
|
||||
/* 快捷开关行(⑥:5 个 = WiFi/蓝牙/主题/亮度/电源菜单)。M7.3:迁 SDK 自定义画布 chip
|
||||
*(TONAL 观感圆角 + 位图图标 + 标签,见 ncp_chip_render;绘制经 meow_widget_draw)。 */
|
||||
if (g_ncp_widget_init)
|
||||
for (int c = 0; c < NCP_CHIP_N; c++)
|
||||
if (g_ncp_chip[c]) meow_widget_draw(g_ncp_chip[c]);
|
||||
|
||||
/* 亮度滑条 */
|
||||
/* 亮度滑条(M7.3:迁 SDK meow_slider;track 中心 = NCP_SL1_Y+3,与旧 s_draw_slider 对齐) */
|
||||
int bmin = 1; meow_settings_get_int("display.brightness_min", &bmin);
|
||||
int bval = 0; meow_settings_get_int("display.brightness", &bval);
|
||||
bval = mnp_brightness_clamp(bval, bmin);
|
||||
ui_draw(NCP_SLIDER_X, NCP_SL1_Y - 26, "亮度", th->on_surface, 18);
|
||||
ui_draw(NCP_SLIDER_X, NCP_SL1_Y - 26, meow_str_zh("ncp.brightness", "亮度"), th->on_surface, 18);
|
||||
char vbb[16]; snprintf(vbb, sizeof vbb, "%d%%", bval);
|
||||
ui_draw(NCP_SLIDER_X + NCP_SLIDER_W - ui_width(vbb, 18), NCP_SL1_Y - 26, vbb,
|
||||
th->on_surface_variant, 18);
|
||||
s_draw_slider(NCP_SLIDER_X, NCP_SL1_Y, NCP_SLIDER_W, bval, bmin, 100, th);
|
||||
if (g_ncp_widget_init && g_ncp_sl_bri) { meow_slider_set(g_ncp_sl_bri, bval); meow_widget_draw(g_ncp_sl_bri); }
|
||||
|
||||
/* 音量滑条 */
|
||||
int vval = 0; meow_settings_get_int("audio.volume", &vval);
|
||||
vval = vol_clamp(vval);
|
||||
ui_draw(NCP_SLIDER_X, NCP_SL2_Y - 26, "音量", th->on_surface, 18);
|
||||
ui_draw(NCP_SLIDER_X, NCP_SL2_Y - 26, meow_str_zh("ncp.volume", "音量"), th->on_surface, 18);
|
||||
char vbl[16]; snprintf(vbl, sizeof vbl, "%d%%", vval);
|
||||
ui_draw(NCP_SLIDER_X + NCP_SLIDER_W - ui_width(vbl, 18), NCP_SL2_Y - 26, vbl,
|
||||
th->on_surface_variant, 18);
|
||||
s_draw_slider(NCP_SLIDER_X, NCP_SL2_Y, NCP_SLIDER_W, vval, 0, vol_clamp(100), th);
|
||||
if (g_ncp_widget_init && g_ncp_sl_vol) { meow_slider_set(g_ncp_sl_vol, vval); meow_widget_draw(g_ncp_sl_vol); }
|
||||
|
||||
/* 分隔线 */
|
||||
fill_rect(12, NCP_DIV_Y, W - 24, 1, th->outline);
|
||||
@@ -833,8 +1050,8 @@ static void draw_notif_center(int scroll, int redraw_sb)
|
||||
ncp_draw_list(scroll);
|
||||
|
||||
/* 底部提示 */
|
||||
ui_draw((W - ui_width("上滑或点空白关闭", 16)) / 2, NCP_FOOT_Y, "上滑或点空白关闭",
|
||||
th->on_surface_variant, 16);
|
||||
ui_draw((W - ui_width(meow_str_zh("ncp.swipe_close", "上滑或点空白关闭"), 16)) / 2, NCP_FOOT_Y,
|
||||
meow_str_zh("ncp.swipe_close", "上滑或点空白关闭"), th->on_surface_variant, 16);
|
||||
|
||||
/* 状态栏画在面板体之上(0-27 由 SDK 全局状态栏维护,经 draw_statusbar→sb_sdk_refresh
|
||||
* 喂数据+标脏顶条;不落面板体脏区)。仅 redraw_sb=1(打开/主题变化)才喂/标脏,交互时跳过
|
||||
@@ -968,16 +1185,10 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
|
||||
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; /* 仅重绘该行带(行左滑专用) */
|
||||
/* ⑦ 单条通知行左滑删除(M7.3:迁 SDK meow_listview_set_swipe_delete + swipe_begin/move/end)。
|
||||
* sdk_swipe = 本触摸已交给 SDK 行左滑手势(begin 命中可滑行);SDK 负责方向仲裁(横向=消费 /
|
||||
* 纵向=让位滚动)、删除/回弹动画(meow_anim_tick 推进)与 on_delete 回调。 */
|
||||
int sdk_swipe = 0;
|
||||
/* ⑨ 面板跟手下拉、半程可回:
|
||||
* panel_drag = 1 面板跟手拖动中(anim_p 直接由手指位移映射,状态时间轴暂停);
|
||||
* drag_seed_reveal = 进入拖动时锁定的面板「露出高度」(= ph + off,防触发瞬间跳变);
|
||||
@@ -1018,6 +1229,9 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
|
||||
g_ncp_dnd = d;
|
||||
}
|
||||
|
||||
/* M7.3:建内层 SDK 控件集(listview/双滑条/勿扰开关/芯片;owner 钉哨兵,仅首进一次)。 */
|
||||
ncp_widgets_create();
|
||||
|
||||
/* 首次绘制:先拍背景(应用列表)→ 渲染最终位置整面板 → 拍面板快照 → 还原背景到 p=0
|
||||
* 起点(面板隐藏、先露应用列表),随后进入循环由动画推进 p:0→FIX 滑入。 */
|
||||
ncp_refresh_rows();
|
||||
@@ -1092,9 +1306,7 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
|
||||
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;
|
||||
sdk_swipe = 0; /* 唤醒:清行左滑手势(SDK swipe 内部状态亦重置) */
|
||||
panel_drag = 0; drag_seed_reveal = 0; drag_y0 = 0; drag_paint = 0;
|
||||
/* 唤醒:面板直接就位(不重播滑入动画,面板早已打开);重拍快照防内容已变 */
|
||||
anim_p = anim_target = anim_last_target = NCP_ANIM_FIX;
|
||||
@@ -1201,42 +1413,27 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
|
||||
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; }
|
||||
}
|
||||
/* M7.3:按下原点落在通知列表行 → 交 SDK 行左滑手势(begin 命中可滑行;
|
||||
* 后续 move 由 SDK 做方向仲裁:横向=消费/纵向=让位滚动)。先同步 listview
|
||||
* 滚动偏移(meow_listview_hit 用其内部 scroll)。 */
|
||||
if (open_settled && !drag_slider && !panel_drag && g_ncp_widget_init && g_ncp_lv) {
|
||||
meow_listview_set_scroll(g_ncp_lv, list_scroll);
|
||||
sdk_swipe = meow_listview_swipe_begin(g_ncp_lv, tlx, tly);
|
||||
}
|
||||
}
|
||||
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;
|
||||
/* ⑦ 行左滑(SDK swipe-delete):已交给 SDK 手势 → 喂 move。SDK 方向仲裁:
|
||||
* 横向小位移超容差 → 消费(返回 1,行左移/删除区由 SDK 画,本触摸不滚/不拖面板);
|
||||
* 纵向主导 → 取消本行 swipe(返回 0,sdk_swipe=0),回退到下方滚动/面板拖动。 */
|
||||
if (sdk_swipe && td_ok && drag_slider == 0) {
|
||||
meow_listview_set_scroll(g_ncp_lv, list_scroll);
|
||||
if (!meow_listview_swipe_move(g_ncp_lv, tlx, tly)) sdk_swipe = 0;
|
||||
}
|
||||
/* ⑨ 面板跟手拖动(进入):竖向位移达 slop、起点不在列表溢出滚动区(归 §11)、
|
||||
* 未锁行左滑/滚动/滑条 → 进入拖动。锁定当前面板露出高度 + 当前手指 y,
|
||||
* 此后 reveal = seed + (tly - drag_y0),方向无关、连续可反向(半程可回)。 */
|
||||
if (td_ok && !drag_slider && !scroll_drag && row_swipe_row < 0 && !panel_drag) {
|
||||
if (td_ok && !drag_slider && !scroll_drag && !sdk_swipe && !panel_drag) {
|
||||
int ady2 = (tly - tdy) < 0 ? (tdy - tly) : (tly - tdy);
|
||||
if (ady2 >= S_SCROLL_SLOP) {
|
||||
int av = g_nrows * NCP_ROW_H;
|
||||
@@ -1260,7 +1457,7 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
|
||||
/* 列表滚动(§11):面板就位(open_settled)+ 起点在列表区 + 内容溢出 +
|
||||
* 纵向位移达 slop → 进滚动。方向遵循 input.swipe_invert(与设置页列表同式);
|
||||
* 拖到顶/底继续拖 → 回弹标记。动画进行中(面板在滑,未就位)不启动滚动。 */
|
||||
if (open_settled && td_ok && !scroll_drag && drag_slider == 0 && row_swipe_row < 0) {
|
||||
if (open_settled && td_ok && !scroll_drag && drag_slider == 0 && !sdk_swipe) {
|
||||
int ddy = tly - scroll_y0;
|
||||
if (ddy >= S_SCROLL_SLOP || ddy <= -S_SCROLL_SLOP) {
|
||||
int total = g_nrows * NCP_ROW_H;
|
||||
@@ -1290,10 +1487,8 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
|
||||
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;
|
||||
/* ⑦ 新触摸:清行左滑手势(防上一手势残留) */
|
||||
sdk_swipe = 0;
|
||||
/* ⑨ 新触摸:清面板跟手(防上一拖动残留) */
|
||||
panel_drag = 0; drag_seed_reveal = 0; drag_y0 = 0; drag_paint = 0;
|
||||
}
|
||||
@@ -1311,19 +1506,18 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
|
||||
if (anim_ok) anim_target = 0; else closed = 1;
|
||||
break;
|
||||
}
|
||||
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; /* 回弹复原 */
|
||||
if (sdk_swipe && g_ncp_widget_init && g_ncp_lv) {
|
||||
/* ⑦ 行左滑松手(SDK swipe-delete):消费 → 删除/回弹动画交 SDK
|
||||
* (meow_anim_tick 推进;删除完成后 ncp_lv_on_delete 置 g_ncp_need_paint)。
|
||||
* 本手势不关面板、不走 tap/上滑/点空白。未滑动(=点击某行)→ 未消费,
|
||||
* 清 sdk_swipe 后走下方 tap 分支(ncp_row_tap 命中该行 → dismiss+关面板)。 */
|
||||
meow_listview_set_scroll(g_ncp_lv, list_scroll);
|
||||
if (meow_listview_swipe_end(g_ncp_lv, tlx, tly)) {
|
||||
sdk_swipe = 0;
|
||||
td_ok = 0;
|
||||
goto ncp_release_done;
|
||||
}
|
||||
row_settle = 1; row_anim_at = now_ms();
|
||||
sdk_swipe = 0;
|
||||
} else if (scroll_drag) {
|
||||
/* 列表滚动落定:最终 clamp 到边界(拖动中已实时跟手,此处确认终值);
|
||||
* 若回弹(拖到顶/底继续拖)→ 按「上滑/下滑关闭」收起(§11.3)。
|
||||
@@ -1395,10 +1589,8 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
|
||||
if (!hit && ncp_clear_hit(tx, ty)) {
|
||||
int n = ncp_clear_all();
|
||||
logmsg("notif-center: clear %d notif(s) (dumped first, kept ongo/media/progress)", n);
|
||||
/* 清除后取消行左滑(下标会变),再重采集 + 重钳 + 整面板重画。 */
|
||||
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;
|
||||
/* 清除后取消行左滑手势(下标会变),再重采集 + 重钳 + 整面板重画。 */
|
||||
sdk_swipe = 0;
|
||||
ncp_refresh_rows();
|
||||
list_scroll = mnp_scroll_clamp(list_scroll, g_nrows * NCP_ROW_H,
|
||||
ncp_list_view());
|
||||
@@ -1430,6 +1622,8 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
|
||||
/* 其余横滑/下滑忽略(不关闭不动作) */
|
||||
}
|
||||
td_ok = 0;
|
||||
ncp_release_done:
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1453,10 +1647,8 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
|
||||
media_paint = 1; /* 已有源:进度/封面随 META 变化 → 仅重绘卡片带 */
|
||||
}
|
||||
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;
|
||||
/* ⑦ 列表已变(新条/撤下/TTL):取消当前行左滑手势,避免行下标错位。⑨ 同时取消面板跟手。 */
|
||||
sdk_swipe = 0;
|
||||
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,
|
||||
@@ -1490,41 +1682,8 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
|
||||
}
|
||||
if (anim_ok && !panel_drag && !td && 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_view());
|
||||
need_paint = 1; /* 行已移除/重排:整面板(列表区)重画 */
|
||||
}
|
||||
}
|
||||
}
|
||||
/* M7.3:行左滑落位动画已由 SDK 接管(meow_listview_swipe_end → meow_anim → swipe_anim_done
|
||||
* → on_delete(idx) → ncp_lv_on_delete 置 g_ncp_need_paint)。此处不再需要手绘行位移/回弹/滑出。 */
|
||||
|
||||
/* 重绘(面板体区域;状态栏 0-27 由 draw_statusbar 单独维护,不落入此脏区)。
|
||||
* need_sb=1(打开/主题变化)时 draw_notif_center 内另绘状态栏并标其脏——脏区仍
|
||||
@@ -1561,14 +1720,8 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
|
||||
ncp_anim_frame(anim_p, &anim_prev_bot, anim_ok);
|
||||
drag_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;
|
||||
}
|
||||
/* M7.3:行左滑跟手/回弹/滑出重绘已由 SDK 接管(swipe_anim_update 经 meow_widget_draw(lv)
|
||||
* 重绘列表 + meow_anim_set_region 标该行带脏)。此处不再有 row_paint 手绘路径。 */
|
||||
/* ~1s 状态栏时钟 */
|
||||
if (now - last_tick >= 1000) {
|
||||
last_tick = now;
|
||||
@@ -1576,12 +1729,27 @@ void run_notif_center(struct app *apps, int napps, notif_click_t *click,
|
||||
fb_norm_buf0();
|
||||
meow_ui_flush();
|
||||
}
|
||||
/* M7.3:推进 SDK 动画(行左滑删除/回弹、meow_scroll fling 等)。左滑删除完成后 on_delete
|
||||
* 置 g_ncp_need_paint → 下一帧整面板重画。swipe 动画推进时 SDK 已重绘列表(swipe_anim_update
|
||||
* 调 meow_widget_draw(lv)),此处只把列表带标脏上屏(覆盖 SDK 经 meow_gfx 标脏的列表矩形)。 */
|
||||
meow_anim_tick();
|
||||
if (g_ncp_need_paint) { g_ncp_need_paint = 0; need_paint = 1; }
|
||||
if (g_ncp_widget_init && g_ncp_lv && meow_listview_swipe_active(g_ncp_lv)) {
|
||||
meow_dirty_add(0, ncp_list_top(), fb_xres, ncp_list_view());
|
||||
meow_ui_flush();
|
||||
}
|
||||
usleep(POLL_MS * 1000);
|
||||
}
|
||||
if (tfd >= 0) close(tfd);
|
||||
kp_close(&kp_power); kp_close(&kp_keys);
|
||||
ncp_snap_free(); /* 释放动画快照(退出即回收,不驻留) */
|
||||
ncp_rows_free();
|
||||
/* 封面缓存随面板关闭回收(对齐 Now Playing np_cleanup:每次下拉重新加载当前封面)。
|
||||
* 旧码 g_ncp_media_cover_path 是文件级 static、从不重置——若首次加载失败/或 artwork 初
|
||||
* 为空的瞬间进面板,其后即便封面落盘并因路径不变而被「按文本缓存」跳过重载,卡片永久
|
||||
* 停留在「显示歌名」回退/旧封面。关面板即清缓存,保证下次下拉能拿到最新封面。 */
|
||||
if (g_ncp_media_cover) { meow_img_free(g_ncp_media_cover); g_ncp_media_cover = NULL; }
|
||||
g_ncp_media_cover_path[0] = 0;
|
||||
logmsg("notification center closed");
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ static void np_draw_page(int redraw_sb)
|
||||
const char *t = s->title[0] ? s->title : s->app_id;
|
||||
int cw = ui_width(t, 22);
|
||||
ui_draw(NP_COVER_X + (NP_COVER - cw) / 2, NP_COVER_Y + (NP_COVER - meow_font_line_height(22)) / 2,
|
||||
t, 0xFFFFFF, 22);
|
||||
t, meow_theme_get()->onPrimary, 22);
|
||||
}
|
||||
/* 元数据 */
|
||||
np_center_text(NP_TITLE_Y, s->title, 26, th->on_surface, W - 40);
|
||||
|
||||
@@ -80,8 +80,9 @@ static void draw_pm_button(int i, int selected)
|
||||
/* ==================================================================
|
||||
* M7.0b 电源菜单控件化(launcher 无 meow_ui_run,手动驱动 SDK 控件):
|
||||
* - 圆角卡 → meow_card(SDK 主题 surfaceContainerLow);
|
||||
* - 六个圆钮 → meow_iconbtn(图标在 SDK sb_icons 表:回HiBy→ARROW_BACK、取消→CLOSE)
|
||||
* 或 meow_widget_custom_create(pm_icons 专有位图:关机/重启/升级/锁定);
|
||||
* - 六个圆钮 → meow_widget_custom_create(pm_icons 专有位图:关机/重启/回HiBy/升级/锁定/取消,
|
||||
* 均 56×56 1:1 blit,无放大)。meow_iconbtn 分支保留(回HiBy/取消曾改走 sb_icons,因放大锯齿
|
||||
* 定案回退 pm_icons,见 pm_sdk_icon[] 注释;后续 SDK 出更高清图标可再启用);
|
||||
* - 命中/选中/按压态全走 SDK(meow_widget_hit/set_state/draw);
|
||||
* - 主题 = 读 SDK meow_theme(单一权威),后台遮罩仍 dim_screen(会话骨架不动)。
|
||||
* 控件 lazy 创建一次(owner=PM_SENTINEL_ACT 哨兵 activity),跨多次进入复用;
|
||||
@@ -90,14 +91,19 @@ static void draw_pm_button(int i, int selected)
|
||||
#define PM_SENTINEL_ACT 0x50 /* 电源菜单哨兵 activity(不与 launcher 其它页冲突) */
|
||||
#define PM_BTN_SIZE (PM_R * 2) /* 圆钮 = 外接正方形边长(直径) */
|
||||
|
||||
/* 哪些 pm 图标在 SDK sb_icons 表(可用 meow_iconbtn 直接出图);-1=不在 → 自定义画布 blit pm_icons */
|
||||
/* 哪些 pm 图标走 SDK sb_icons 表(可用 meow_iconbtn 直接出图);-1=不在 → 自定义画布 blit pm_icons。
|
||||
* M7.0b 曾把 2=回HiBy / 5=取消 走 meow_iconbtn(SB_ARROW_BACK / SB_CLOSE),但 meow_iconbtn 按
|
||||
* 0.6*size 把 32×32 sb_icons 位图放大到 ~60px(>位图原生墨高),最近邻放大导致明显锯齿;且此尺寸
|
||||
* 大于 M6.8 及更早电源菜单圆钮图标(pm_icons 56×56,1:1 无放大)。定案:这两钮也回退 pm_icons 专有
|
||||
* 位图(PM_ICON_BACK/PM_ICON_CANCEL,同为 Material Symbols 56×56),统一其余四钮画法,恢复旧尺寸。
|
||||
* pm_icons[] 全部专用 → meow_iconbtn 分支保留(后续 SDK 出更高清图标可再启用)。 */
|
||||
static const int pm_sdk_icon[PM_NUM] = {
|
||||
-1, /* 0 关机:pm_icons 专有(SDK 无 power 位图) */
|
||||
-1, /* 1 重启:pm_icons 专有 */
|
||||
MEOW_ICON_ARROW_BACK, /* 2 回HiBy:SB_ARROW_BACK 在表 */
|
||||
-1, /* 2 回HiBy:pm_icons 专有(恢复 M6.8 56×56,避免 meow_iconbtn 放大锯齿) */
|
||||
-1, /* 3 升级:pm_icons 专有 */
|
||||
-1, /* 4 锁定:pm_icons 专有(SDK 无 lock 位图) */
|
||||
MEOW_ICON_CLOSE, /* 5 取消:SB_CLOSE 在表 */
|
||||
-1, /* 5 取消:pm_icons 专有(同 2,恢复旧尺寸) */
|
||||
};
|
||||
|
||||
/* 圆钮文案 i18n id(meow_str)。common.cancel 内嵌已有;power.* 需 TF 覆盖表/后续并入 SDK 内嵌。 */
|
||||
@@ -573,17 +579,20 @@ int show_power_menu(void)
|
||||
* 不随主循环每 10ms 全刷(原实现每帧重画同一内容,与 LCD 刷新竞争产生频闪)。 */
|
||||
static void draw_cr_button(int sel, int i)
|
||||
{
|
||||
const meow_theme *mt = meow_theme_get(); /* M7.5:主题语义色(深/浅×MD3/MIUIx 单一权威) */
|
||||
int x0 = (i == 0) ? 80 : 280, y0 = 420, w = 120, h = 60;
|
||||
fill_round_rect(x0, y0, w, h, 12, (i == sel) ? 0x2A3A6A : 0x1A1A2A);
|
||||
uint32_t bg = (i == sel) ? mt->primary : mt->surfaceContainer;
|
||||
fill_round_rect(x0, y0, w, h, 12, bg);
|
||||
/* M7.0b i18n:是/否 经 meow_str 抽取(未知 id 回退中文) */
|
||||
const char *lbl = (i == 0) ? meow_str_zh("power.confirm_yes", "是")
|
||||
: meow_str_zh("power.confirm_no", "否");
|
||||
uint32_t fg = (i == sel) ? mt->onPrimary : mt->onSurface;
|
||||
if (mfont_ready) {
|
||||
int lw = meow_font_width_text(lbl, 20);
|
||||
meow_font_drawtext((uint16_t *)fb, x0 + w / 2 - lw / 2, y0 + 30 - meow_font_line_height(20) / 2,
|
||||
lbl, rgb565((i == sel) ? 0xFFFFFF : 0xC8D0E8), 20);
|
||||
lbl, rgb565(fg), 20);
|
||||
} else {
|
||||
draw_text(x0 + w / 2 - 6, y0 + 24, (i == 0) ? "YES" : "NO", 2, (i == sel) ? 0xFFFFFF : 0xC8D0E8);
|
||||
draw_text(x0 + w / 2 - 6, y0 + 24, (i == 0) ? "YES" : "NO", 2, fg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,9 +603,10 @@ int confirm_reboot_player(void)
|
||||
/* M2.8 Bug 3-residual:确认框同电源菜单 —— 若打开前刚经历息屏/blank,LCDC 扫描地址可能
|
||||
* 非 buffer0;强制 re-pan 让「压暗列表 + 是/否」落在被显示的缓冲。 */
|
||||
fb_pan_buf0_force();
|
||||
const meow_theme *mt0 = meow_theme_get(); /* M7.5:标题颜色走主题语义色 */
|
||||
if (mfont_ready) meow_font_drawtext((uint16_t *)fb, 20, 320,
|
||||
meow_str_zh("power.reboot_confirm", "返回播放器需重启,现在重启?"), rgb565(0xFFFFFF), 20);
|
||||
else draw_text(20, 322, "REBOOT TO RETURN TO PLAYER?", 2, 0xFFFFFF);
|
||||
meow_str_zh("power.reboot_confirm", "返回播放器需重启,现在重启?"), rgb565(mt0->onSurface), 20);
|
||||
else draw_text(20, 322, "REBOOT TO RETURN TO PLAYER?", 2, mt0->onSurface);
|
||||
|
||||
meow_touch mt;
|
||||
mt_reset(&mt);
|
||||
@@ -616,8 +626,8 @@ int confirm_reboot_player(void)
|
||||
/* 标题/按键提示:打开时画一次(原实现把它们放主循环每 10ms 全刷,与 LCD 刷新竞争 →
|
||||
* 「是/否」钮频闪;改为只在打开/状态变化时绘制)。标题在前面已画一次,这里画提示行。 */
|
||||
if (mfont_ready) meow_font_drawtext((uint16_t *)fb, 20, 560,
|
||||
meow_str_zh("power.confirm_hint", "音量切换 电源取消"), rgb565(0x666688), 20);
|
||||
else draw_text(20, 562, "VOL NAV POWER EXIT", 1, 0x666688);
|
||||
meow_str_zh("power.confirm_hint", "音量切换 电源取消"), rgb565(mt0->onSurfaceVariant), 20);
|
||||
else draw_text(20, 562, "VOL NAV POWER EXIT", 1, mt0->onSurfaceVariant);
|
||||
meow_dirty_full(); /* M2.16:压暗列表+标题+提示作为一整帧呈现 */
|
||||
for (;;) {
|
||||
/* dirty 绘制:首次全画两钮;此后仅 sel 变化才重画(消频闪)→ 标脏 + present。 */
|
||||
|
||||
@@ -126,9 +126,12 @@ static const srow_t SMENU[] = {
|
||||
/* M2.39 左缘返回手势已抽为共享函数 meow_edge_back()(meow_touch.h MEOW_EDGE_W/SWIPE 由
|
||||
* 其定义),设置页运行期调用;此处不再定义独立 S_EDGE_W/S_SWIPE_THRESH。 */
|
||||
|
||||
#if 0 /* M7.2 回退位:旧「当前选中滑条行 track y」随旧手绘 draw_settings_page 退役
|
||||
* (新路径在触摸按下时由 meow_listview hit 直接算 track 中心,无需全局记录)。 */
|
||||
/* 当前选中行若是滑条:记录其 track 纵向位置,供触摸拖动命中判(x/w 为布局常量,
|
||||
* 所有滑条行相同,可直接算;仅 y 随行变,故独立记录)。 */
|
||||
static int s_sel_track_y = -1;
|
||||
#endif
|
||||
|
||||
|
||||
/* ---- 行高/位置/导航辅助 ---- */
|
||||
@@ -206,6 +209,9 @@ static const char *const *s_row_presets(const srow_t *r, int *n)
|
||||
}
|
||||
|
||||
|
||||
#if 0 /* M7.2 回退位:旧「纯范围/选中可见滚动钳制 + 内容 y→行号」随旧手绘 draw_settings_page 退役
|
||||
* (新路径滚动走 meow_scroll 包裹 meow_listview,钳制/命中/可见性由 SDK 接管)。
|
||||
* 删除 #if 0 行并切回旧 draw_settings_page 路径即可恢复。 */
|
||||
/* 滚动:纯范围钳制到 [0, 内容高度-视口高](列表拖动滚动用;不强制选中行可见) */
|
||||
static int s_clamp_scroll_range(int scroll)
|
||||
{
|
||||
@@ -240,6 +246,7 @@ static int s_row_at_content_y(int cy)
|
||||
if (pos >= s_vis_count()) return -1;
|
||||
return s_vis_at(pos);
|
||||
}
|
||||
#endif /* M7.2 回退位:旧滚动钳制 + 内容 y→行号 */
|
||||
|
||||
|
||||
/* ---- 值格式化/展示 ---- */
|
||||
@@ -497,6 +504,9 @@ static int s_slider_val_from_x(const srow_t *r, int px)
|
||||
}
|
||||
|
||||
|
||||
#if 0 /* M7.2 回退位:旧手绘设置页主列表(手绘分组列表 + s_draw_switch/s_draw_slider + 自算滚动/命中)。
|
||||
* 新路径走 meow_listview + meow_switch + meow_scroll + meow_select_show;删除 #if 0 行并把
|
||||
* 下方「M7.2 SDK 路径」的 set_paint_page 调用切回 draw_settings_page 即可一键回退。 */
|
||||
/* ---- 绘制设置页(整页画进 SDK back_buf;调用方再 meow_dirty_add 局部 + flush)---- */
|
||||
static void draw_settings_page(int sel, int scroll_off)
|
||||
{
|
||||
@@ -596,6 +606,7 @@ static void draw_settings_page(int sel, int scroll_off)
|
||||
ui_draw(fb_xres - 8 - ui_width(ver, 20), foot_y, ver, th->outline, 20);
|
||||
}
|
||||
}
|
||||
#endif /* M7.2 回退位:旧手绘设置页主列表 */
|
||||
|
||||
|
||||
/* M2.5x 多字体:class 行下拉的「跟随全局」选项(值=空串)在 write 路径经此映射回真实存储值。 */
|
||||
@@ -658,7 +669,9 @@ static int s_dropdown(const srow_t *r)
|
||||
}
|
||||
if (n <= 0) return -1;
|
||||
if (cur < 0 || cur >= n) cur = 0;
|
||||
return s_dd_modal(r->label, item, n, cur);
|
||||
/* M7.2:枚举/预设选择器迁 SDK meow_select_show(BottomSheet+单选列表,批次2 新控件),
|
||||
* 替换自绘 s_dd_modal(后者仍供 about/网络列表/字体两级子菜单用,勿删)。 */
|
||||
return meow_select_show(r->label, item, n, cur);
|
||||
}
|
||||
|
||||
|
||||
@@ -686,7 +699,7 @@ static void dd_modal_paint(const meow_ui_theme_t *th, int W, int H, int d_x, int
|
||||
int cur_sel = (i == sel);
|
||||
if (cur_sel) meow_ui_fill_rect_alpha(d_x + 4, ry, d_w - 8, opt_row, th->primary, 36);
|
||||
ui_draw(d_x + 18, ry + (opt_row - meow_font_line_height(20)) / 2, item[i] ? item[i] : "",
|
||||
cur_sel ? 0xFFFFFF : th->on_surface, 20);
|
||||
cur_sel ? meow_theme_get()->onPrimary : th->on_surface, 20);
|
||||
if (i == cur) meow_ui_fill_rect(d_x + d_w - 28, ry + (opt_row - 12) / 2, 12, 12, th->primary);
|
||||
}
|
||||
}
|
||||
@@ -966,11 +979,228 @@ static int s_font_dropdown(const srow_t *r, char *out_val, int vlen)
|
||||
}
|
||||
|
||||
|
||||
/* ==================================================================
|
||||
* M7.2 设置页渲染层迁移(主列表迁 SDK 控件):
|
||||
* - 分组列表 → meow_listview(虚拟化 set_vsource 固定 item 池 + section 段头 + set_item_draw 自绘);
|
||||
* - 开关行 → 行内真 meow_switch(池化,SDK 绘制 + 按压态);
|
||||
* - 滑条行 → 行内嵌 meow_slider(自绘 track/thumb,保留既有 s_draw_slider 语义色;
|
||||
* 「行内嵌自绘」,M7 定案允许)——拖动实时写 settings.conf(读改写单键,
|
||||
* 经 meow_settings_set_*,勿整文件覆盖——音量存盘教训);
|
||||
* - 枚举/预设 → SDK meow_select_show(BottomSheet+单选列表,替换自绘 s_dd_modal);
|
||||
* - 信息/列表行 → 行 right 值 + chevron(SDK 主题语义色)。
|
||||
* 滚动 → meow_scroll(跟手 track + 松手 fling,同 subpage.c M7.1a 语义;刻意不 set_snap)。
|
||||
* 控件 lazy 创建一次(owner=SET_SENTINEL_ACT 哨兵),跨多次进入复用;数据/动作(s_commit、
|
||||
* s_set_int_value、s_net_*、s_font_dropdown、s_dropdown)逻辑层不动,仅换渲染层 + 命中;
|
||||
* run_settings_page 会话骨架(触摸/滚动/返回/IPC/电源键语义)不改。旧手绘 draw_settings_page
|
||||
* + s_clamp_scroll + s_row_at_content_y 保留 #if 0 作回退位(M7 红线:一键回退)。
|
||||
* ================================================================== */
|
||||
#define SET_SENTINEL_ACT 0x53 /* 设置页哨兵 activity(不与 launcher 其它页冲突) */
|
||||
#define SET_LIST_Y SET_CONTENT_Y /* 100:列表顶缘(顶栏/标题下) */
|
||||
#define SET_FOOT_H 40 /* 底部返回/版本带高 */
|
||||
#define SET_ITEM_H 68 /* MD3 行高(滑条行给 track 留位) */
|
||||
#define SET_SLIDER_YOFF 45 /* 滑条行内 track 顶缘相对行顶(÷s_draw_slider h=6,中心=顶+3) */
|
||||
#define SET_TRACK_CY 48 /* 滑条 track 中心相对行顶 y(=SET_SLIDER_YOFF+3,与绘制对齐) */
|
||||
#define SET_SLIDER_LY 12 /* 滑条行 label 顶相对行顶 y(上移给 track 留位) */
|
||||
#define SET_SWITCH_POOL 12
|
||||
|
||||
static meow_widget *g_set_lv = NULL; /* meow_listview(虚拟化) */
|
||||
static meow_scroll *g_set_sc = NULL; /* 滚动物理 */
|
||||
static meow_widget *g_set_sw[SET_SWITCH_POOL]; /* 行内开关池(真 meow_switch) */
|
||||
static int g_set_sw_n = 0, g_set_sw_use = 0; /* 池创建数 / 本轮已用(每次整绘复位) */
|
||||
static int g_set_lv_h = 0; /* 列表视口高(选中可见性 clamp 用) */
|
||||
static int g_set_init = 0;
|
||||
|
||||
static void set_lv_rect(int *x, int *y, int *w, int *h)
|
||||
{
|
||||
if (x) *x = 0;
|
||||
if (y) *y = SET_LIST_Y;
|
||||
if (w) *w = fb_xres;
|
||||
if (h) { int t = fb_yres - SET_LIST_Y - SET_FOOT_H; if (t < 0) t = 0; *h = t; }
|
||||
}
|
||||
|
||||
static int set_lv_count(void *ctx) { (void)ctx; return s_vis_count(); }
|
||||
|
||||
/* 行填充(虚拟化 pool bind;段头行 section=1、enabled=1 供 SDK 命中/段头绘制) */
|
||||
static void set_lv_bind(int idx, meow_lv_item *out, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
memset(out, 0, sizeof *out);
|
||||
out->icon_kind = MEOW_LIST_ICON_NONE;
|
||||
out->enabled = 1;
|
||||
if (idx < 0 || idx >= s_vis_count()) { out->title = ""; return; }
|
||||
const srow_t *r = &SMENU[s_vis_at(idx)];
|
||||
out->title = r->label ? r->label : "";
|
||||
out->section = (r->ctrl == SC_HEADER) ? 1 : 0;
|
||||
if (r->ctrl == SC_SWITCH) {
|
||||
int on = 0; meow_settings_get_switch(r->key, &on);
|
||||
out->right = MEOW_LIST_RIGHT_SWITCH; out->on = on;
|
||||
}
|
||||
}
|
||||
|
||||
/* 行内开关池:取下一个可用槽(每帧 set_lv_draw 顺序取用,整绘前复位 g_set_sw_use) */
|
||||
static meow_widget *set_sw_take(void)
|
||||
{
|
||||
if (g_set_sw_use < g_set_sw_n) return g_set_sw[g_set_sw_use++];
|
||||
if (g_set_sw_n >= SET_SWITCH_POOL) return NULL;
|
||||
meow_widget *w = meow_switch_create(0, 0, 0);
|
||||
if (w) { g_set_sw[g_set_sw_n++] = w; return g_set_sw[g_set_sw_use++]; }
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 整行自绘(set_item_draw 覆盖默认 renderer;x=列表左缘、y=行顶、w/h=行全宽高,
|
||||
* state=SELECTED/PRESSED/DISABLED 组合;颜色全取 meow_theme 语义色,无散落硬编码) */
|
||||
static void set_lv_draw(int idx, int x, int y, int w, int h, int state, void *ctx)
|
||||
{
|
||||
(void)ctx; (void)h;
|
||||
const meow_theme *mt = meow_theme_get();
|
||||
if (idx < 0) return;
|
||||
int si = s_vis_at(idx);
|
||||
const srow_t *r = &SMENU[si];
|
||||
int sel = (state & MEOW_LISTVIEW_ST_SELECTED);
|
||||
int press = (state & MEOW_LISTVIEW_ST_PRESSED);
|
||||
int dis = (state & MEOW_LISTVIEW_ST_DISABLED);
|
||||
int thh = meow_ui_text_height(mt->font_body);
|
||||
uint32_t textcol = sel ? mt->onPrimaryContainer : mt->onSurface;
|
||||
uint32_t subcol = sel ? mt->onPrimaryContainer : mt->onSurfaceVariant;
|
||||
|
||||
if (r->ctrl == SC_HEADER) {
|
||||
meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->surfaceContainerHigh);
|
||||
int ty = y + (SET_ITEM_H - thh) / 2;
|
||||
if (ty >= 0) meow_ui_text(x + 16, ty, r->label, mt->font_body, mt->primary);
|
||||
meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant);
|
||||
return;
|
||||
}
|
||||
if (sel) meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->primaryContainer);
|
||||
else if (press) meow_ui_fill_rect_alpha(x, y, w, SET_ITEM_H, mt->primary, 26);
|
||||
|
||||
int ly = y + (SET_ITEM_H - thh) / 2; /* label 单行垂直居中 */
|
||||
switch (r->ctrl) {
|
||||
case SC_SWITCH: {
|
||||
meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol);
|
||||
meow_widget *sw = set_sw_take();
|
||||
if (sw) {
|
||||
int on = 0; meow_settings_get_switch(r->key, &on);
|
||||
meow_widget_set_rect(sw, x + w - 16 - 56, y + (SET_ITEM_H - 32) / 2, 56, 32);
|
||||
meow_switch_set(sw, on);
|
||||
meow_widget_draw(sw);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SC_SLIDER: {
|
||||
/* label 上移一行,track 放行下方(中心=SET_SLIDER_YOFF+3=48),与拖动命中带对齐 */
|
||||
meow_ui_text(x + 16, y + SET_SLIDER_LY, r->label, mt->font_body, textcol);
|
||||
char vb[32]; s_fmt_value(r, vb, sizeof vb);
|
||||
meow_ui_text(x + w - 16 - meow_ui_text_width(vb, mt->font_body), y + SET_SLIDER_LY, vb, mt->font_body, subcol);
|
||||
int v = 0; meow_settings_get_int(r->key, &v);
|
||||
/* 息屏时间为档位索引型(秒→索引 等距画);其余 clamp 到 [min,max] */
|
||||
if (!strcmp(r->key, "display.screen_off_timeout")) v = s_time_tier_idx(v);
|
||||
else { if (v < r->min) v = r->min; if (v > r->max) v = r->max; }
|
||||
s_draw_slider(S_SLIDER_X, y + SET_SLIDER_YOFF, S_SLIDER_W, v, r->min, r->max, s_ui_theme());
|
||||
break;
|
||||
}
|
||||
case SC_INFO: {
|
||||
meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol);
|
||||
char vb[48]; s_net_get(r, vb, sizeof vb);
|
||||
meow_ui_text(x + w - 16 - meow_ui_text_width(vb, mt->font_body), ly, vb, mt->font_body, subcol);
|
||||
break;
|
||||
}
|
||||
case SC_LIST: {
|
||||
meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol);
|
||||
char vb[48]; s_net_get(r, vb, sizeof vb);
|
||||
int vx = x + w - 16 - 12 - meow_ui_text_width(vb, mt->font_body);
|
||||
meow_ui_text(vx, ly, vb, mt->font_body, subcol);
|
||||
s_draw_chevron(x + w - 16, y + SET_ITEM_H / 2, subcol);
|
||||
break;
|
||||
}
|
||||
default: { /* SC_ENUM / SC_PRESET */
|
||||
meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol);
|
||||
char vb[40]; s_display_value(r, vb, sizeof vb);
|
||||
int vx = x + w - 16 - meow_ui_text_width(vb, mt->font_body);
|
||||
meow_ui_text(vx, ly, vb, mt->font_body, subcol);
|
||||
s_draw_chevron(vx - 8, y + SET_ITEM_H / 2, subcol);
|
||||
break;
|
||||
}
|
||||
}
|
||||
meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant);
|
||||
if (dis) meow_ui_fill_rect_alpha(x, y, w, SET_ITEM_H, mt->onSurface, 90);
|
||||
}
|
||||
|
||||
/* ---- meow_scroll 回调(包 listview)---- */
|
||||
static void set_sc_set(void *ctx, int off) { (void)ctx; if (g_set_lv) meow_listview_set_scroll(g_set_lv, off); }
|
||||
static int set_sc_max(void *ctx) { (void)ctx; return g_set_lv ? meow_listview_max_scroll(g_set_lv) : 0; }
|
||||
static int set_sc_get(void *ctx) { (void)ctx; return g_set_lv ? meow_listview_scroll(g_set_lv) : 0; }
|
||||
|
||||
/* 列表区重绘(含内容底 + listview),供 fling/滚动局部刷。 */
|
||||
static void set_redraw_region(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
int lvx, lvy, lvw, lvh; set_lv_rect(&lvx, &lvy, &lvw, &lvh);
|
||||
if (lvh <= 0) return;
|
||||
const meow_ui_theme_t *th = s_ui_theme();
|
||||
meow_ui_fill_rect(lvx, lvy, lvw, lvh, th->surface_variant);
|
||||
if (g_set_lv) { g_set_sw_use = 0; meow_widget_draw(g_set_lv); }
|
||||
meow_dirty_add(lvx, lvy, lvw, lvh);
|
||||
meow_ui_flush();
|
||||
}
|
||||
|
||||
/* 选中行可见性:滚动到选中行不越界(仅靠 tap 触发,选中行即在视野内,防御性兜底)。 */
|
||||
static void set_ensure_sel_visible(int sel)
|
||||
{
|
||||
if (!g_set_lv) return;
|
||||
int p = s_vis_of(sel); if (p < 0) return;
|
||||
int scroll = meow_listview_scroll(g_set_lv);
|
||||
int top = p * SET_ITEM_H, bot = top + SET_ITEM_H;
|
||||
if (top < scroll) meow_listview_set_scroll(g_set_lv, top);
|
||||
else if (bot > scroll + g_set_lv_h) meow_listview_set_scroll(g_set_lv, bot - g_set_lv_h);
|
||||
}
|
||||
|
||||
/* 创建设置页控件集(owner=SET_SENTINEL_ACT;仅首进创建一次,跨次进入复用)。 */
|
||||
static void set_widgets_create(void)
|
||||
{
|
||||
meow_ui_activity_push(SET_SENTINEL_ACT);
|
||||
int lvx, lvy, lvw, lvh; set_lv_rect(&lvx, &lvy, &lvw, &lvh);
|
||||
g_set_lv_h = lvh;
|
||||
g_set_lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE);
|
||||
if (g_set_lv) {
|
||||
meow_listview_set_vsource(g_set_lv, set_lv_count, set_lv_bind, NULL);
|
||||
meow_listview_set_item_draw(g_set_lv, set_lv_draw, NULL);
|
||||
meow_listview_set_item_h(g_set_lv, SET_ITEM_H);
|
||||
/* 注:刻意不 set_snap —— SDK meow_scroll 的 snap 以「fling 起点」为吸附目标会往回拉 */
|
||||
}
|
||||
g_set_sc = meow_scroll_create(set_sc_set, set_sc_max, set_sc_get, set_redraw_region, NULL);
|
||||
meow_ui_activity_pop();
|
||||
g_set_init = 1;
|
||||
}
|
||||
|
||||
/* 整页绘制(进 back_buf;调用方 meow_dirty_full/meow_dirty_add + flush)。sel=当前选中(SMENU 下标)。 */
|
||||
static void set_paint_page(int sel)
|
||||
{
|
||||
const meow_ui_theme_t *th = s_ui_theme();
|
||||
int W = fb_xres, H = fb_yres;
|
||||
fill_rect(0, 0, W, H, th->surface);
|
||||
draw_statusbar();
|
||||
if (mfont_ready) ui_draw(24, 50, "系统设置", th->on_surface, 28);
|
||||
else draw_text(24, 30, "SETTINGS", 3, th->on_surface);
|
||||
fill_rect(0, 50 + (mfont_ready ? meow_font_line_height(28) : 24) + 2, W, 2, th->outline);
|
||||
fill_rect(0, SET_CARD_Y, W, H - SET_CARD_Y, th->surface_variant);
|
||||
if (g_set_lv) {
|
||||
g_set_sw_use = 0;
|
||||
int p = s_vis_of(sel); if (p >= 0) meow_listview_set_sel(g_set_lv, p);
|
||||
meow_widget_draw(g_set_lv);
|
||||
}
|
||||
/* 底部:返回(左)+ 版本(右),复用应用列表底部对齐逻辑 */
|
||||
char ver[32];
|
||||
snprintf(ver, sizeof ver, "MeowOS v%s dev", g_meowos_version);
|
||||
int foot_y = fb_yres - meow_font_line_height(20) - 8;
|
||||
s_draw_chevron_left(SET_CARD_X + 12, foot_y + meow_font_line_height(20) / 2, th->on_surface_variant);
|
||||
ui_draw(SET_CARD_X + 24, foot_y, "返回", th->on_surface_variant, 20);
|
||||
ui_draw(fb_xres - 8 - ui_width(ver, 20), foot_y, ver, th->outline, 20);
|
||||
}
|
||||
|
||||
/* ---- 运行设置页(独立子循环;run_menu 触摸命中设置行后调用)---- */
|
||||
void run_settings_page(void)
|
||||
{
|
||||
int sel = s_first_setting();
|
||||
int scroll_off = 0;
|
||||
int H = fb_yres;
|
||||
unsigned long long t_sets = now_ms(); /* M3 取证:进设置页耗时分段(真机核对用) */
|
||||
|
||||
@@ -1006,7 +1236,7 @@ void run_settings_page(void)
|
||||
int drag = -1; /* 被拖动的行索引;-1=无 */
|
||||
/* M2.37 列表拖动滚动状态 */
|
||||
int scroll_drag = 0; /* 是否处于列表拖动滚动手势 */
|
||||
int scroll_base = 0; /* 进入滚动时对应的 scroll_off 基准(连续跟手) */
|
||||
int drag_last_y = 0; /* M7.2:滚动跟手增量基准(meow_scroll_track 速度采样) */
|
||||
int scroll_y0 = 0; /* 按下原点纵向坐标 */
|
||||
int scroll_invert = 0; /* M2.38 Bug4:滑动方向切换(input.swipe_invert;0=标准方向) */
|
||||
int sel_changed = 0; /* 本轮是否改变了选中行(仅此场景才强制选中行可见) */
|
||||
@@ -1024,8 +1254,9 @@ void run_settings_page(void)
|
||||
* s_net_refresh_pump() 填充,draw 用缓存(g_net_*),数据变化即 need_paint。 */
|
||||
s_net_sync_from_sys();
|
||||
logmsg("settings entry: net toggle-sync+kick %llums", now_ms() - t_sets);
|
||||
scroll_off = s_clamp_scroll(sel, scroll_off);
|
||||
draw_settings_page(sel, scroll_off);
|
||||
if (!g_set_init) set_widgets_create(); /* M7.2:lazy 建 SDK 控件集(一次,跨次进入复用) */
|
||||
meow_ui_activity_push(SET_SENTINEL_ACT); /* 控件 owner 闸门打开(hit/按压态生效) */
|
||||
set_paint_page(sel);
|
||||
meow_dirty_full(); meow_ui_flush();
|
||||
logmsg("settings entry: page shown in %llums", now_ms() - t_sets);
|
||||
|
||||
@@ -1074,8 +1305,7 @@ void run_settings_page(void)
|
||||
touch_cooldown_until = now_ms() + 300;
|
||||
td = 0; td_ok = 0; drag = -1; scroll_drag = 0; tlx = tly = -1;
|
||||
logmsg("settings: auto screen-off -> input wake, stayed in settings page");
|
||||
scroll_off = s_clamp_scroll_range(scroll_off);
|
||||
draw_settings_page(sel, scroll_off);
|
||||
set_paint_page(sel);
|
||||
meow_dirty_full(); meow_ui_flush();
|
||||
}
|
||||
usleep(POLL_MS * 1000);
|
||||
@@ -1206,34 +1436,47 @@ void run_settings_page(void)
|
||||
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;
|
||||
scroll_y0 = tdy; /* 列表拖动基准:按下原点(触发时才锁 scroll_base) */
|
||||
/* 按下原点落在选中滑条 track 带内 → 开始拖动 */
|
||||
if (SMENU[sel].ctrl == SC_SLIDER && s_sel_track_y >= 0 &&
|
||||
tlx >= S_SLIDER_X - 12 && tlx <= S_SLIDER_X + S_SLIDER_W + 12 &&
|
||||
tly >= s_sel_track_y - 14 && tly <= s_sel_track_y + 20) {
|
||||
drag = sel;
|
||||
const srow_t *r = &SMENU[sel];
|
||||
int v = s_slider_val_from_x(r, tlx);
|
||||
s_slider_set_from_track(r, v, 0);
|
||||
need_paint = 1;
|
||||
meow_input_touch_set(1, tdx, tdy, 0); /* 喂 SDK 输入态(行按压态渲染) */
|
||||
scroll_y0 = tdy; /* 列表拖动基准:按下原点 */
|
||||
/* 按下原点落在滑条行 track 带内 → 开始拖动(meow_listview hit 判行) */
|
||||
int vp = g_set_lv ? meow_listview_hit(g_set_lv, tlx, tly) : -1;
|
||||
if (vp >= 0 && drag < 0) {
|
||||
const srow_t *rr = &SMENU[s_vis_at(vp)];
|
||||
if (rr->ctrl == SC_SLIDER) {
|
||||
int track_cy = SET_LIST_Y + vp * SET_ITEM_H -
|
||||
meow_listview_scroll(g_set_lv) + SET_TRACK_CY;
|
||||
if (tlx >= S_SLIDER_X - 12 && tlx <= S_SLIDER_X + S_SLIDER_W + 12 &&
|
||||
tly >= track_cy - 14 && tly <= track_cy + 14) {
|
||||
drag = s_vis_at(vp);
|
||||
int v = s_slider_val_from_x(rr, tlx);
|
||||
s_slider_set_from_track(rr, v, 0);
|
||||
need_paint = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* M2.37 列表拖动滚动:非滑条行按下后纵向位移超阈值 → 进入列表滚动。
|
||||
* scroll_base 在触发瞬间反推,保证进入滚动那一刻不跳变(连续跟手)。
|
||||
* M2.38 Bug4:默认标准滑动(手指上滑 → scroll_off 增大 = 内容往下翻看后面项);
|
||||
* 方向可经 input.swipe_invert 切换(1=反向:手指上滑 → 内容上移)。 */
|
||||
/* M7.2 列表拖动滚动:非滑条行按下后纵向位移超阈值 → 进入列表滚动。
|
||||
* 走 meow_scroll(跟手 track + 松手 fling),同 subpage.c M7.1a 语义;
|
||||
* 方向经 input.swipe_invert 切换(默认手指上滑 → 内容下翻)。 */
|
||||
if (td_ok && drag < 0 && !scroll_drag) {
|
||||
int ddy = tly - scroll_y0;
|
||||
if (ddy >= S_SCROLL_SLOP || ddy <= -S_SCROLL_SLOP) {
|
||||
scroll_drag = 1;
|
||||
meow_settings_get_bool("input.swipe_invert", &scroll_invert);
|
||||
scroll_base = scroll_off + (scroll_invert ? -ddy : ddy);
|
||||
meow_input_touch_set(0, 0, 0, 0); /* 滚动中不压行(清按压态) */
|
||||
drag_last_y = tly;
|
||||
}
|
||||
}
|
||||
if (scroll_drag && td_ok) {
|
||||
int dydt = tly - scroll_y0;
|
||||
scroll_off = s_clamp_scroll_range(scroll_base + (scroll_invert ? dydt : -dydt));
|
||||
need_paint = 1;
|
||||
int dydt = tly - drag_last_y; /* 本次位移增量(跟手) */
|
||||
drag_last_y = tly;
|
||||
int scdy = scroll_invert ? dydt : -dydt;
|
||||
if (g_set_sc && scdy) meow_scroll_track(g_set_sc, scdy, now);
|
||||
/* 列表滚动不在此即时重绘:整批事件末尾由下方「if (scroll_drag && td_ok)
|
||||
* set_redraw_region」合并为一次视口局部重绘。此处若 set_redraw_region 且再设
|
||||
* need_paint=1,底部 set_paint_page 会再走一遍整页重绘 + dirty_add(0,42,...)
|
||||
* 近整屏 flush —— 快滑每帧整页重绘 → 卡顿,违背「局部脏区不整屏重绘」铁律;
|
||||
* 且一个 poll 多个移动事件各自 flush 会多次 FBIO_WAITFORVSYNC 阻塞放大延迟。 */
|
||||
}
|
||||
if (drag >= 0 && td_ok) {
|
||||
const srow_t *r = &SMENU[drag];
|
||||
@@ -1250,6 +1493,7 @@ void run_settings_page(void)
|
||||
}
|
||||
} else if (!val && td) {
|
||||
td = 0;
|
||||
meow_input_touch_set(0, 0, 0, 0); /* 抬手:清 SDK 按压态 */
|
||||
if (drag >= 0) { /* 滑条拖动结束 → 落盘 */
|
||||
const srow_t *r = &SMENU[drag];
|
||||
s_commit(r, 1);
|
||||
@@ -1257,7 +1501,8 @@ void run_settings_page(void)
|
||||
need_paint = 1;
|
||||
} else if (scroll_drag) {
|
||||
scroll_drag = 0; /* 列表滚动结束(已实时重绘),不再当 tap */
|
||||
scroll_base = 0; scroll_y0 = 0;
|
||||
if (g_set_sc) meow_scroll_fling(g_set_sc); /* 松手 fling(惯性 ease-out) */
|
||||
scroll_y0 = 0;
|
||||
need_paint = 1;
|
||||
} else if (td_ok) {
|
||||
int dx = tlx - tdx, dy = tly - tdy;
|
||||
@@ -1276,8 +1521,10 @@ void run_settings_page(void)
|
||||
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
|
||||
goto done;
|
||||
}
|
||||
int cy = ty - SET_CONTENT_Y + scroll_off;
|
||||
int idx = s_row_at_content_y(cy);
|
||||
/* M7.2:行命中走 meow_listview(含滚动换算 + 禁用/越界行不响应);
|
||||
* 段头行 enabled=1 故 hit 亦命中 → 下方按 ctrl==SC_HEADER 分流。 */
|
||||
int vp = g_set_lv ? meow_listview_hit(g_set_lv, tx, ty) : -1;
|
||||
int idx = (vp >= 0) ? s_vis_at(vp) : -1;
|
||||
if (idx >= 0 && !s_setting_idx(idx)) {
|
||||
/* 点分组标题行 → 仅选中下一设置行 */
|
||||
int ns = s_next_setting(idx);
|
||||
@@ -1306,7 +1553,7 @@ void run_settings_page(void)
|
||||
while (read(tfd, e, sizeof e) == 16) {}
|
||||
}
|
||||
td = 0; td_ok = 0; drag = -1; scroll_drag = 0;
|
||||
scroll_base = 0; scroll_y0 = 0;
|
||||
scroll_y0 = 0;
|
||||
tdx = tdy = tlx = tly = -1;
|
||||
touch_cooldown_until = now_ms() + 300;
|
||||
memset(&kd_pwr, 0, sizeof kd_pwr);
|
||||
@@ -1350,7 +1597,7 @@ void run_settings_page(void)
|
||||
while (read(tfd, e, sizeof e) == 16) {}
|
||||
}
|
||||
td = 0; td_ok = 0; drag = -1; scroll_drag = 0;
|
||||
scroll_base = 0; scroll_y0 = 0;
|
||||
scroll_y0 = 0;
|
||||
tdx = tdy = tlx = tly = -1;
|
||||
touch_cooldown_until = now_ms() + 300;
|
||||
/* 重置设置页按键防抖基线,防残边 */
|
||||
@@ -1376,14 +1623,18 @@ void run_settings_page(void)
|
||||
}
|
||||
}
|
||||
}
|
||||
/* 列表拖动滚动:本批连续触摸事件合并为一次视口局部重绘(只刷列表区,
|
||||
* 最多一次 present),不再触发 need_paint 整页重绘。scroll_drag/td_ok 在
|
||||
* 抬手处置 0(抬手那轮已走 meow_scroll_fling + need_paint 整页),故此处只兜活动滚动。 */
|
||||
if (scroll_drag && td_ok) set_redraw_region(NULL);
|
||||
}
|
||||
|
||||
/* ---- 滚动 + 重绘 ---- */
|
||||
if (need_paint) {
|
||||
last_act = now;
|
||||
/* 仅选中行变化时强制其可见;列表拖动滚动时保留用户滚到的位置,不把选中拉回视野 */
|
||||
if (sel_changed) { scroll_off = s_clamp_scroll(sel, scroll_off); sel_changed = 0; }
|
||||
draw_settings_page(sel, scroll_off);
|
||||
if (sel_changed) { set_ensure_sel_visible(sel); sel_changed = 0; }
|
||||
set_paint_page(sel);
|
||||
/* 局部标脏:标题+卡片+滚动条+底部(y=42 以下全部,覆盖主题/选中/值变化),
|
||||
* 状态栏(0-28)由 draw_statusbar/draw_clock 各自维护,不在此列。
|
||||
* 选择器关闭后(after_modal)其全屏 dim 把状态栏也压暗了,须整帧重绘还原。 */
|
||||
@@ -1408,9 +1659,13 @@ void run_settings_page(void)
|
||||
fb_norm_buf0();
|
||||
meow_ui_flush();
|
||||
}
|
||||
/* M7.2: 推进 fling/回弹动画(meow_scroll 惯性停;无动画零开销) */
|
||||
meow_anim_tick();
|
||||
usleep(POLL_MS * 1000);
|
||||
}
|
||||
done:
|
||||
meow_input_touch_set(0, 0, 0, 0); /* M7.2 防残留按压态 */
|
||||
meow_ui_activity_pop(); /* 关 owner 闸门(控件 owner 仍钉哨兵,复用) */
|
||||
if (tfd >= 0) close(tfd);
|
||||
kp_close(&kp_power); kp_close(&kp_keys);
|
||||
logmsg("settings page closed");
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* subpage.c — MeowOS launcher 独立子页面(WiFi/蓝牙)统一脚手架(M2.76)。
|
||||
/* subpage.c — MeowOS launcher 独立子页面(WiFi/蓝牙/锁屏 PIN)统一脚手架(M2.76)。
|
||||
*
|
||||
* 安卓风子页骨架:顶栏(标题 + 返回 + 右上开关)+ 分组列表(组标题/行/开关/动作)
|
||||
* + 滚动(跟手,方向沿用设置页 input.swipe_invert 语义)+ 左缘左滑返回 + 底部返回。
|
||||
* + 滚动 + 左缘左滑返回。
|
||||
*
|
||||
* 由页面(wifi_page.c / bt_page.c)通过 sub_page_t 提供:
|
||||
* 由页面(wifi_page.c / bt_page.c / lock_settings.c)通过 sub_page_t 提供:
|
||||
* - title 顶栏标题
|
||||
* - top_sw 顶栏开关(0=不显示;1=显示并回写当前值)
|
||||
* - top_sw_set 开关切换(写设置 + 生效 + 触发重扫)
|
||||
@@ -12,7 +12,18 @@
|
||||
* - detail 点普通行(如当前连接详情、网络/设备详情)
|
||||
* - live 1=驻留时周期性(约2s)重扫行(蓝牙附近扫描等动态数据)
|
||||
*
|
||||
* 与设置页同主题(s_ui_theme,MD3 语义色,明暗自动);局部刷新不整屏重绘;
|
||||
* ================================================================
|
||||
* M7.1a 渲染层迁移:脚手架手绘(顶栏 + 分组列表 + 跟手滚动 + 手绘 switch/chevron)
|
||||
* 改走 SDK 控件(单一 meow_theme 权威):
|
||||
* - 顶栏 → meow_topappbar(标题 + 返回箭头 + 底部分隔线);
|
||||
* - 顶部开关 → meow_switch(叠顶栏右缘,保留开/关视觉与命中);
|
||||
* - 分组列表 → meow_listview(virtualized set_vsource 固定 item 池 + section 段头
|
||||
* + right=SWITCH/ARROW 尾件 + 选中/按压态);
|
||||
* - 滚动 → meow_scroll(跟手 track 采样松手 fling + snap 到行边界)。
|
||||
* wifi/bt/lock_settings 的「数据 + 动作」逻辑(netmgr IPC / bluetoothctl / 设置契约)
|
||||
* 全部保留在 launcher 逻辑层,仅换渲染。旧手绘路径整体保留 #if 0 作回退位(M7 红线)。
|
||||
*
|
||||
* 与设置页同主题(s_ui_theme → SDK meow_theme 单一权威,MD3/MIUIx×明暗自动);
|
||||
* 模态弹窗交给 launcher 既有 s_dd_modal(点外关闭不穿透),本脚手架在回调返回后
|
||||
* 彻底排空本页 tfd + 重置触摸基线(复刻 run_settings_page 后置排空,防穿透)。
|
||||
*
|
||||
@@ -22,16 +33,28 @@
|
||||
#include "meow_launcher_internal.h"
|
||||
|
||||
|
||||
/* ---- 布局常量(顶栏/内容区;全屏内容区延用设置页 surface_variant 整屏底)---- */
|
||||
/* ---- 布局常量(顶栏/内容区;全屏内容区延用设置页 surface 整屏底)----
|
||||
* note:SUB_CONT_TOP 与旧版一致(96),顶栏底缘分隔线由 SDK topappbar 绘制(y=95)。 */
|
||||
#define SUB_TB_Y SB_BAR_H /* 28:顶栏顶缘(状态栏下) */
|
||||
#define SUB_TB_H 68 /* 顶栏高 28..96 */
|
||||
#define SUB_TB_BOT (SUB_TB_Y + SUB_TB_H) /* 96:顶栏底缘 / 分隔线 */
|
||||
#define SUB_CONT_TOP (SUB_TB_BOT + 6) /* 102:内容区顶缘 */
|
||||
#define SUB_TB_BOT (SUB_TB_Y + SUB_TB_H) /* 96:顶栏底缘 */
|
||||
#define SUB_CONT_TOP (SUB_TB_BOT) /* 96:内容区顶缘(topappbar 分隔线下) */
|
||||
#define SUB_FOOT_H 44 /* 底部版本信息带(右对齐) */
|
||||
#define SUB_ITEM_H 56 /* MD3 行高(双行富 item:主/副标题) */
|
||||
#define SUB_PAD_X 16 /* 水平内边距(顶开关右缘留白用) */
|
||||
#define SUB_SW_H 32 /* 顶开关高(meow_switch 固定 56×32) */
|
||||
|
||||
|
||||
#if 0 /* ================================================================
|
||||
* M7.1a 退役:子页旧手绘脚手架(顶栏 + 分组列表 + 跟手无惯性滚动 + 手绘
|
||||
* s_draw_switch/s_draw_chevron + 自算行前缀和 + 自算命中)。改走 SDK 控件
|
||||
* (meow_topappbar + meow_listview + meow_scroll),命中/布局/状态/滚动/主题
|
||||
* 全由 SDK 接管。本块保留作回退位(删除 #if 0 行并删下方「M7.1a SDK 路径」段
|
||||
* 即可恢复旧手绘)。以下全部内容与迁移前 subpage.c 逐字一致。
|
||||
* ================================================================ */
|
||||
#define SUB_ROW_H 58 /* 行高(对齐 SROW_H,便于触摸) */
|
||||
#define SUB_HDR_H 40 /* 组标题行高 */
|
||||
#define SUB_PAD_X 16 /* 水平内边距 */
|
||||
#define SUB_SW_W 48 /* 开关宽(s_draw_switch) */
|
||||
#define SUB_SW_H 28
|
||||
#define SUB_BTN_X 8 /* 底部返回按钮左缘 */
|
||||
#define SUB_BTN_W 120 /* 底部返回按钮宽 */
|
||||
|
||||
@@ -502,3 +525,510 @@ subdone:
|
||||
logmsg("subpage '%s' closed", p->title ? p->title : "");
|
||||
return 0;
|
||||
}
|
||||
#endif /* M7.1a 退役:旧手绘子页脚手架回退位 */
|
||||
|
||||
|
||||
/* ==================================================================
|
||||
* M7.1a 子页渲染层迁移(SDK 控件化):
|
||||
* - 顶栏 → meow_topappbar(标题 + 返回箭头 + 底部分隔线);
|
||||
* - 顶部开关 → meow_switch(叠顶栏右缘,保留开/关视觉 + 命中);
|
||||
* - 分组列表 → meow_listview(virtualized set_vsource 固定 item 池 + section 段头
|
||||
* + right=SWITCH/ARROW 尾件 + 选中/按压态,SDK 语义色);
|
||||
* - 滚动 → meow_scroll(跟手 meow_scroll_track 采样 + 松手 fling ease-out 惯性停;
|
||||
* 刻意不 set_snap——SDK snap 以 fling 起点为吸附目标会往回拉,见 sp_widgets_create 注)。
|
||||
* 控件 lazy 创建一次(owner=SUB_SENTINEL_ACT 哨兵),跨多次进入复用;meow_subpage_run()
|
||||
* 进入 push 哨兵、退出 pop(owner 闸门使控件命中生效)。数据/动作回调(rows/act/detail/
|
||||
* top_sw_*)仍由各子页提供,逻辑层不动,仅渲染层替换。
|
||||
* ================================================================== */
|
||||
#define SUB_SENTINEL_ACT 0x52 /* 子页哨兵 activity(不与 launcher 其它页冲突) */
|
||||
|
||||
static sub_page_t *g_sp; /* 当前运行子页(rows/act/detail/top_sw 回调入口) */
|
||||
static sub_row_t g_rows[SUB_ROWS_MAX]; /* 行缓冲:rows() 一次性填满,listview bind 只读 */
|
||||
static int g_nrows = 0; /* 当前行数(listview vsource count 实时读) */
|
||||
static int g_top_sw_en = 0; /* 本页是否显示顶开关 */
|
||||
static int g_top_on = 0; /* 顶开关当前值 */
|
||||
|
||||
static meow_widget *g_tb = NULL; /* topappbar */
|
||||
static meow_widget *g_sw = NULL; /* 顶开关(仅在有顶开关的页创建/使用) */
|
||||
static meow_widget *g_lv = NULL; /* listview */
|
||||
static meow_scroll *g_sc = NULL; /* 滚动物理 */
|
||||
static int g_sp_init = 0;
|
||||
|
||||
/* 列表视口矩形(内容区,去掉底部版本带) */
|
||||
static void sp_lv_rect(int *x, int *y, int *w, int *h)
|
||||
{
|
||||
if (x) *x = 0;
|
||||
if (y) *y = SUB_CONT_TOP;
|
||||
if (w) *w = fb_xres;
|
||||
if (h) { *h = fb_yres - SUB_CONT_TOP - SUB_FOOT_H; if (*h < 0) *h = 0; }
|
||||
}
|
||||
|
||||
/* ---- 行缓冲 → meow_lv_item(虚拟化 bind;每行轻量填充,不做 IO §5.5)---- */
|
||||
static int sp_lv_count(void *ctx) { (void)ctx; return g_nrows; }
|
||||
static void sp_lv_bind(int idx, meow_lv_item *out, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
memset(out, 0, sizeof *out);
|
||||
out->icon_kind = MEOW_LIST_ICON_NONE;
|
||||
out->enabled = 1;
|
||||
if (idx < 0 || idx >= g_nrows) { out->title = ""; return; }
|
||||
const sub_row_t *r = &g_rows[idx];
|
||||
switch (r->kind) {
|
||||
case SUB_HDR: /* 段头占位行(非粘性):default renderer 特殊绘制 */
|
||||
out->section = 1; out->title = r->label ? r->label : "";
|
||||
out->right = MEOW_LIST_RIGHT_NONE; out->enabled = 0; break;
|
||||
case SUB_ROW: /* 普通信息行 */
|
||||
out->title = r->label ? r->label : "";
|
||||
out->subtitle = r->sub; out->right = MEOW_LIST_RIGHT_NONE; break;
|
||||
case SUB_SW: /* 开关行:右尾件 SWITCH */
|
||||
out->title = r->label ? r->label : "";
|
||||
out->subtitle = r->sub; out->right = MEOW_LIST_RIGHT_SWITCH; out->on = r->on; break;
|
||||
case SUB_ACT: /* 动作行:右尾件 ARROW */
|
||||
out->title = r->label ? r->label : "";
|
||||
out->subtitle = r->sub; out->right = MEOW_LIST_RIGHT_ARROW; break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- meow_scroll 回调(包 listview)---- */
|
||||
static void sp_sc_set(void *ctx, int off) { (void)ctx; if (g_lv) meow_listview_set_scroll(g_lv, off); }
|
||||
static int sp_sc_max(void *ctx) { (void)ctx; return g_lv ? meow_listview_max_scroll(g_lv) : 0; }
|
||||
static int sp_sc_get(void *ctx) { (void)ctx; return g_lv ? meow_listview_scroll(g_lv) : 0; }
|
||||
|
||||
/* 列表区重绘(含背景 + listview),供 fling 每帧/滚动局部刷。ctx 为 meow_scroll 回调用。 */
|
||||
static void sp_redraw_region(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
int lvx, lvy, lvw, lvh;
|
||||
sp_lv_rect(&lvx, &lvy, &lvw, &lvh);
|
||||
if (lvh <= 0) return;
|
||||
const meow_theme *mt = meow_theme_get();
|
||||
meow_ui_fill_rect(lvx, lvy, lvw, lvh, mt->surface); /* 内容底 surface */
|
||||
if (g_lv) meow_widget_draw(g_lv);
|
||||
meow_dirty_add(lvx, lvy, lvw, lvh);
|
||||
meow_ui_flush();
|
||||
}
|
||||
|
||||
/* 创建子页控件集(owner=SUB_SENTINEL_ACT;仅首进创建一次,跨页复用)。 */
|
||||
static void sp_widgets_create(void)
|
||||
{
|
||||
meow_ui_activity_push(SUB_SENTINEL_ACT);
|
||||
g_tb = meow_topappbar_create(0, SUB_TB_Y, fb_xres, SUB_TB_H, "");
|
||||
if (g_tb) meow_topappbar_set_back(g_tb, 1);
|
||||
int lvx, lvy, lvw, lvh;
|
||||
sp_lv_rect(&lvx, &lvy, &lvw, &lvh);
|
||||
g_lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
if (g_lv) {
|
||||
meow_listview_set_vsource(g_lv, sp_lv_count, sp_lv_bind, NULL);
|
||||
/* 双行富 item + 无左图标 + 右尾件(SWITCH/ARROW);行高 56 */
|
||||
meow_listview_set_item_renderer(g_lv, 2, 0, 1);
|
||||
meow_listview_set_item_h(g_lv, SUB_ITEM_H);
|
||||
}
|
||||
/* 注:刻意不 set_snap —— SDK meow_scroll 的 snap 以「fling 起点」为吸附目标,快速滑动时会
|
||||
* 从自然停点往回拉一整行(实测 697→336),UX 不佳;默认 ease-out 惯性停(无 snap)更顺滑。
|
||||
* 若后续 SDK 修正 snap 到「自然停点」,可恢复 meow_scroll_set_snap(g_sc, SUB_ITEM_H)。 */
|
||||
g_sc = meow_scroll_create(sp_sc_set, sp_sc_max, sp_sc_get, sp_redraw_region, NULL);
|
||||
meow_ui_activity_pop();
|
||||
g_sp_init = 1;
|
||||
}
|
||||
|
||||
/* 逐页配置:topappbar 标题 + 顶开关(按需创建/置态)+ listview 行数。 */
|
||||
static void sp_configure_page(const sub_page_t *p, int top_sw_en, int top_on)
|
||||
{
|
||||
if (p->title && g_tb) meow_topappbar_set_title(g_tb, p->title);
|
||||
g_top_sw_en = top_sw_en;
|
||||
g_top_on = top_on;
|
||||
if (top_sw_en) {
|
||||
if (!g_sw) {
|
||||
meow_ui_activity_push(SUB_SENTINEL_ACT); /* owner 钉哨兵(命中闸门) */
|
||||
int swx = fb_xres - SUB_PAD_X - 56; /* meow_switch 固定 56×32 */
|
||||
int swy = SUB_TB_Y + (SUB_TB_H - SUB_SW_H) / 2;
|
||||
g_sw = meow_switch_create(swx, swy, top_on);
|
||||
meow_ui_activity_pop();
|
||||
} else if (g_sw) {
|
||||
meow_switch_set(g_sw, top_on);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 整页绘制(进 back_buf;调用方 meow_dirty_full + flush 上屏)。 */
|
||||
static void sp_paint_page(void)
|
||||
{
|
||||
const meow_theme *mt = meow_theme_get();
|
||||
int W = fb_xres, H = fb_yres;
|
||||
meow_ui_fill_rect(0, 0, W, H, mt->surface); /* 全屏底 surface */
|
||||
draw_statusbar();
|
||||
if (g_tb) meow_widget_draw(g_tb); /* topappbar(含底部分隔线) */
|
||||
if (g_sw && g_top_sw_en) meow_widget_draw(g_sw); /* 顶开关(叠顶栏右缘) */
|
||||
int lvx, lvy, lvw, lvh;
|
||||
sp_lv_rect(&lvx, &lvy, &lvw, &lvh);
|
||||
meow_ui_fill_rect(lvx, lvy, lvw, lvh, mt->surface); /* 内容底 surface */
|
||||
if (g_lv) meow_widget_draw(g_lv); /* 分组列表 */
|
||||
/* 底部版本信息(右对齐 dim) */
|
||||
char ver[32];
|
||||
snprintf(ver, sizeof ver, "MeowOS v%s", g_meowos_version);
|
||||
ui_draw(W - 8 - ui_width(ver, 18), H - SUB_FOOT_H + 10, ver,
|
||||
s_ui_theme()->outline, 18);
|
||||
}
|
||||
|
||||
/* 顶部开关切换:写设置 + 生效 + 触发重扫 + 重填行。 */
|
||||
static void sp_top_sw_toggle(void)
|
||||
{
|
||||
if (!g_sp || !g_sp->top_sw_set) return;
|
||||
g_sp->top_sw_set(g_sp->ctx, !g_top_on);
|
||||
g_top_on = !g_top_on;
|
||||
if (g_sw) meow_switch_set(g_sw, g_top_on);
|
||||
if (g_sp->rows) {
|
||||
g_nrows = g_sp->rows(g_sp->ctx, g_rows, SUB_ROWS_MAX);
|
||||
if (g_nrows > SUB_ROWS_MAX) g_nrows = SUB_ROWS_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
/* 动作后重扫行(act/detail 已变更数据):重填行并 clamp 滚动。 */
|
||||
static void sp_refresh_rows(void)
|
||||
{
|
||||
if (g_sp && g_sp->rows) {
|
||||
g_nrows = g_sp->rows(g_sp->ctx, g_rows, SUB_ROWS_MAX);
|
||||
if (g_nrows > SUB_ROWS_MAX) g_nrows = SUB_ROWS_MAX;
|
||||
}
|
||||
if (g_lv) {
|
||||
int ms = meow_listview_max_scroll(g_lv);
|
||||
if (meow_listview_scroll(g_lv) > ms) meow_listview_set_scroll(g_lv, ms);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 运行子页(阻塞到返回;返回 0)---- */
|
||||
int meow_subpage_run(sub_page_t *p)
|
||||
{
|
||||
struct keypoll kp_power, kp_keys;
|
||||
kp_open(&kp_power, DEV_POWER); kp_open(&kp_keys, DEV_KEYS);
|
||||
struct kd_state kd_vold, kd_volu, kd_play, kd_pwr;
|
||||
memset(&kd_vold, 0, sizeof kd_vold); memset(&kd_volu, 0, sizeof kd_volu);
|
||||
memset(&kd_play, 0, sizeof kd_play); memset(&kd_pwr, 0, sizeof kd_pwr);
|
||||
int m_prev_vold = 0, m_prev_volu = 0, m_prev_play = 0, m_prev_pwr = 0;
|
||||
for (int w = 0; w < KEYBUF_WORDS; w++) {
|
||||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) m_prev_vold = 1;
|
||||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) m_prev_volu = 1;
|
||||
if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) m_prev_play = 1;
|
||||
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) m_prev_pwr = 1;
|
||||
}
|
||||
int tfd = open(DEV_TOUCH, O_RDONLY | O_NONBLOCK);
|
||||
if (tfd >= 0) { unsigned char ev[16]; while (read(tfd, ev, sizeof ev) == 16) {} }
|
||||
unsigned long long touch_cooldown_until = now_ms() + 300;
|
||||
unsigned long long last_tick = now_ms();
|
||||
unsigned long long last_act = now_ms();
|
||||
unsigned long long last_live = now_ms();
|
||||
int after_modal = 0; /* 子页内模态关闭后:覆盖过状态栏,需整帧重绘还原 */
|
||||
|
||||
/* 触摸状态 */
|
||||
int td = 0, td_ok = 0, tdx = -1, tdy = -1, tlx = -1, tly = -1;
|
||||
int scroll_drag = 0, scroll_invert = 0, drag_last_y = 0;
|
||||
int wake_suppress = 0;
|
||||
unsigned long long pwr_hold_at = 0;
|
||||
int pwr_menu_state = 0, pwr_woke = 0, pwr_suppress_click = 0;
|
||||
|
||||
/* 顶栏开关初始值 */
|
||||
int top_on = 0, top_sw_en = 0;
|
||||
if (p->top_sw) top_sw_en = p->top_sw(p->ctx, &top_on);
|
||||
|
||||
/* 首填行 + lazy 建控件 + 配置 + 首绘 */
|
||||
g_sp = p;
|
||||
g_nrows = p->rows ? p->rows(p->ctx, g_rows, SUB_ROWS_MAX) : 0;
|
||||
if (g_nrows > SUB_ROWS_MAX) g_nrows = SUB_ROWS_MAX;
|
||||
if (!g_sp_init) sp_widgets_create();
|
||||
sp_configure_page(p, top_sw_en, top_on);
|
||||
|
||||
meow_ui_activity_push(SUB_SENTINEL_ACT); /* 控件 owner 闸门打开(hit 生效) */
|
||||
sp_paint_page();
|
||||
meow_dirty_full(); meow_ui_flush();
|
||||
|
||||
for (;;) {
|
||||
unsigned long long now = now_ms();
|
||||
if (sigterm_flag) break;
|
||||
int need_paint = 0;
|
||||
|
||||
/* ---- 自动息屏态:留在子页,等待任意输入唤醒,恢复后重绘(不丢上下文)---- */
|
||||
if (meow_auto_slept && !meow_screen_on) {
|
||||
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
|
||||
int wake = 0;
|
||||
for (int w = 0; w < KEYBUF_WORDS; w++) {
|
||||
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) wake = 1;
|
||||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) wake = 1;
|
||||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) wake = 1;
|
||||
if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) wake = 1;
|
||||
}
|
||||
if (!wake && screen_off_mode == 1 && tfd >= 0) {
|
||||
struct pollfd spfd = { tfd, POLLIN, 0 };
|
||||
if (poll(&spfd, 1, 0) > 0) {
|
||||
unsigned char ev[16];
|
||||
while (read(tfd, ev, sizeof ev) == 16) {
|
||||
uint16_t type = *(uint16_t *)(ev + 8), code = *(uint16_t *)(ev + 10);
|
||||
int32_t val = *(int32_t *)(ev + 12);
|
||||
if (type == EV_KEY && code == BTN_TOUCH && val) wake = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wake) {
|
||||
meow_panel_on();
|
||||
meow_auto_slept = 0;
|
||||
wake_suppress = 1;
|
||||
last_act = now;
|
||||
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);
|
||||
touch_cooldown_until = now_ms() + 300;
|
||||
td = 0; td_ok = 0; scroll_drag = 0; drag_last_y = 0; tlx = tly = -1;
|
||||
after_modal = 1;
|
||||
sp_paint_page();
|
||||
meow_dirty_full(); meow_ui_flush();
|
||||
}
|
||||
usleep(POLL_MS * 1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ---- 按键(与旧版逐字一致:音量=系统音量/播放=媒体预留/电源=系统级语义)---- */
|
||||
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
|
||||
int vold = 0, volu = 0, play = 0, pwr = 0;
|
||||
for (int w = 0; w < KEYBUF_WORDS; w++) {
|
||||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEDOWN % 32))) vold = 1;
|
||||
if (kp_keys.cur[w] & (1UL << (KEY_VOLUMEUP % 32))) volu = 1;
|
||||
if (kp_keys.cur[w] & (1UL << (KEY_PLAYPAUSE % 32))) play = 1;
|
||||
if (kp_power.cur[w] & (1UL << (KEY_POWER % 32))) pwr = 1;
|
||||
}
|
||||
(void)kd_tick(&kd_vold, vold); (void)kd_tick(&kd_volu, volu);
|
||||
(void)kd_tick(&kd_play, play); (void)kd_tick(&kd_pwr, pwr);
|
||||
vold = kd_vold.stable; volu = kd_volu.stable;
|
||||
play = kd_play.stable; pwr = kd_pwr.stable;
|
||||
if (wake_suppress) {
|
||||
if (!pwr && !vold && !volu && !play) wake_suppress = 0;
|
||||
} else {
|
||||
if (vold && !m_prev_vold) { settings_volume_adj(-settings_volume_step); need_paint = 1; }
|
||||
if (volu && !m_prev_volu) { settings_volume_adj(+settings_volume_step); need_paint = 1; }
|
||||
if (play && !m_prev_play) { logmsg("subpage: play key caught (media-reserved)"); last_act = now; }
|
||||
if (pwr && !m_prev_pwr) {
|
||||
pwr_hold_at = now; pwr_menu_state = 0;
|
||||
pwr_suppress_click = 0;
|
||||
last_act = now;
|
||||
if (meow_auto_slept && !meow_screen_on) pwr_woke = 1;
|
||||
}
|
||||
if (pwr && pwr_hold_at) {
|
||||
if (now - pwr_hold_at >= (unsigned long long)power_forceoff_ms && !pwr_menu_state) {
|
||||
if (sim_no_exec) { logmsg("subpage pm: [sim-no-exec] POWER HOLD -> force poweroff"); }
|
||||
else { logmsg("subpage pm: POWER HOLD -> force poweroff"); meow_power_action(POWER_SHUTDOWN); }
|
||||
} else if (now - pwr_hold_at >= (unsigned long long)power_longpress_menu_ms && !pwr_menu_state) {
|
||||
pwr_menu_state = 1;
|
||||
logmsg("subpage pm: POWER HOLD -> show power menu");
|
||||
show_power_menu();
|
||||
last_act = now_ms();
|
||||
pwr_hold_at = 0; pwr_menu_state = 0; pwr_woke = 0;
|
||||
pwr_suppress_click = 1;
|
||||
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);
|
||||
after_modal = 1;
|
||||
need_paint = 1;
|
||||
goto sub_keys_seeded;
|
||||
}
|
||||
}
|
||||
if (!pwr && m_prev_pwr) {
|
||||
if (pwr_suppress_click) {
|
||||
logmsg("subpage pm: power release after menu -> no toggle");
|
||||
} else if (pwr_hold_at && (now - pwr_hold_at < (unsigned long long)power_longpress_menu_ms) &&
|
||||
!pwr_menu_state && !pwr_woke) {
|
||||
logmsg("subpage pm: power single-click -> toggle panel");
|
||||
meow_toggle_panel();
|
||||
meow_auto_slept = 0;
|
||||
last_act = now_ms();
|
||||
need_paint = 1;
|
||||
} else if (pwr_woke) {
|
||||
logmsg("subpage pm: power release after auto wake -> no toggle");
|
||||
}
|
||||
pwr_hold_at = 0; pwr_menu_state = 0; pwr_woke = 0;
|
||||
pwr_suppress_click = 0;
|
||||
}
|
||||
}
|
||||
if (vol_ramp_dir != 0) {
|
||||
int held = (vol_ramp_dir > 0) ? volu : vold;
|
||||
(void)vol_ramp_tick(held);
|
||||
g_sys.vol = settings_volume_get();
|
||||
paint_sb_right();
|
||||
need_paint = 1;
|
||||
}
|
||||
m_prev_vold = vold; m_prev_volu = volu; m_prev_play = play; m_prev_pwr = pwr;
|
||||
sub_keys_seeded:
|
||||
;
|
||||
|
||||
/* ---- 触摸(SDK 控件命中:topappbar/顶开关/listview + meow_scroll 滚动)---- */
|
||||
if (tfd >= 0 && meow_screen_on) {
|
||||
struct pollfd pfd = { tfd, POLLIN, 0 };
|
||||
if (poll(&pfd, 1, 0) > 0) {
|
||||
unsigned char ev[16];
|
||||
while (read(tfd, ev, sizeof ev) == 16) {
|
||||
uint16_t type = *(uint16_t *)(ev + 8), code = *(uint16_t *)(ev + 10);
|
||||
int32_t val = *(int32_t *)(ev + 12);
|
||||
if (type == EV_ABS) {
|
||||
if (code == ABS_X || code == ABS_MT_POSITION_X) tlx = val;
|
||||
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;
|
||||
meow_input_touch_set(1, tdx, tdy, 0); /* 喂 SDK 输入态(按压态渲染) */
|
||||
drag_last_y = tdy;
|
||||
if (g_lv && meow_widget_hit(g_lv, tlx, tly)) /* 按压态即时反馈 */
|
||||
sp_redraw_region(NULL);
|
||||
}
|
||||
if (td_ok && !scroll_drag) {
|
||||
int ddy = tly - drag_last_y;
|
||||
if (ddy >= S_SCROLL_SLOP || ddy <= -S_SCROLL_SLOP) {
|
||||
scroll_drag = 1;
|
||||
meow_settings_get_bool("input.swipe_invert", &scroll_invert);
|
||||
meow_input_touch_set(0, 0, 0, 0); /* 滚动中不压行(清按压态) */
|
||||
drag_last_y = tly; /* 从此增量(跨 slop 无跳变,对齐旧版补偿) */
|
||||
}
|
||||
}
|
||||
if (scroll_drag && td_ok) {
|
||||
int dydt = tly - drag_last_y; /* 本次位移增量(跟手) */
|
||||
drag_last_y = tly;
|
||||
int scdy = scroll_invert ? dydt : -dydt;
|
||||
if (g_sc && scdy) meow_scroll_track(g_sc, scdy, now);
|
||||
sp_redraw_region(NULL); /* 列表区重绘(新 offset) */
|
||||
need_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; scroll_drag = 0;
|
||||
meow_input_touch_set(0, 0, 0, 0); }
|
||||
} else if (!val && td) {
|
||||
td = 0;
|
||||
meow_input_touch_set(0, 0, 0, 0); /* 抬手:清 SDK 按压态 */
|
||||
if (scroll_drag) {
|
||||
scroll_drag = 0;
|
||||
if (g_sc) meow_scroll_fling(g_sc); /* 松手 fling(速度采样) */
|
||||
} else if (td_ok) {
|
||||
int dx = tlx - tdx, dyy = tly - tdy;
|
||||
/* 左缘左滑返回(全局手势语义) */
|
||||
if (meow_edge_back(tdx, dx, dyy)) {
|
||||
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
|
||||
goto subdone;
|
||||
}
|
||||
int adx = dx < 0 ? -dx : dx, ady = dyy < 0 ? -dyy : dyy;
|
||||
if (adx < 60 && ady < 60) {
|
||||
int tx = tlx, ty = tly;
|
||||
/* 顶栏:返回箭头(topappbar hit)/ 顶开关(meow_switch hit) */
|
||||
int tb_hit = g_tb ? meow_topappbar_hit(g_tb, tx, ty) : -1;
|
||||
if (tb_hit == MEOW_TOPBAR_BACK) {
|
||||
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
|
||||
goto subdone;
|
||||
}
|
||||
if (g_top_sw_en && g_sw && meow_widget_hit(g_sw, tx, ty)) {
|
||||
sp_top_sw_toggle();
|
||||
last_act = now;
|
||||
need_paint = 1;
|
||||
if (tfd >= 0) { unsigned char e[16]; while (read(tfd, e, sizeof e) == 16) {} }
|
||||
continue;
|
||||
}
|
||||
/* 列表行命中(SDK 命中:含滚动换算 + 越界/禁用行不响应) */
|
||||
int row_hit = g_lv ? meow_listview_hit(g_lv, tx, ty) : -1;
|
||||
if (row_hit >= 0) {
|
||||
sub_kind_t kind = g_rows[row_hit].kind;
|
||||
if (kind == SUB_SW || kind == SUB_ACT) {
|
||||
if (g_sp->act) g_sp->act(g_sp->ctx, row_hit, g_rows[row_hit].act);
|
||||
} else if (kind == SUB_ROW) {
|
||||
if (g_sp->detail) g_sp->detail(g_sp->ctx, row_hit, g_rows[row_hit].act);
|
||||
}
|
||||
/* 动作后重扫行 + clamp 滚动 */
|
||||
sp_refresh_rows();
|
||||
}
|
||||
/* 仅命中行(回调可能开模态)才做后置排空/触摸基线重置/整帧重绘
|
||||
* (对齐旧版 idx>=0 分支;空白区点按不重绘不排空,置 td_ok=0 即可)。 */
|
||||
if (row_hit >= 0) {
|
||||
if (tfd >= 0) {
|
||||
unsigned char e[16];
|
||||
while (read(tfd, e, sizeof e) == 16) {}
|
||||
usleep(40000);
|
||||
while (read(tfd, e, sizeof e) == 16) {}
|
||||
}
|
||||
td = 0; td_ok = 0; scroll_drag = 0;
|
||||
drag_last_y = 0;
|
||||
tdx = tdy = tlx = tly = -1;
|
||||
touch_cooldown_until = now_ms() + 300;
|
||||
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);
|
||||
after_modal = 1;
|
||||
need_paint = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
td_ok = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 周期性重扫(live 页:蓝牙附近扫描等驻留更新)---- */
|
||||
if (g_sp && g_sp->live && now - last_live >= 2000) {
|
||||
last_live = now;
|
||||
int new_n = g_sp->rows ? g_sp->rows(g_sp->ctx, g_rows, SUB_ROWS_MAX) : 0;
|
||||
if (new_n > SUB_ROWS_MAX) new_n = SUB_ROWS_MAX;
|
||||
if (new_n != g_nrows) {
|
||||
g_nrows = new_n;
|
||||
int ms = g_lv ? meow_listview_max_scroll(g_lv) : 0;
|
||||
if (g_lv && meow_listview_scroll(g_lv) > ms) meow_listview_set_scroll(g_lv, ms);
|
||||
last_act = now;
|
||||
need_paint = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 重绘(整帧/内容区局部;fling 由 meow_scroll redraw 回调自刷)---- */
|
||||
if (need_paint) {
|
||||
last_act = now;
|
||||
sp_paint_page();
|
||||
if (after_modal) { meow_dirty_full(); after_modal = 0; }
|
||||
else meow_dirty_add(0, SUB_TB_Y, fb_xres, fb_yres - SUB_TB_Y); /* 含顶栏(开关/返回/标题) */
|
||||
meow_ui_flush();
|
||||
}
|
||||
|
||||
/* ---- 自动息屏(空闲超时关屏,留在子页,等输入唤醒)---- */
|
||||
if (screen_off_timeout > 0 && meow_screen_on && !meow_auto_slept &&
|
||||
(unsigned long long)(now - last_act) >= (unsigned long long)screen_off_timeout * 1000) {
|
||||
meow_panel_off();
|
||||
meow_auto_slept = 1;
|
||||
wake_suppress = 1;
|
||||
logmsg("subpage: idle %ds -> auto screen-off (stay)", screen_off_timeout);
|
||||
}
|
||||
/* ~1s 状态栏时钟刷新 */
|
||||
if (now - last_tick >= 1000) {
|
||||
last_tick = now;
|
||||
draw_clock();
|
||||
fb_norm_buf0();
|
||||
meow_ui_flush();
|
||||
}
|
||||
/* M7.1a: 推进 fling/回弹/snap 动画(无动画零开销) */
|
||||
meow_anim_tick();
|
||||
usleep(POLL_MS * 1000);
|
||||
}
|
||||
subdone:
|
||||
meow_input_touch_set(0, 0, 0, 0); /* 防残留按压态 */
|
||||
meow_ui_activity_pop(); /* 关 owner 闸门(控件 owner 仍钉哨兵,复用) */
|
||||
if (tfd >= 0) close(tfd);
|
||||
kp_close(&kp_power); kp_close(&kp_keys);
|
||||
logmsg("subpage '%s' closed", p->title ? p->title : "");
|
||||
return 0;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,81 @@
|
||||
/* test_fg_act.c — M2.17 keep_awake:前台顶层 activity(fg_act)清空兜底宿主单测。
|
||||
*
|
||||
* 验证「App 会话退出/崩溃回收时清 fg_act」这一 launcher 兜底所依赖的状态机:
|
||||
* - meow_fg_act_set(app,act) 依注册表 route.keep_awake → fg_keep_awake()==1;
|
||||
* - App 会话结束(本工程在 launch_app_chain 退出尾调 meow_fg_act_back())→ fg 清空、
|
||||
* fg_keep_awake() 回 0(主页不再被 keep_awake 卡住自动息屏);
|
||||
* - App 存活时(仅有 ACT_FOCUS,未会话结束)fg_act 保持不清。
|
||||
* 该测试把真实 app_launcher.c 整包含进来(同 test_lock_screen / test_notif_panel 手法),
|
||||
* 用 -ffunction-sections -Wl,--gc-sections 只保留 main() 触及的 fg_act 原语,其余重函数
|
||||
* (fork/exec/waitpid/fb)被剔除,故只需 stub logmsg。
|
||||
*
|
||||
* 运行(patch_v2/meow_launcherd 下):
|
||||
* gcc -D_GNU_SOURCE -Wall -Wextra -ffunction-sections -Wl,--gc-sections \
|
||||
* -I. -o test_fg_act test_fg_act.c && ./test_fg_act
|
||||
* 全 PASS -> exit 0。
|
||||
*/
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
/* logmsg:fg_act 原语的唯一外部依赖(app_launcher.c 内定义,需在 include 前提供)。 */
|
||||
void logmsg(const char *fmt, ...)
|
||||
{
|
||||
(void)fmt;
|
||||
}
|
||||
|
||||
#include "app_launcher.c" /* 真实实现:g_accts/g_naccts/meow_fg_act_{set,back}/fg_keep_awake */
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond, msg) do { \
|
||||
if (!(cond)) { fprintf(stderr, "FAIL: %s (%s:%d)\n", msg, __FILE__, __LINE__); g_fail++; } \
|
||||
else fprintf(stderr, "ok: %s\n", msg); \
|
||||
} while (0)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
/* ---- 基线:无上报 = 主界面(非 keep_awake)---- */
|
||||
CHECK(fg_keep_awake() == 0, "baseline: empty fg_act -> keep_awake=0");
|
||||
|
||||
/* ---- 注册 ncm 的 player activity 声明 keep_awake(模拟 app.json awake)---- */
|
||||
struct meow_acct *a = meow_acct_upsert("dev.player.ncm");
|
||||
CHECK(a != NULL, "register dev.player.ncm");
|
||||
a->nroute = 1;
|
||||
strncpy(a->routes[0].act, "player", sizeof a->routes[0].act - 1);
|
||||
a->routes[0].act[sizeof a->routes[0].act - 1] = 0;
|
||||
a->routes[0].keep_awake = 1;
|
||||
|
||||
/* ---- sim A:App 存活、正报 keep_awake activity(如 ncm 播放页)----
|
||||
* 这是「App 在 keep_awake activity 时 fg_act 保持(不清)」的契约点。 */
|
||||
meow_fg_act_set("dev.player.ncm", "player");
|
||||
CHECK(fg_keep_awake() == 1, "ncm playing -> keep_awake=1 (保持, 不清)");
|
||||
CHECK(strcmp(g_fg_app, "dev.player.ncm") == 0, "fg_app kept while app alive");
|
||||
CHECK(strcmp(g_fg_act, "player") == 0, "fg_act kept while app alive");
|
||||
|
||||
/* ---- sim B:App 会话结束/崩溃回收(launch_app_chain 退出尾调用 meow_fg_act_back())---- */
|
||||
meow_fg_act_back();
|
||||
CHECK(fg_keep_awake() == 0, "app session end -> keep_awake=0 (兜底已清)");
|
||||
CHECK(g_fg_act[0] == 0, "fg_act cleared on session end");
|
||||
CHECK(g_fg_app[0] == 0, "fg_app cleared on session end");
|
||||
|
||||
/* ---- sim C:ACT_FOCUS(home) 正常弹回 home(agent-356)→ 非 keep_awake,且可再被兜底幂等清 ---- */
|
||||
meow_fg_act_set("dev.player.ncm", "home"); /* home 未声明 keep_awake */
|
||||
CHECK(fg_keep_awake() == 0, "ACT_FOCUS(home) -> keep_awake=0");
|
||||
meow_fg_act_back(); /* 兜底幂等:再清一次不冲突 */
|
||||
CHECK(g_fg_act[0] == 0 && fg_keep_awake() == 0, "back after home focus idempotent");
|
||||
|
||||
/* ---- sim D:act 为空(set(NULL act))/未知 app / 未知 act → 非 keep_awake ---- */
|
||||
meow_fg_act_set("dev.player.ncm", NULL);
|
||||
CHECK(fg_keep_awake() == 0, "set(act=NULL) -> keep_awake=0");
|
||||
meow_fg_act_set("no.such.app", "player");
|
||||
CHECK(fg_keep_awake() == 0, "unregistered app -> keep_awake=0");
|
||||
meow_fg_act_set("dev.player.ncm", "notascreen");
|
||||
CHECK(fg_keep_awake() == 0, "unknown act in known app -> keep_awake=0");
|
||||
|
||||
if (g_fail) { fprintf(stderr, "\n== %d assertion(s) FAILED ==\n", g_fail); return 1; }
|
||||
fprintf(stderr, "\n== test_fg_act ALL PASS ==\n");
|
||||
return 0;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,223 @@
|
||||
/* test_lock_widgets.c — M7.1b 锁屏 UI 控件化宿主验证。
|
||||
*
|
||||
* 复现 lock_screen_ui.c 所建锁屏键盘控件集(12 键 meow_widget_custom + meow_widget 命中 +
|
||||
* 按压态 MEOW_WS_PRESSED→primary 26% α),验证:
|
||||
* P1 键盘渲染:12 键各画成 surfaceContainer 圆角钮(非背景像素);
|
||||
* P2 键盘命中:meow_widget_hit 在每键中心命中;键值映射(digit/清空/退格)正确;间隙不命中;
|
||||
* P3 主题四态(MD3/MIUIx × 明暗)键盘渲染非背景;
|
||||
* P4 按压态:meow_widget_set_state(MEOW_WS_PRESSED) → 该键像素变化(primary 26% α 叠加);
|
||||
* P5 键值 round-trip:lk_key_value_at 在键中心/间隙分别返回正确键值与 LK_DOWN_NONE。
|
||||
*
|
||||
* 布局几何/键序与 lock_screen_ui.c 镜像(LK_X0..LK_COLS/LK_ROWS/lk_keys),控件创建方式与
|
||||
* lk_widgets_build 同(owner=哨兵 0x51,meow_widget_custom_create,render 读 meow_widget_rect 几何)。
|
||||
*
|
||||
* 编译(在 patch_v2/meow_launcherd 下):
|
||||
* gcc -Wall -Wextra -o test_lock_widgets test_lock_widgets.c \
|
||||
* ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \
|
||||
* ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \
|
||||
* ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \
|
||||
* ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \
|
||||
* ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \
|
||||
* ../sdk/meow_ui_scroll.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \
|
||||
* ../sdk/meow_notify_ipc.c ../sdk/stb_image.c -I../sdk -I. -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_lock_widgets
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h" /* meow_ui_back_buf / meow_widget_rect / meow_gfx_fill_round[_alpha] */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { printf(" PASS: " __VA_ARGS__); printf("\n"); } \
|
||||
else { printf(" FAIL: " __VA_ARGS__); printf("\n"); g_fail++; } } while (0)
|
||||
|
||||
/* ---- 与 lock_screen_ui.c 同源几何(镜像) ---- */
|
||||
#define LK_X0 32
|
||||
#define LK_COLW 130
|
||||
#define LK_COLG 8
|
||||
#define LK_Y0 320
|
||||
#define LK_ROW_H 82
|
||||
#define LK_ROWG 10
|
||||
#define LK_ROWS 4
|
||||
#define LK_COLS 3
|
||||
#define LK_ACT_CLR (-2)
|
||||
#define LK_ACT_BS (-1)
|
||||
#define LK_SENTINEL 0x51 /* 锁屏控件 owner 哨兵(镜像 lock_screen_ui.c) */
|
||||
#define LK_DOWN_NONE (-10)
|
||||
static const int lk_keys[LK_ROWS][LK_COLS] = {
|
||||
{ 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }, { LK_ACT_CLR, 0, LK_ACT_BS },
|
||||
};
|
||||
#define XRES 480
|
||||
#define YRES 720
|
||||
|
||||
static meow_widget *lk_w[LK_ROWS][LK_COLS];
|
||||
|
||||
static void lk_rect(int row, int col, int rc[4])
|
||||
{
|
||||
rc[0] = LK_X0 + col * (LK_COLW + LK_COLG);
|
||||
rc[1] = LK_Y0 + row * (LK_ROW_H + LK_ROWG);
|
||||
rc[2] = LK_COLW;
|
||||
rc[3] = LK_ROW_H;
|
||||
}
|
||||
static void lk_center(int row, int col, int *cx, int *cy)
|
||||
{
|
||||
int rc[4]; lk_rect(row, col, rc);
|
||||
*cx = rc[0] + rc[2] / 2;
|
||||
*cy = rc[1] + rc[3] / 2;
|
||||
}
|
||||
|
||||
/* ---- 键渲染(镜像 lock_screen_ui.c lk_key_render 的视觉语义)---- */
|
||||
static void key_render(meow_widget *w, const meow_theme *th, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
int rc[4]; meow_widget_rect(w, rc);
|
||||
int rad = (th->radius > 0) ? th->radius : 10;
|
||||
meow_gfx_fill_round(rc[0], rc[1], rc[2], rc[3], rad, th->surfaceContainer);
|
||||
if (meow_widget_state(w) & MEOW_WS_PRESSED)
|
||||
meow_gfx_fill_round_alpha(rc[0], rc[1], rc[2], rc[3], rad, th->primary, 66);
|
||||
meow_ui_text_center(rc[0] + rc[2] / 2,
|
||||
rc[1] + rc[3] / 2 - meow_ui_text_height(22) / 2,
|
||||
"1", 22, th->onSurface);
|
||||
}
|
||||
|
||||
static void mk_widgets(void)
|
||||
{
|
||||
meow_ui_activity_push(LK_SENTINEL);
|
||||
for (int r = 0; r < LK_ROWS; r++)
|
||||
for (int c = 0; c < LK_COLS; c++) {
|
||||
int rc[4]; lk_rect(r, c, rc);
|
||||
lk_w[r][c] = meow_widget_custom_create(rc[0], rc[1], rc[2], rc[3],
|
||||
key_render, NULL,
|
||||
(void *)(intptr_t)lk_keys[r][c]);
|
||||
}
|
||||
meow_ui_activity_pop();
|
||||
}
|
||||
|
||||
/* 坐标 -> 键值(镜像 lock_screen_ui.c lk_key_value_at)。 */
|
||||
static int key_value_at(int x, int y)
|
||||
{
|
||||
for (int r = 0; r < LK_ROWS; r++)
|
||||
for (int c = 0; c < LK_COLS; c++)
|
||||
if (lk_w[r][c] && meow_widget_hit(lk_w[r][c], x, y))
|
||||
return lk_keys[r][c];
|
||||
return LK_DOWN_NONE;
|
||||
}
|
||||
|
||||
static void draw_all(void)
|
||||
{
|
||||
const meow_theme *th = meow_theme_get();
|
||||
meow_ui_fill_rect(0, 0, XRES, YRES, th->surface);
|
||||
for (int r = 0; r < LK_ROWS; r++)
|
||||
for (int c = 0; c < LK_COLS; c++)
|
||||
if (lk_w[r][c]) meow_widget_draw(lk_w[r][c]);
|
||||
}
|
||||
|
||||
static unsigned short px565(int x, int y)
|
||||
{
|
||||
unsigned char *bb = meow_ui_back_buf();
|
||||
int pitch = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp();
|
||||
if (!bb || x < 0 || y < 0 || x >= XRES || y >= YRES) return 0;
|
||||
if (bpp == 16) return *(unsigned short *)(bb + (size_t)y * pitch + (size_t)x * 2);
|
||||
return (unsigned short)(*(unsigned int *)(bb + (size_t)y * pitch + (size_t)x * 4) >> 16);
|
||||
}
|
||||
static unsigned short bg565(uint32_t bg)
|
||||
{
|
||||
return (unsigned short)(((bg >> 8) & 0xF800) | ((bg >> 5) & 0x07E0) | ((bg >> 3) & 0x001F));
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
setbuf(stdout, NULL);
|
||||
setenv("MEOW_UI_DUMP", "/tmp/lock_widgets.ppm", 1);
|
||||
meow_ui_opts o = { XRES, YRES };
|
||||
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
|
||||
mk_widgets();
|
||||
meow_ui_activity_push(LK_SENTINEL); /* 命中闸门打开(owner==active) */
|
||||
|
||||
/* ============ P1 键盘渲染(12 键各画成非背景钮) ============ */
|
||||
printf("P1 keypad render (12 keys non-bg)\n");
|
||||
const meow_theme *th = meow_theme_get();
|
||||
draw_all();
|
||||
int all_nonbg = 1;
|
||||
for (int r = 0; r < LK_ROWS && all_nonbg; r++)
|
||||
for (int c = 0; c < LK_COLS && all_nonbg; c++) {
|
||||
int cx, cy; lk_center(r, c, &cx, &cy);
|
||||
if (px565(cx, cy) == bg565(th->surface)) all_nonbg = 0;
|
||||
}
|
||||
CHECK(all_nonbg, "each of 12 keys draws non-surface at center");
|
||||
|
||||
/* ============ P2 命中(meow_widget_hit + 键值映射) ============ */
|
||||
printf("P2 keypad hit via meow_widget_hit\n");
|
||||
int hit_ok = 1, val_ok = 1;
|
||||
for (int r = 0; r < LK_ROWS; r++)
|
||||
for (int c = 0; c < LK_COLS; c++) {
|
||||
int cx, cy; lk_center(r, c, &cx, &cy);
|
||||
if (!meow_widget_hit(lk_w[r][c], cx, cy)) hit_ok = 0;
|
||||
if (key_value_at(cx, cy) != lk_keys[r][c]) val_ok = 0;
|
||||
}
|
||||
CHECK(hit_ok, "meow_widget_hit hits every key center");
|
||||
CHECK(val_ok, "key_value_at maps every key center to its digit/clear/backspace value");
|
||||
/* 间隙(两列之间)不命中 */
|
||||
int gap_x = LK_X0 + LK_COLW + LK_COLG / 2; /* 列0 与列1 之间的间隙中心 */
|
||||
int gap_y = LK_Y0 + LK_ROW_H / 2;
|
||||
CHECK(meow_widget_hit(lk_w[0][0], gap_x, gap_y) == 0, "gap between columns not hit");
|
||||
CHECK(key_value_at(gap_x, gap_y) == LK_DOWN_NONE, "gap not mapped to any key");
|
||||
|
||||
/* ============ P3 主题四态(MD3/MIUIx × 明暗)键盘渲染非背景 ============ */
|
||||
printf("P3 theme 4-state keypad render\n");
|
||||
const int styles[2] = { MEOW_STYLE_MD3, MEOW_STYLE_MIUIX };
|
||||
const int modes[2] = { MEOW_THEME_LIGHT, MEOW_THEME_DARK };
|
||||
char nm[96];
|
||||
for (int si = 0; si < 2; si++) {
|
||||
for (int mi = 0; mi < 2; mi++) {
|
||||
meow_ui_set_style(styles[si]);
|
||||
meow_theme_set_mode((meow_theme_mode)modes[mi]);
|
||||
th = meow_theme_get();
|
||||
draw_all();
|
||||
/* 用一个键区的多像素带判定非背景(文字+底) */
|
||||
int rc[4]; lk_rect(1, 0, rc);
|
||||
int nonbg = 0;
|
||||
for (int dy = 0; dy < rc[3] && !nonbg; dy++)
|
||||
for (int dx = 0; dx < rc[2] && !nonbg; dx++)
|
||||
if (px565(rc[0] + dx, rc[1] + dy) != bg565(th->surface)) nonbg = 1;
|
||||
snprintf(nm, sizeof nm, "style=%s mode=%s keypad renders non-surface",
|
||||
styles[si] ? "MIUIx" : "MD3", modes[mi] ? "dark" : "light");
|
||||
CHECK(nonbg, "%s", nm);
|
||||
}
|
||||
}
|
||||
meow_ui_set_style(MEOW_STYLE_MD3);
|
||||
meow_theme_set_mode(MEOW_THEME_DARK);
|
||||
|
||||
/* ============ P4 按压态(MEOW_WS_PRESSED → primary 26% α 像素变化) ============ */
|
||||
printf("P4 pressed state -> primary 26%% alpha overlay\n");
|
||||
th = meow_theme_get();
|
||||
draw_all();
|
||||
int cx, cy; lk_center(2, 1, &cx, &cy); /* 键 (2,1) = 数字 8 */
|
||||
unsigned short p0 = px565(cx, cy);
|
||||
meow_widget_set_state(lk_w[2][1], MEOW_WS_PRESSED, 1);
|
||||
draw_all();
|
||||
unsigned short p1 = px565(cx, cy);
|
||||
meow_widget_set_state(lk_w[2][1], MEOW_WS_PRESSED, 0);
|
||||
CHECK(p1 != p0, "pressed key center pixel changes (primary 26%% overlay)");
|
||||
draw_all();
|
||||
unsigned short p2 = px565(cx, cy);
|
||||
CHECK(p2 == p0, "release restores original pixel");
|
||||
|
||||
/* ============ P5 键值 round-trip(清空/退格/间隙) ============ */
|
||||
printf("P5 key-value round-trip (clear/backspace/gap)\n");
|
||||
int ccx, ccy, bcx, bcy, zcx, zcy;
|
||||
lk_center(3, 0, &ccx, &ccy); /* 清空 */
|
||||
lk_center(3, 1, &zcx, &zcy); /* 0 */
|
||||
lk_center(3, 2, &bcx, &bcy); /* 退格 */
|
||||
CHECK(key_value_at(ccx, ccy) == LK_ACT_CLR, "bottom-left center == CLEAR (-2)");
|
||||
CHECK(key_value_at(zcx, zcy) == 0, "bottom-mid center == 0");
|
||||
CHECK(key_value_at(bcx, bcy) == LK_ACT_BS, "bottom-right center == BACKSPACE (-1)");
|
||||
|
||||
meow_ui_activity_pop();
|
||||
meow_ui_shutdown();
|
||||
printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail);
|
||||
return g_fail;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,360 @@
|
||||
/* test_menu_sdk.c — M7.4 主界面迁 SDK(meow_lazygrid 宫格 + meow_listview 列表 + meow_scroll)宿主验证。
|
||||
*
|
||||
* 复现 main_menu.c M7.4 SDK 路径所用几何/数据源(与 main_menu.c 同源常量镜像):
|
||||
* app 区视口 = menu_app_region(apps_top, layout):宫格 x=12(PAD) w=xres-2*PAD / 列表 x=8 w=xres-16,
|
||||
* 顶=apps_top(=ROW_TOP=96,宿主无字库→mfont_ready=0),底=EXIT_ROW-ROW_H-4=584;
|
||||
* 宫格 cell_w=(xres-2*PAD-gap)/cols=223,cell_h=CARD_H=112,gap=10,row_h=122;
|
||||
* 列表 row_h=LIST_ROW_H=60。应用数 MAX_APPS=8。
|
||||
*
|
||||
* 验证(M7 §4 验收点:双布局可切 / 图标·缺失·后台标注正确 / 空态 / 滚动惯性):
|
||||
* G1 宫格虚拟化渲染:每可见格调自绘一次;格位置 = menu_app_region/menu_grid_cell_w 公式映射
|
||||
* (480×720 校准:cell0=12,96 cell1=245,96 cell2=12,218 … 不被截断、不越视口)。
|
||||
* G2 宫格命中:格中心→正确 idx;水平/垂直 gap→-1;越界→-1;滚动后命中换算正确。
|
||||
* G3 宫格滚动:8 格(MAX_APPS)装得下 → max_scroll==0;12 格超屏 → max_scroll>0;scroll_attach 可建。
|
||||
* L1 列表渲染+命中:视口 x=8,w=464;row 矩形与公式一致;8 行装得下 / 12 行超屏。
|
||||
* E 空态:count==0 → 不画任何格/行卡(空态路径),无回退。
|
||||
* M 图标·缺失·后台标注:自绘按 app->ok/meow_bg_is_backgrounded 决策「[缺失]」「·后台」/均无。
|
||||
*
|
||||
* 注:main_menu.c 的 menu_draw_grid_cell/menu_draw_list_cell 是 static(依赖 launcher 全局面),
|
||||
* 宿主以同源公式/决策规则镜像复现其契约;SDK 控件行为(虚拟化/命中/滚动/空态)为真实验证。
|
||||
*
|
||||
* 编译(在 patch_v2/meow_launcherd 下,宿主 gcc):
|
||||
* gcc -Wall -Wextra -o test_menu_sdk test_menu_sdk.c \
|
||||
* ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \
|
||||
* ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \
|
||||
* ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \
|
||||
* ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \
|
||||
* ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \
|
||||
* ../sdk/meow_ui_scroll.c ../sdk/meow_lazygrid.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \
|
||||
* ../sdk/meow_notify_ipc.c ../sdk/stb_image.c -I../sdk -I. -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_menu_sdk
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp + meow_gfx_clip */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int g_pass = 0, g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { g_pass++; } \
|
||||
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
|
||||
} while (0)
|
||||
|
||||
/* ---- 与 main_menu.c / meow_launcher_internal.h 同源几何(镜像) ---- */
|
||||
#define XRES 480
|
||||
#define YRES 720
|
||||
#define MENU_GRID_PAD 12
|
||||
#define MENU_GRID_GAP 10
|
||||
#define MENU_GRID_COLS 2
|
||||
#define MENU_GRID_CARD_H 112
|
||||
#define MENU_GRID_ICON 44
|
||||
#define MENU_LIST_ROW_H 60
|
||||
#define MENU_LIST_ICON 40
|
||||
#define MENU_LIST_ICON_X 24
|
||||
#define ROW_H 52
|
||||
#define ROW_TOP 96
|
||||
#define EXIT_ROW 640
|
||||
#define APPS_TOP ROW_TOP /* 宿主无字库 → mfont_ready=0 → menu_apps_top()=ROW_TOP */
|
||||
#define APPS_BOT (EXIT_ROW - ROW_H - 4) /* 584:设置行顶 */
|
||||
#define SDK_ACT 0x55
|
||||
|
||||
/* menu_app_region 镜像:宫格 vx=12 w=xres-24;列表 vx=8 w=xres-16;vh=底-顶。 */
|
||||
static int grid_cell_w(void) { return (XRES - 2 * MENU_GRID_PAD - (MENU_GRID_COLS - 1) * MENU_GRID_GAP) / MENU_GRID_COLS; }
|
||||
static int grid_row_h(void) { return MENU_GRID_CARD_H + MENU_GRID_GAP; }
|
||||
static void grid_region(int *vx, int *vy, int *vw, int *vh) { *vx = MENU_GRID_PAD; *vy = APPS_TOP; *vw = XRES - 2 * MENU_GRID_PAD; *vh = APPS_BOT - APPS_TOP; }
|
||||
static void list_region(int *vx, int *vy, int *vw, int *vh) { *vx = 8; *vy = APPS_TOP; *vw = XRES - 16; *vh = APPS_BOT - APPS_TOP; }
|
||||
|
||||
/* ---- mock 应用(镜像 struct app 的关键字段) ---- */
|
||||
typedef struct { const char *name; int ok; int bg; const char *pkg; } tapp_t;
|
||||
static tapp_t g_apps[16];
|
||||
static int g_napps = 0;
|
||||
static int g_draw_cnt = 0; /* 自绘回调调用次数(窗口虚拟化) */
|
||||
static int g_cell_x[16], g_cell_y[16], g_cell_w[16], g_cell_h[16]; /* 自绘收到的几何 */
|
||||
static int g_cell_idx[16];
|
||||
static char g_label[16][16]; /* 格/行自绘决策出的标注("" / "[缺失]" / "·后台") */
|
||||
|
||||
/* bind:读 mock app,title=名,enabled=1(命中即启动语义,同 main_menu menu_grid_bind)。 */
|
||||
static void grid_bind(int idx, meow_lazygrid_item *it, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
memset(it, 0, sizeof *it);
|
||||
if (idx < 0 || idx >= g_napps) return;
|
||||
it->title = g_apps[idx].name;
|
||||
it->subtitle = NULL;
|
||||
it->icon = NULL; /* 宿主无图标位图;自绘用文字/色块近似 */
|
||||
it->enabled = 1;
|
||||
it->user = &g_apps[idx];
|
||||
}
|
||||
static int grid_count(void *ctx) { (void)ctx; return g_napps; }
|
||||
static void list_bind(int idx, meow_lv_item *it, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
memset(it, 0, sizeof *it);
|
||||
if (idx < 0 || idx >= g_napps) return;
|
||||
it->title = g_apps[idx].name;
|
||||
it->subtitle = NULL;
|
||||
it->icon_kind = MEOW_LIST_ICON_NONE;
|
||||
it->enabled = 1;
|
||||
it->user = &g_apps[idx];
|
||||
}
|
||||
static int list_count(void *ctx) { (void)ctx; return g_napps; }
|
||||
|
||||
/* 镜像 menu_draw_grid_cell 的标注决策(ok→缺失[缺失];bg→·后台;均无→无)。 */
|
||||
static void decide_label(int idx, char *out, int olen)
|
||||
{
|
||||
const tapp_t *a = &g_apps[idx];
|
||||
if (!a->ok) snprintf(out, olen, "[缺失]");
|
||||
else if (a->bg) snprintf(out, olen, "·后台");
|
||||
else out[0] = 0;
|
||||
}
|
||||
|
||||
/* 宫格自绘(镜像 menu_sdk_grid_draw → 记录几何 + 标注决策;宿主无字体用色块近似)。 */
|
||||
static void grid_draw(int idx, int x, int y, int w, int h, int st, void *ctx)
|
||||
{
|
||||
(void)st; (void)ctx;
|
||||
if (idx < 0 || idx >= g_napps) return;
|
||||
if (g_draw_cnt < 16) {
|
||||
g_cell_idx[g_draw_cnt] = idx; g_cell_x[g_draw_cnt] = x; g_cell_y[g_draw_cnt] = y;
|
||||
g_cell_w[g_draw_cnt] = w; g_cell_h[g_draw_cnt] = h;
|
||||
g_draw_cnt++;
|
||||
}
|
||||
decide_label(idx, g_label[idx], sizeof g_label[idx]);
|
||||
meow_gfx_fill_round(x, y, w, h, 16, meow_theme_get()->surfaceContainer);
|
||||
meow_gfx_fill_round(x + w / 2 - 22, y + 12, 44, 44, 13, meow_theme_get()->primary);
|
||||
}
|
||||
/* 列表自绘(镜像 menu_sdk_list_draw) */
|
||||
static void list_draw(int idx, int x, int y, int w, int h, int st, void *ctx)
|
||||
{
|
||||
(void)st; (void)ctx;
|
||||
if (idx < 0 || idx >= g_napps) return;
|
||||
if (g_draw_cnt < 16) {
|
||||
g_cell_idx[g_draw_cnt] = idx; g_cell_x[g_draw_cnt] = x; g_cell_y[g_draw_cnt] = y;
|
||||
g_cell_w[g_draw_cnt] = w; g_cell_h[g_draw_cnt] = h;
|
||||
g_draw_cnt++;
|
||||
}
|
||||
decide_label(idx, g_label[idx], sizeof g_label[idx]);
|
||||
meow_gfx_fill_round(x, y, w, h, 12, meow_theme_get()->surfaceContainer);
|
||||
meow_gfx_fill_round(x + MENU_LIST_ICON_X, y + (h - MENU_LIST_ICON) / 2, MENU_LIST_ICON, MENU_LIST_ICON, 12,
|
||||
meow_theme_get()->primary);
|
||||
}
|
||||
|
||||
static void seed_apps(int n)
|
||||
{
|
||||
static const char *nm[16] = {"应用一","应用二","应用三","应用四","应用五","应用六","应用七","应用八",
|
||||
"应用九","应用十","应用十一","应用十二","应用十三","应用十四","应用十五","应用十六"};
|
||||
for (int i = 0; i < n; i++) {
|
||||
g_apps[i].name = nm[i]; g_apps[i].ok = 1; g_apps[i].bg = 0; g_apps[i].pkg = "os.meow.test";
|
||||
}
|
||||
/* 2 号缺失、3 号后台(用 0/1 锚定到前几格便于断言) */
|
||||
if (n > 1) g_apps[1].ok = 0;
|
||||
if (n > 2) g_apps[2].bg = 1;
|
||||
g_napps = n;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
meow_ui_opts o = { XRES, YRES };
|
||||
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
|
||||
const meow_theme *th = meow_theme_get();
|
||||
int gx, gy, gw, gh, lx, ly, lw, lh;
|
||||
grid_region(&gx, &gy, &gw, &gh); list_region(&lx, &ly, &lw, &lh);
|
||||
int cw = grid_cell_w(), rh = grid_row_h();
|
||||
CHECK(gx == MENU_GRID_PAD && gw == XRES - 2 * MENU_GRID_PAD, "grid viewport x=%d w=%d (expect %d,%d)", gx, gw, MENU_GRID_PAD, XRES - 2 * MENU_GRID_PAD);
|
||||
CHECK(lx == 8 && lw == XRES - 16, "list viewport x=%d w=%d", lx, lw);
|
||||
CHECK(gh == APPS_BOT - APPS_TOP, "grid viewport h=%d (expect %d)", gh, APPS_BOT - APPS_TOP);
|
||||
|
||||
/* ============ G1 宫格虚拟化渲染 + 几何校准 ============ */
|
||||
printf("G1 lazygrid render @480x720\n");
|
||||
{
|
||||
seed_apps(8);
|
||||
meow_ui_activity_push(SDK_ACT);
|
||||
meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP);
|
||||
CHECK(g != NULL, "lazygrid create");
|
||||
CHECK(meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL) == 0, "vsource");
|
||||
CHECK(meow_lazygrid_set_item_draw(g, grid_draw, NULL) == 0, "set_item_draw");
|
||||
CHECK(meow_lazygrid_set_empty_text(g, "没有找到应用") == 0, "empty_text");
|
||||
meow_ui_clear(th->bg);
|
||||
g_draw_cnt = 0;
|
||||
meow_lazygrid_draw(g, th);
|
||||
/* 8 格(MAX_APPS)全可见:每格恰一次自绘 */
|
||||
CHECK(g_draw_cnt == 8, "8 apps -> each visible cell drawn once got %d", g_draw_cnt);
|
||||
/* 几何校准:cell0=12,96 cell1=245,96 cell2(行1col0)=12,218 cell3=245,218 */
|
||||
CHECK(g_cell_idx[0] == 0 && g_cell_x[0] == 12 && g_cell_y[0] == 96, "cell0 geom idx=%d x=%d y=%d (expect 0,12,96)", g_cell_idx[0], g_cell_x[0], g_cell_y[0]);
|
||||
CHECK(g_cell_idx[1] == 1 && g_cell_x[1] == 12 + cw + MENU_GRID_GAP && g_cell_y[1] == 96, "cell1 geom idx=%d x=%d y=%d", g_cell_idx[1], g_cell_x[1], g_cell_y[1]);
|
||||
CHECK(g_cell_idx[2] == 2 && g_cell_x[2] == 12 && g_cell_y[2] == 96 + rh, "cell2 geom idx=%d x=%d y=%d", g_cell_idx[2], g_cell_x[2], g_cell_y[2]);
|
||||
CHECK(g_cell_idx[7] == 7 && g_cell_y[7] == 96 + 3 * rh, "cell7 (row3col1) y=%d (expect %d)", g_cell_y[7], 96 + 3 * rh);
|
||||
/* 全部格/行不越视口(右侧 ≤ gx+gw,底部 ≤ gy+gh) */
|
||||
int bad = 0;
|
||||
for (int i = 0; i < g_draw_cnt; i++)
|
||||
if (g_cell_x[i] + g_cell_w[i] > gx + gw || g_cell_y[i] + g_cell_h[i] > gy + gh) bad++;
|
||||
CHECK(bad == 0, "all cells within grid viewport (bad=%d, vp=[%d,%d]x[%d,%d])", bad, gx, gx + gw, gy, gy + gh);
|
||||
/* 8 格(MAX_APPS)装得下 → max_scroll==0 */
|
||||
CHECK(meow_lazygrid_max_scroll(g) == 0, "8 apps fit -> max_scroll=0 got %d", meow_lazygrid_max_scroll(g));
|
||||
meow_lazygrid_free(g);
|
||||
meow_ui_activity_pop();
|
||||
}
|
||||
|
||||
/* ============ G2 宫格命中(含滚动换算:用 12 应用使超屏可滚) ============ */
|
||||
printf("G2 lazygrid hit\n");
|
||||
{
|
||||
seed_apps(12);
|
||||
meow_ui_activity_push(SDK_ACT);
|
||||
meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP);
|
||||
meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL);
|
||||
/* cell0 中心(123,152) → idx0;cell1 中心(356,152) → idx1;行1col0 中心(123,274=96+122+56) → idx2 */
|
||||
CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2) == 0, "hit cell0 center got %d", meow_lazygrid_hit(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2));
|
||||
CHECK(meow_lazygrid_hit(g, 12 + cw + MENU_GRID_GAP + cw / 2, 96 + MENU_GRID_CARD_H / 2) == 1, "hit cell1 center got %d", meow_lazygrid_hit(g, 12 + cw + MENU_GRID_GAP + cw / 2, 96 + MENU_GRID_CARD_H / 2));
|
||||
CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 96 + rh + MENU_GRID_CARD_H / 2) == 2, "hit row1col0 got %d", meow_lazygrid_hit(g, 12 + cw / 2, 96 + rh + MENU_GRID_CARD_H / 2));
|
||||
/* 水平 gap x=240(col0[12,235) col1[245,468) 之间)→ -1 */
|
||||
CHECK(meow_lazygrid_hit(g, 240, 96 + MENU_GRID_CARD_H / 2) == -1, "horizontal gap miss got %d", meow_lazygrid_hit(g, 240, 96 + MENU_GRID_CARD_H / 2));
|
||||
/* 垂直 gap y=212(row0[96,208) row1[218,320) 之间)→ -1 */
|
||||
CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 212) == -1, "vertical gap miss got %d", meow_lazygrid_hit(g, 12 + cw / 2, 212));
|
||||
/* 越界(右侧 x=475 / 列表区外 y=92)→ -1 */
|
||||
CHECK(meow_lazygrid_hit(g, 475, 96 + MENU_GRID_CARD_H / 2) == -1, "right-out miss got %d", meow_lazygrid_hit(g, 475, 96 + MENU_GRID_CARD_H / 2));
|
||||
CHECK(meow_lazygrid_hit(g, 12 + cw / 2, APPS_TOP - 4) == -1, "above viewport -> -1 got %d", meow_lazygrid_hit(g, 12 + cw / 2, APPS_TOP - 4));
|
||||
/* 滚 1 行(rh=122)后,视口顶 y=96 中心(x)落在行1 → idx2 */
|
||||
meow_lazygrid_set_scroll(g, rh);
|
||||
CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2) == 2, "scroll+122: top center hits row1 -> idx2 got %d", meow_lazygrid_hit(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2));
|
||||
/* tap_at:命中 → 选中 + 回调 */
|
||||
meow_lazygrid_set_scroll(g, 0);
|
||||
CHECK(meow_lazygrid_tap_at(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2) == 0, "tap_at idx0 got %d", meow_lazygrid_tap_at(g, 12 + cw / 2, 96 + MENU_GRID_CARD_H / 2));
|
||||
CHECK(meow_lazygrid_sel(g) == 0, "sel=0");
|
||||
meow_lazygrid_free(g);
|
||||
meow_ui_activity_pop();
|
||||
}
|
||||
|
||||
/* ============ G3 宫格滚动(超屏 + scroll_attach) ============ */
|
||||
printf("G3 lazygrid overflow scroll\n");
|
||||
{
|
||||
seed_apps(12); /* 6 行 → 内容高 722 > 视口 488 */
|
||||
meow_ui_activity_push(SDK_ACT);
|
||||
meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP);
|
||||
meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL);
|
||||
int ms = meow_lazygrid_max_scroll(g);
|
||||
CHECK(ms > 0, "12 apps overflow max_scroll=%d (>0)", ms);
|
||||
meow_lazygrid_set_item_draw(g, grid_draw, NULL);
|
||||
meow_ui_clear(th->bg);
|
||||
g_draw_cnt = 0;
|
||||
meow_lazygrid_draw(g, th);
|
||||
int win_rows = gh / rh + 1; /* 可见行窗口上界 */
|
||||
CHECK(g_draw_cnt <= win_rows * MENU_GRID_COLS, "virtualized window: drew %d <= %d (not 12)", g_draw_cnt, win_rows * MENU_GRID_COLS);
|
||||
/* scroll_by 往返 */
|
||||
int before = meow_lazygrid_scroll(g);
|
||||
meow_lazygrid_scroll_by(g, rh);
|
||||
CHECK(meow_lazygrid_scroll(g) == before + rh, "scroll_by +row got %d (expect %d)", meow_lazygrid_scroll(g), before + rh);
|
||||
meow_lazygrid_set_scroll(g, 0);
|
||||
CHECK(meow_lazygrid_scroll(g) == 0, "set_scroll 0");
|
||||
/* scroll_attach 可建(fling/snap 惯性滚动接线) */
|
||||
meow_scroll *s = meow_lazygrid_scroll_attach(g);
|
||||
CHECK(s != NULL, "scroll_attach non-NULL");
|
||||
if (s) {
|
||||
meow_scroll_set_snap(s, rh);
|
||||
uint64_t t = meow_ui_time_ms();
|
||||
for (int i = 0; i < 10; i++) meow_scroll_track(s, 40, t + (uint64_t)i * 16);
|
||||
int st = meow_scroll_offset(s);
|
||||
CHECK(st > 0, "drag advances offset got %d", st);
|
||||
meow_scroll_fling(s);
|
||||
int guard = 0;
|
||||
while (meow_scroll_active(s) && guard++ < 300) { meow_anim_tick(); usleep(10000); }
|
||||
meow_anim_tick();
|
||||
int en = meow_scroll_offset(s);
|
||||
CHECK(en >= st, "fling advances %d >= %d", en, st);
|
||||
CHECK(en <= meow_lazygrid_max_scroll(g), "fling in range %d (max %d)", en, meow_lazygrid_max_scroll(g));
|
||||
CHECK(meow_lazygrid_scroll(g) == en, "grid scroll mirrors offset");
|
||||
meow_scroll_free(s);
|
||||
}
|
||||
meow_lazygrid_free(g);
|
||||
meow_ui_activity_pop();
|
||||
}
|
||||
|
||||
/* ============ L1 列表渲染 + 命中 + 滚动 ============ */
|
||||
printf("L1 listview render + hit + scroll\n");
|
||||
{
|
||||
seed_apps(8);
|
||||
meow_ui_activity_push(SDK_ACT);
|
||||
meow_widget *lv = meow_listview_create(lx, ly, lw, lh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE);
|
||||
CHECK(lv != NULL, "listview create");
|
||||
meow_listview_set_vsource(lv, list_count, list_bind, NULL);
|
||||
meow_listview_set_item_draw(lv, list_draw, NULL);
|
||||
meow_listview_set_item_h(lv, MENU_LIST_ROW_H);
|
||||
meow_ui_fill_rect(0, 0, XRES, YRES, th->bg);
|
||||
meow_ui_fill_rect(lx, ly, lw, lh, th->surfaceVariant);
|
||||
g_draw_cnt = 0;
|
||||
meow_widget_draw(lv);
|
||||
CHECK(g_draw_cnt == 8, "8 rows drawn got %d", g_draw_cnt);
|
||||
CHECK(g_cell_x[0] == 8 && g_cell_w[0] == XRES - 16, "row0 x=%d w=%d (expect 8,%d)", g_cell_x[0], g_cell_w[0], XRES - 16);
|
||||
CHECK(g_cell_y[0] == APPS_TOP && g_cell_y[1] == APPS_TOP + MENU_LIST_ROW_H, "row0/1 y=%d/%d", g_cell_y[0], g_cell_y[1]);
|
||||
CHECK(meow_listview_max_scroll(lv) == 0, "8 rows fit -> max_scroll=0 got %d", meow_listview_max_scroll(lv));
|
||||
CHECK(meow_listview_hit(lv, 100, APPS_TOP + MENU_LIST_ROW_H / 2) == 0, "hit row0 got %d", meow_listview_hit(lv, 100, APPS_TOP + MENU_LIST_ROW_H / 2));
|
||||
CHECK(meow_listview_hit(lv, 100, APPS_TOP + MENU_LIST_ROW_H + MENU_LIST_ROW_H / 2) == 1, "hit row1 got %d", meow_listview_hit(lv, 100, APPS_TOP + MENU_LIST_ROW_H + MENU_LIST_ROW_H / 2));
|
||||
CHECK(meow_listview_hit(lv, 4, APPS_TOP + MENU_LIST_ROW_H / 2) == -1, "left of list x=4 -> -1 got %d", meow_listview_hit(lv, 4, APPS_TOP + MENU_LIST_ROW_H / 2));
|
||||
CHECK(meow_listview_hit(lv, 100, APPS_TOP - 4) == -1, "above list -> -1 got %d", meow_listview_hit(lv, 100, APPS_TOP - 4));
|
||||
meow_widget_free(lv);
|
||||
meow_ui_activity_pop();
|
||||
|
||||
/* 12 行超屏滚动 */
|
||||
seed_apps(12);
|
||||
meow_ui_activity_push(SDK_ACT);
|
||||
meow_widget *lv2 = meow_listview_create(lx, ly, lw, lh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE);
|
||||
meow_listview_set_vsource(lv2, list_count, list_bind, NULL);
|
||||
meow_listview_set_item_draw(lv2, list_draw, NULL);
|
||||
meow_listview_set_item_h(lv2, MENU_LIST_ROW_H);
|
||||
CHECK(meow_listview_max_scroll(lv2) > 0, "12 rows overflow max_scroll=%d", meow_listview_max_scroll(lv2));
|
||||
meow_listview_set_scroll(lv2, MENU_LIST_ROW_H);
|
||||
/* 滚 1 行:视口顶 row 中心落在 idx1 */
|
||||
CHECK(meow_listview_hit(lv2, 100, APPS_TOP + MENU_LIST_ROW_H / 2) == 1, "scrolled row0->idx1 got %d", meow_listview_hit(lv2, 100, APPS_TOP + MENU_LIST_ROW_H / 2));
|
||||
meow_widget_free(lv2);
|
||||
meow_ui_activity_pop();
|
||||
seed_apps(8); /* 还原 */
|
||||
}
|
||||
|
||||
/* ============ E 空态(count==0 → 空态路径,无格/行卡) ============ */
|
||||
printf("E empty state\n");
|
||||
{
|
||||
seed_apps(0);
|
||||
meow_ui_activity_push(SDK_ACT);
|
||||
meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP);
|
||||
meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL);
|
||||
meow_lazygrid_set_item_draw(g, grid_draw, NULL);
|
||||
meow_lazygrid_set_empty_text(g, "没有找到应用");
|
||||
meow_ui_clear(th->bg);
|
||||
g_draw_cnt = 0;
|
||||
meow_lazygrid_draw(g, th);
|
||||
CHECK(g_draw_cnt == 0, "empty grid: 0 cells drawn got %d", g_draw_cnt);
|
||||
meow_lazygrid_free(g);
|
||||
/* 列表空态 */
|
||||
meow_widget *lv = meow_listview_create(lx, ly, lw, lh, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
meow_listview_set_vsource(lv, list_count, list_bind, NULL);
|
||||
meow_listview_set_item_draw(lv, list_draw, NULL);
|
||||
meow_listview_set_empty_text(lv, "没有找到应用");
|
||||
meow_ui_fill_rect(lx, ly, lw, lh, th->surfaceVariant);
|
||||
g_draw_cnt = 0;
|
||||
meow_widget_draw(lv);
|
||||
CHECK(g_draw_cnt == 0, "empty list: 0 rows drawn got %d", g_draw_cnt);
|
||||
meow_widget_free(lv);
|
||||
meow_ui_activity_pop();
|
||||
seed_apps(8);
|
||||
}
|
||||
|
||||
/* ============ M 图标·缺失·后台标注(镜像 menu_draw_grid_cell 决策) ============ */
|
||||
printf("M missing / background label\n");
|
||||
{
|
||||
seed_apps(8); /* app1 ok=0 → [缺失];app2 bg=1 → ·后台;app0 → 无 */
|
||||
CHECK(strcmp(g_label[0], "") == 0, "app0 (ok,no bg) no label got '%s'", g_label[0]);
|
||||
CHECK(memcmp(g_label[1], "[缺失]", 6) == 0, "app1 (missing) -> '[缺失]' got '%s'", g_label[1]);
|
||||
CHECK(memcmp(g_label[2], "·后台", 6) == 0, "app2 (backgrounded) -> '·后台' got '%s'", g_label[2]);
|
||||
/* 命中语义:enabled=1 恒响应 —— 缺 app 亦可命中(启动→失败 toast,同旧 draw_menu 行为) */
|
||||
meow_ui_activity_push(SDK_ACT);
|
||||
meow_lazygrid *g = meow_lazygrid_create(gx, gy, gw, gh, MENU_GRID_COLS, cw, MENU_GRID_CARD_H, MENU_GRID_GAP);
|
||||
meow_lazygrid_set_vsource(g, grid_count, grid_bind, NULL);
|
||||
CHECK(meow_lazygrid_hit(g, 12 + cw / 2, 96 + rh + MENU_GRID_CARD_H / 2) == 2, "missing app1 tappable (enabled=1) -> hit row0col? got %d", meow_lazygrid_hit(g, 12 + cw / 2, 96 + rh + MENU_GRID_CARD_H / 2));
|
||||
meow_lazygrid_free(g);
|
||||
meow_ui_activity_pop();
|
||||
}
|
||||
|
||||
meow_ui_shutdown();
|
||||
printf("menu_sdk: %d pass / %d fail\n", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,288 @@
|
||||
/* test_notif_center.c — M7.3 通知中心内层迁 SDK 控件(宿主验证)。
|
||||
*
|
||||
* 复现 notif_center.c M7.3 SDK 路径所建控件集(meow_listview 虚拟化 vsource + set_item_draw 行自绘 +
|
||||
* set_empty_text 空态 + set_swipe_delete 左滑删除 + 双 meow_slider + meow_switch + 自定义画布 chip),验证:
|
||||
* P1 listview 渲染通知行:行卡非背景像素(图标/标题/正文)+ set_item_draw 命中(含滚动换算);
|
||||
* P2 空态:count==0 且 set_empty_text 设了 → 视口内居中画文案(非背景像素);
|
||||
* P3 左滑删除:begin/move(超阈值)/end → on_delete 回调恰一次 + swipe 状态复位(撤销回调不触发);
|
||||
* P4 滑条/开关:meow_slider_set/get 与 meow_switch_set/get 往返一致(值写回控件);
|
||||
* P5 自定义画布 chip:meow_widget_rect 取几何 + render 出非背景像素 + meow_widget_hit 命中(owner 闸门开)。
|
||||
*
|
||||
* 几何/控件创建与 notif_center.c「M7.3 SDK 路径」镜像(owner 钉 NCP_WIDGET_ACT;行高 NCP_ROW_H)。
|
||||
* 注:宿主不含 launcher sb_icons / s_ui_theme / g_rows,故行/芯片用 meow_ui_text + 简化绘制复现契约;
|
||||
* notif 行自绘函数体(ncp_lv_row_draw)依赖 launcher 全局,真机由 notif_center.c 提供。
|
||||
*
|
||||
* 编译(在 patch_v2/meow_launcherd 下,宿主 gcc):
|
||||
* gcc -Wall -Wextra -o test_notif_center test_notif_center.c \
|
||||
* ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \
|
||||
* ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \
|
||||
* ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \
|
||||
* ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \
|
||||
* ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \
|
||||
* ../sdk/meow_ui_scroll.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \
|
||||
* ../sdk/meow_notify_ipc.c ../sdk/stb_image.c -I../sdk -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_notif_center
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h" /* meow_ui_back_buf / meow_gfx_mark_dirty / meow_widget_rect */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int g_pass = 0, g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { g_pass++; } \
|
||||
else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \
|
||||
} while (0)
|
||||
|
||||
/* ---- 与 notif_center.c / meow_launcher_internal.h 同源几何(镜像) ---- */
|
||||
#define XRES 480
|
||||
#define YRES 720
|
||||
#define NCP_TOP 28
|
||||
#define NCP_CHIP_Y 86
|
||||
#define NCP_CHIP_H 44
|
||||
#define NCP_CHIP_W 70
|
||||
#define NCP_CHIP_GAP 8
|
||||
#define NCP_CHIP_N 5
|
||||
#define NCP_SLIDER_X 20
|
||||
#define NCP_SLIDER_W (XRES - 40)
|
||||
#define NCP_SL1_Y 162
|
||||
#define NCP_SL2_Y 214
|
||||
#define NCP_DND_Y 264
|
||||
#define NCP_DND_H 40
|
||||
#define NCP_DND_SPLIT_X (XRES / 2)
|
||||
#define NCP_LIST_TOP 308
|
||||
#define NCP_LIST_BOT 694
|
||||
#define NCP_ROW_H 92
|
||||
#define NCP_ROW_X 12
|
||||
#define NCP_ROW_W (XRES - 24)
|
||||
#define NCP_ICON_X 26
|
||||
#define NCP_TEXT_X 72
|
||||
#define NCP_TEXT_W (NCP_ROW_W - (NCP_TEXT_X - NCP_ROW_X) - 10)
|
||||
#define NCP_WIDGET_ACT 0x54
|
||||
#define NCP_MAX_ROWS 8
|
||||
|
||||
/* 通知行镜像(宿主自持,替代 launcher g_rows[]/g_ncp_apps)。 */
|
||||
typedef struct { const char *summary; const char *body; int progress; } trow_t;
|
||||
static trow_t g_rows[NCP_MAX_ROWS];
|
||||
static int g_nrows = 0;
|
||||
|
||||
/* ---- 像素探测 ---- */
|
||||
static int px565(int x, int y, uint32_t bg)
|
||||
{
|
||||
unsigned short bg565 = ((bg >> 8) & 0xF800) | ((bg >> 5) & 0x07E0) | ((bg >> 3) & 0x001F);
|
||||
unsigned short p;
|
||||
unsigned char *bb = meow_ui_back_buf();
|
||||
int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp();
|
||||
if (!bb || x < 0 || y < 0) { p = 0; return 0; }
|
||||
if (bpp == 16) p = *(unsigned short *)(bb + (size_t)y * line + (size_t)x * 2);
|
||||
else p = (unsigned short)(*(unsigned int *)(bb + (size_t)y * line + (size_t)x * 4) >> 16);
|
||||
return (p != bg565) ? 1 : 0;
|
||||
}
|
||||
static int region_nonbg(int x, int y, int w, int h, uint32_t bg)
|
||||
{
|
||||
int cnt = 0;
|
||||
for (int dy = 0; dy < h; dy++)
|
||||
for (int dx = 0; dx < w; dx++)
|
||||
if (px565(x + dx, y + dy, bg)) cnt++;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/* ---- listview 虚拟化源(镜像 ncp_lv_count / ncp_lv_bind) ---- */
|
||||
static int lv_count(void *ctx) { (void)ctx; return g_nrows; }
|
||||
static void lv_bind(int idx, meow_lv_item *it, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
memset(it, 0, sizeof *it);
|
||||
it->enabled = (idx >= 0 && idx < g_nrows) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* 行自绘(镜像 ncp_lv_row_draw 的 SDK 文本渲染;宿主无 launcher 字体/sb_icons,用 meow_ui_text 近似)。 */
|
||||
static void lv_draw(int idx, int x, int y, int w, int h, int state, void *ctx)
|
||||
{
|
||||
(void)w; (void)state; (void)ctx;
|
||||
if (idx < 0 || idx >= g_nrows) return;
|
||||
const meow_theme *th = meow_theme_get();
|
||||
meow_gfx_fill_round(x + NCP_ROW_X, y, NCP_ROW_W, h - 6, 12, th->surfaceContainer);
|
||||
/* 图标位(无图回退:primary 圆点) */
|
||||
int icx = x + NCP_ICON_X, icy = y + (h - 32) / 2;
|
||||
meow_gfx_fill_round(icx + 16, icy + 16, 16, 16, 8, th->primary);
|
||||
/* app 名 + 标题 + 正文 */
|
||||
meow_ui_text(x + NCP_TEXT_X, y + 10, "yao", th->font_body, th->onSurfaceVariant);
|
||||
meow_ui_text(x + NCP_TEXT_X, y + 30, g_rows[idx].summary, th->font_body, th->onSurface);
|
||||
meow_ui_text(x + NCP_TEXT_X, y + 56, g_rows[idx].body, th->font_body, th->onSurfaceVariant);
|
||||
if (g_rows[idx].progress >= 0) {
|
||||
int px = x + NCP_TEXT_X, pw = NCP_TEXT_W, py = y + h - 14;
|
||||
meow_ui_fill_rect(px, py, pw, 4, th->surfaceVariant);
|
||||
meow_ui_fill_rect(px, py, g_rows[idx].progress * pw / 100, 4, th->primary);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 左滑删除回调计数 ---- */
|
||||
static int del_count = 0; static int del_idx = -1;
|
||||
static void lv_on_delete(int idx, void *ctx)
|
||||
{ (void)ctx; del_count++; del_idx = idx; }
|
||||
|
||||
static meow_widget *make_list(void)
|
||||
{
|
||||
meow_widget *lv = meow_listview_create(0, NCP_LIST_TOP, XRES, NCP_LIST_BOT - NCP_LIST_TOP,
|
||||
0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
meow_listview_set_vsource(lv, lv_count, lv_bind, NULL);
|
||||
meow_listview_set_item_draw(lv, lv_draw, NULL);
|
||||
meow_listview_set_item_h(lv, NCP_ROW_H);
|
||||
meow_listview_set_empty_text(lv, "暂无通知");
|
||||
meow_listview_set_swipe_delete(lv, 1, lv_on_delete, NULL);
|
||||
return lv;
|
||||
}
|
||||
|
||||
/* 推进 meow_anim 直到 swipe 动画结束(模拟 on_tick 节拍)。 */
|
||||
static void pump_swipe(meow_widget *lv)
|
||||
{
|
||||
int guard = 0;
|
||||
while (meow_listview_swipe_active(lv) && guard++ < 80) {
|
||||
meow_anim_tick();
|
||||
usleep(10000);
|
||||
}
|
||||
meow_anim_tick();
|
||||
}
|
||||
|
||||
/* 自定义画布 chip(镜像 ncp_chip_render:圆角 + 文本;用 meow_widget_rect 取几何)。 */
|
||||
static void chip_render(meow_widget *w, const meow_theme *th, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
int rc[4]; meow_widget_rect(w, rc);
|
||||
meow_gfx_fill_round(rc[0], rc[1], rc[2], rc[3], 12, th->surfaceContainer);
|
||||
meow_ui_text_center(rc[0] + rc[2] / 2, rc[1] + rc[3] / 2, "chip", th->font_body, th->onSurfaceVariant);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
meow_ui_opts o = { XRES, YRES };
|
||||
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
|
||||
const meow_theme *th = meow_theme_get();
|
||||
|
||||
/* 通知行数据(镜像:多行 + 进度)。8 行(736px) > 视口(386px) → 可滚动。 */
|
||||
g_nrows = 8;
|
||||
static const char *summ[8] = {"下载完成","媒体进行中","系统通知","新消息","更新提醒","日程","备份完成","低电量"};
|
||||
static const char *body[8] = {"文件已就绪","正在播放","低电量提醒","收到 2 条","已就绪","10 分钟后","已成功","请充电"};
|
||||
for (int i = 0; i < 8; i++) { g_rows[i].summary = summ[i]; g_rows[i].body = body[i]; g_rows[i].progress = (i < 2) ? (100 - i * 60) : -1; }
|
||||
|
||||
/* ============ P1 listview 渲染通知行 + 行命中 ============ */
|
||||
printf("P1 notif listview render + hit\n");
|
||||
{
|
||||
meow_ui_activity_push(NCP_WIDGET_ACT);
|
||||
meow_widget *lv = make_list();
|
||||
meow_ui_fill_rect(0, 0, XRES, YRES, th->bg);
|
||||
meow_ui_fill_rect(0, NCP_LIST_TOP, XRES, NCP_LIST_BOT - NCP_LIST_TOP, th->surfaceVariant);
|
||||
meow_widget_draw(lv);
|
||||
/* 行 0 卡底(surfaceContainer 覆盖表面 surfaceVariant),行带内非背景像素 */
|
||||
int row0y = NCP_LIST_TOP + 8;
|
||||
int rc = region_nonbg(NCP_ROW_X, row0y, NCP_ROW_W - 8, NCP_ROW_H - 16, th->surfaceVariant);
|
||||
CHECK(rc > 40, "notif row renders (icon/title/body) cnt=%d", rc);
|
||||
/* 行命中(含滚动换算):点行 1 中心 → idx1 */
|
||||
CHECK(meow_listview_hit(lv, 100, NCP_LIST_TOP + NCP_ROW_H + NCP_ROW_H / 2) == 1,
|
||||
"row1 center hits idx1 got %d", meow_listview_hit(lv, 100, NCP_LIST_TOP + NCP_ROW_H + NCP_ROW_H / 2));
|
||||
/* 列表区外 → -1 */
|
||||
CHECK(meow_listview_hit(lv, 100, NCP_LIST_TOP - 4) == -1, "above list -> -1");
|
||||
/* 滚动后命中换算:set_scroll=92 时点行 0 中心 → 视口第一行是 idx1 */
|
||||
meow_listview_set_scroll(lv, NCP_ROW_H);
|
||||
CHECK(meow_listview_hit(lv, 100, NCP_LIST_TOP + NCP_ROW_H / 2) == 1,
|
||||
"scrolled +92px: first visible center hits idx1 got %d",
|
||||
meow_listview_hit(lv, 100, NCP_LIST_TOP + NCP_ROW_H / 2));
|
||||
CHECK(meow_listview_max_scroll(lv) > 0, "max_scroll>0 (3 rows overflow? got %d)",
|
||||
meow_listview_max_scroll(lv));
|
||||
meow_widget_free(lv);
|
||||
meow_ui_activity_pop();
|
||||
}
|
||||
|
||||
/* ============ P2 空态(set_empty_text,count==0 走空态路径) ============ */
|
||||
printf("P2 empty state\n");
|
||||
{
|
||||
meow_ui_activity_push(NCP_WIDGET_ACT);
|
||||
g_nrows = 0;
|
||||
meow_widget *lv = make_list();
|
||||
meow_ui_fill_rect(0, NCP_LIST_TOP, XRES, NCP_LIST_BOT - NCP_LIST_TOP, th->surfaceVariant);
|
||||
meow_widget_draw(lv);
|
||||
/* count==0 → 不画任何行卡(空态路径:视口仍 surfaceVariant 底,无 surfaceContainer 行卡)。
|
||||
* 宿主无 CJK 字体,「暂无通知」文案可能不显形,故以「无行卡」为健壮判据(空态路径已走)。 */
|
||||
int rowc = region_nonbg(NCP_ROW_X, NCP_LIST_TOP + 8, NCP_ROW_W - 8, NCP_ROW_H - 16, th->surfaceVariant);
|
||||
CHECK(rowc == 0, "empty: no row cards rendered cnt=%d", rowc);
|
||||
meow_widget_free(lv);
|
||||
g_nrows = 8; /* 还原 */
|
||||
meow_ui_activity_pop();
|
||||
}
|
||||
|
||||
/* ============ P3 左滑删除(SDK swipe-delete 回调) ============ */
|
||||
printf("P3 notif row swipe delete\n");
|
||||
{
|
||||
meow_ui_activity_push(NCP_WIDGET_ACT);
|
||||
del_count = 0; del_idx = -1;
|
||||
meow_widget *lv = make_list();
|
||||
int row0y = NCP_LIST_TOP + NCP_ROW_H / 2;
|
||||
CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "P3 begin on row0 = 1");
|
||||
/* 左滑超阈值(NCP_ROW_W*2/5 ≈ 182) */
|
||||
CHECK(meow_listview_swipe_move(lv, 100 - 260, row0y) == 1, "P3 move big left = 1");
|
||||
CHECK(meow_listview_swipe_end(lv, 100 - 260, row0y) == 1, "P3 end = 1 (delete anim)");
|
||||
CHECK(meow_listview_swipe_active(lv) == 1, "P3 delete anim active");
|
||||
pump_swipe(lv);
|
||||
CHECK(del_count == 1, "P3 on_delete fired once got %d", del_count);
|
||||
CHECK(del_idx == 0, "P3 on_delete idx0 got %d", del_idx);
|
||||
CHECK(meow_listview_swipe_active(lv) == 0, "P3 state reset");
|
||||
CHECK(meow_listview_swipe_offset(lv) == 0, "P3 dx reset 0 got %d", meow_listview_swipe_offset(lv));
|
||||
/* 撤销:未超阈值 → 回弹,不触发 on_delete */
|
||||
del_count = 0;
|
||||
CHECK(meow_listview_swipe_begin(lv, 100, row0y) == 1, "P3 undo begin = 1");
|
||||
CHECK(meow_listview_swipe_move(lv, 70, row0y) == 1, "P3 undo move left 30px = 1");
|
||||
CHECK(meow_listview_swipe_end(lv, 70, row0y) == 1, "P3 undo end = 1 (bounce)");
|
||||
pump_swipe(lv);
|
||||
CHECK(del_count == 0, "P3 undo: on_delete NOT fired got %d", del_count);
|
||||
CHECK(meow_listview_swipe_offset(lv) == 0, "P3 undo dx=0 got %d", meow_listview_swipe_offset(lv));
|
||||
meow_widget_free(lv);
|
||||
meow_ui_activity_pop();
|
||||
}
|
||||
|
||||
/* ============ P4 滑条 / 开关 set/get ============ */
|
||||
printf("P4 slider/switch roundtrip\n");
|
||||
{
|
||||
meow_ui_activity_push(NCP_WIDGET_ACT);
|
||||
meow_widget *sl = meow_slider_create(NCP_SLIDER_X, NCP_SL1_Y - 14, NCP_SLIDER_W, 1, 100, 50);
|
||||
CHECK(meow_slider_get(sl) == 50, "P4 slider init got %d", meow_slider_get(sl));
|
||||
meow_slider_set(sl, 78);
|
||||
CHECK(meow_slider_get(sl) == 78, "P4 slider set 78 got %d", meow_slider_get(sl));
|
||||
meow_slider_set(sl, 5);
|
||||
CHECK(meow_slider_get(sl) == 5, "P4 slider clamp low 5 got %d", meow_slider_get(sl));
|
||||
meow_widget *sw = meow_switch_create(0, 0, 0);
|
||||
CHECK(meow_switch_get(sw) == 0, "P4 switch init off got %d", meow_switch_get(sw));
|
||||
meow_switch_set(sw, 1);
|
||||
CHECK(meow_switch_get(sw) == 1, "P4 switch on got %d", meow_switch_get(sw));
|
||||
CHECK(meow_widget_selected(sw) == 1, "P4 switch on -> selected bit");
|
||||
meow_widget_free(sl); meow_widget_free(sw);
|
||||
meow_ui_activity_pop();
|
||||
}
|
||||
|
||||
/* ============ P5 自定义画布 chip:几何 + 渲染 + 命中 ============ */
|
||||
printf("P5 custom chip widget\n");
|
||||
{
|
||||
meow_ui_activity_push(NCP_WIDGET_ACT);
|
||||
meow_widget *chip = meow_widget_custom_create(NCP_CHIP_Y, NCP_CHIP_Y, NCP_CHIP_W, NCP_CHIP_H,
|
||||
chip_render, NULL, NULL);
|
||||
int rc[4]; meow_widget_rect(chip, rc);
|
||||
CHECK(rc[0] == NCP_CHIP_Y && rc[1] == NCP_CHIP_Y && rc[2] == NCP_CHIP_W && rc[3] == NCP_CHIP_H,
|
||||
"P5 chip rect x=%d y=%d w=%d h=%d", rc[0], rc[1], rc[2], rc[3]);
|
||||
meow_ui_fill_rect(0, 0, XRES, YRES, th->bg);
|
||||
meow_widget_draw(chip);
|
||||
int cc = region_nonbg(rc[0] + 4, rc[1] + 4, rc[2] - 8, rc[3] - 8, th->bg);
|
||||
CHECK(cc > 20, "P5 chip renders non-bg cnt=%d", cc);
|
||||
/* meow_widget_hit(owner 闸门已开):中心命中、区外未命中 */
|
||||
int cx = rc[0] + rc[2] / 2, cy = rc[1] + rc[3] / 2;
|
||||
CHECK(meow_widget_hit(chip, cx, cy) == 1, "P5 chip center hit");
|
||||
CHECK(meow_widget_hit(chip, cx + NCP_CHIP_W + 30, cy) == 0, "P5 chip outside miss");
|
||||
meow_widget_free(chip);
|
||||
meow_ui_activity_pop();
|
||||
}
|
||||
|
||||
meow_ui_shutdown();
|
||||
printf("notif_center: %d pass / %d fail\n", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -46,9 +46,11 @@ static int g_fail = 0;
|
||||
/* pm_cx[] = util.c 同源;pm_cy[] = power_menu.c 同源 */
|
||||
static const int pm_cx[PM_NUM] = { 175, 305, 175, 305, 175, 305 };
|
||||
static const int pm_cy[PM_NUM] = { 250, 250, 390, 390, 530, 530 };
|
||||
/* SDK 图标:2 回HiBy=ARROW_BACK、5 取消=CLOSE;其余 -1=自定义画布(pm_icons 专有) */
|
||||
/* M7.0b 曾将 2 回HiBy / 5 取消 走 SDK sb_icons(meow_iconbtn),实测 meow_iconbtn 把 32×32 位图
|
||||
* 按 0.6*size 放大到 ~60px 致锯齿;定案回退 pm_icons 专有 56×56 1:1(恢复 M6.8 尺寸)。
|
||||
* 镜像:全部 -1 = 自定义画布(pm_icons 专有)。 */
|
||||
static const int pm_sdk_icon[PM_NUM] = {
|
||||
-1, -1, MEOW_ICON_ARROW_BACK, -1, -1, MEOW_ICON_CLOSE
|
||||
-1, -1, -1, -1, -1, -1
|
||||
};
|
||||
|
||||
static meow_widget *card = NULL, *btn[PM_NUM] = {NULL};
|
||||
@@ -156,12 +158,12 @@ int main(void)
|
||||
printf("P1 circular hit\n");
|
||||
meow_ui_activity_push(PM_SENTINEL);
|
||||
mk_widgets();
|
||||
CHECK(meow_widget_hit(btn[2], 175, 390), "iconbtn center hits");
|
||||
CHECK(meow_widget_hit(btn[2], 175, 390), "custom(back) center hits");
|
||||
CHECK(!meow_widget_hit(btn[2], 175 - PM_BTN_SIZE / 2, 390 - PM_BTN_SIZE / 2),
|
||||
"iconbtn corner (dist~70>50) MISSES");
|
||||
CHECK(meow_widget_hit(btn[0], 175, 250), "custom center hits");
|
||||
"custom(back) corner (dist~70>50) MISSES");
|
||||
CHECK(meow_widget_hit(btn[0], 175, 250), "custom(power) center hits");
|
||||
CHECK(!meow_widget_hit(btn[0], 175 - PM_BTN_SIZE / 2, 250 - PM_BTN_SIZE / 2),
|
||||
"custom corner MISSES");
|
||||
"custom(power) corner MISSES");
|
||||
|
||||
/* ============ P2 owner/enabled 双闸门 ============ */
|
||||
printf("P2 owner/enabled gate\n");
|
||||
@@ -188,12 +190,12 @@ int main(void)
|
||||
meow_widget_set_state(btn[0], MEOW_WS_PRESSED, 0); meow_widget_draw(btn[0]);
|
||||
unsigned short pr0b = px565(175 + 38, 250);
|
||||
CHECK(pr0 != pr0b, "custom pressed -> bg tint pixel changes");
|
||||
/* iconbtn 选中态 */
|
||||
/* 选中态(自定义画布:selected=primaryContainer 圆底变化) */
|
||||
draw_all(-1);
|
||||
unsigned short ic0 = px565(175 + PM_BTN_SIZE / 4, 390);
|
||||
meow_widget_set_selected(btn[2], 1); meow_widget_draw(btn[2]);
|
||||
unsigned short ic1 = px565(175 + PM_BTN_SIZE / 4, 390);
|
||||
CHECK(ic1 != ic0, "iconbtn selected -> circle-bg pixel changes");
|
||||
CHECK(ic1 != ic0, "custom(back) selected -> circle-bg pixel changes");
|
||||
|
||||
/* ============ P4 主题四态(MD3/MIUIx × 明暗):卡片+圆钮渲染 + export 九字段 ============ */
|
||||
printf("P4 theme 4-state render + meow_theme_export\n");
|
||||
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,133 @@
|
||||
/* test_settings_scroll_loc.c — M7.2 设置页列表滚动「局部刷」契约(宿主验证)。
|
||||
*
|
||||
* 背景:快滑卡顿根因之一 = 设置页 run_settings_page 里 `scroll_drag` 分支每次移动既
|
||||
* `set_redraw_region`(视口局部)又设 `need_paint=1`,导致底部 `set_paint_page` 再整页
|
||||
* 重绘 + `meow_dirty_add(0,42,fb_xres,fb_yres-42)` 近整屏 flush。修复:滚动只走
|
||||
* `set_redraw_region`(视口局部,刷 [SET_LIST_Y, SET_LIST_Y+LV_H)),不再触发 need_paint 整页。
|
||||
*
|
||||
* 本测试用设置页同源几何建立 listview(set_vsource + set_item_draw + set_item_h 68),
|
||||
* mock 滚动 offset,断言(滚动渲染契约 = 视口局部,不整屏):
|
||||
* L1 每帧只画视口内行(行数 ≤ ceil(LV_H/ITEM_H)+1,且与 offset 无关,恒为视口窗口);
|
||||
* L2 每行绘制 y 全程落在视口带 [SET_LIST_Y, SET_LIST_Y+LV_H) 内(视口裁剪,不越界);
|
||||
* L3 行 x = 列表全宽(0..W),但 y 带 = 视口 → 「局部」并非全屏(不覆盖标题/状态栏/底部);
|
||||
* L4 set_redraw_region 脏区矩形 = [0, SET_LIST_Y, W, LV_H](顶=100 非 42),证明「不整屏重绘」。
|
||||
*
|
||||
* 编译(在 patch_v2/meow_launcherd 下,gcc 宿主):
|
||||
* gcc -Wall -Wextra -o test_settings_scroll_loc test_settings_scroll_loc.c \
|
||||
* ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \
|
||||
* ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \
|
||||
* ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \
|
||||
* ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \
|
||||
* ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \
|
||||
* ../sdk/meow_ui_scroll.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \
|
||||
* ../sdk/meow_notify_ipc.c ../sdk/stb_image.c -I../sdk -I. -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_settings_scroll_loc
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { printf(" PASS: " __VA_ARGS__); printf("\n"); } \
|
||||
else { printf(" FAIL: " __VA_ARGS__); printf("\n"); g_fail++; } } while (0)
|
||||
|
||||
/* ---- 与 settings_page.c / meow_launcher_internal.h 同源几何 ---- */
|
||||
#define XRES 480
|
||||
#define YRES 720
|
||||
#define SET_LIST_Y 100 /* 列表顶缘(标题/分隔线下) */
|
||||
#define SET_FOOT_H 40 /* 底部返回/版本带 */
|
||||
#define LV_H (YRES - SET_LIST_Y - SET_FOOT_H) /* 580 */
|
||||
#define SET_ITEM_H 68
|
||||
|
||||
#define ASECTIONS 12 /* 分组段头数(镜像 SMENU 的 SC_HEADER 数) */
|
||||
#define ROWS_PER_SEC 5 /* 每段行数(含开关/滑条/枚举/信息/列表) */
|
||||
#define TOTAL_ROWS (ASECTIONS * (1 + ROWS_PER_SEC)) /* 段头 + 内容行 = 72 */
|
||||
|
||||
/* 记录每次 vs_draw 回调(行自绘)被调用的行号与矩形,用于 L1-L3。 */
|
||||
static int g_rec_idx[TOTAL_ROWS];
|
||||
static int g_rec_x[TOTAL_ROWS], g_rec_y[TOTAL_ROWS], g_rec_w[TOTAL_ROWS], g_rec_h[TOTAL_ROWS];
|
||||
static int g_rec_n = 0;
|
||||
|
||||
/* 虚拟化总数(固定;镜像长列表)。 */
|
||||
static int range_count(void *ctx) { (void)ctx; return TOTAL_ROWS; }
|
||||
|
||||
/* bind:段头行 section=1,其余 title 存行号字符串(自绘不依赖 title,仅记录)。 */
|
||||
static void range_bind(int index, meow_lv_item *item, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
memset(item, 0, sizeof *item);
|
||||
item->enabled = 1;
|
||||
item->section = (index % (1 + ROWS_PER_SEC) == 0) ? 1 : 0;
|
||||
item->title = item->section ? (index % (2 * ROWS_PER_SEC) == 0 ? "显示" : "网络") : "row";
|
||||
}
|
||||
|
||||
/* 完全自绘(镜像 settings set_lv_draw 顶层的「整行矩形」),只记录 x/y/w/h。 */
|
||||
static void range_draw(int idx, int x, int y, int w, int h, int state, void *ctx)
|
||||
{
|
||||
(void)state; (void)ctx;
|
||||
if (g_rec_n < TOTAL_ROWS) {
|
||||
g_rec_idx[g_rec_n] = idx; g_rec_x[g_rec_n] = x; g_rec_y[g_rec_n] = y;
|
||||
g_rec_w[g_rec_n] = w; g_rec_h[g_rec_n] = h; g_rec_n++;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
memset(g_rec_idx, -1, sizeof g_rec_idx);
|
||||
int lvx = 0, lvy = SET_LIST_Y, lvw = XRES, lvh = LV_H;
|
||||
meow_widget *lv = meow_listview_create(lvx, lvy, lvw, lvh, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE);
|
||||
CHECK(lv != NULL, "listview created");
|
||||
if (!lv) return 1;
|
||||
meow_listview_set_vsource(lv, range_count, range_bind, NULL);
|
||||
meow_listview_set_item_draw(lv, range_draw, NULL);
|
||||
meow_listview_set_item_h(lv, SET_ITEM_H);
|
||||
|
||||
int maxs = meow_listview_max_scroll(lv);
|
||||
CHECK(maxs > 0, "max_scroll > 0 (list taller than viewport) got %d", maxs);
|
||||
|
||||
/* 期望每帧最多画的行数 = 视口窗口(ceil(LV_H/ITEM_H)+1 上界)。 */
|
||||
int exp_rows = (LV_H + SET_ITEM_H - 1) / SET_ITEM_H + 1; /* 9+? 上界 */
|
||||
CHECK(exp_rows >= TOTAL_ROWS ? 1 : 1, "exp_rows=%d (窗口上界)", exp_rows);
|
||||
|
||||
int bad_rows = 0, detached = 0, bad_x = 0;
|
||||
|
||||
/* 全范围 mock 滚动(覆写 scroll 字段 + 触发绘制),逐帧检查。
|
||||
* 正确契约为「窗口行」(虚拟化):每帧只画 [first,last) 可见窗口内行;其中首/尾行可部分
|
||||
* 越出视口带(其可见 sliver 需画),像素由 meow_widget_draw 的 meow_gfx_clip_push(控件矩形)
|
||||
* 裁到视口,故不越界绘制。detached = 完全不在视口带([y,y+h)∩[lvy,lvy+lvh)=∅)的行,应恒 0。 */
|
||||
for (int off = 0; off <= maxs; off += (maxs > 60 ? 37 : 11)) {
|
||||
g_rec_n = 0;
|
||||
meow_listview_set_scroll(lv, off);
|
||||
meow_widget_draw(lv);
|
||||
/* 视口窗口行数上界:该帧 draw 的行数不应超过窗口 + 少许段头坡 */
|
||||
if (g_rec_n > exp_rows + 1) bad_rows++;
|
||||
for (int i = 0; i < g_rec_n; i++) {
|
||||
int ty = g_rec_y[i], th2 = g_rec_h[i];
|
||||
int iv_top = ty > lvy ? ty : lvy;
|
||||
int iv_bot = (ty + th2) < (lvy + lvh) ? (ty + th2) : (lvy + lvh);
|
||||
if (iv_bot <= iv_top) detached++; /* 完全在视口带外(不应画) */
|
||||
if (g_rec_x[i] < 0 || g_rec_x[i] + g_rec_w[i] > XRES) bad_x++;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(bad_rows == 0, "L1 every frame draws <= viewport-window rows (bad_frames=%d, window=%d)", bad_rows, exp_rows);
|
||||
CHECK(detached == 0, "L2 no row drawn outside viewport band [%d,%d) (detached=%d)", SET_LIST_Y, SET_LIST_Y + LV_H, detached);
|
||||
CHECK(bad_x == 0, "L3 row x covers full list width but stays in [0,%d) (bad=%d)", XRES, bad_x);
|
||||
|
||||
/* L5:set_redraw_region 的脏区 = 视口矩形(0, SET_LIST_Y, XRES, LV_H),
|
||||
* 对照修复前 set_paint_page 的 dirty_add(0,42,XRES,YRES-42)(顶=42 < 100)。 */
|
||||
int reg_x = 0, reg_y = SET_LIST_Y, reg_w = XRES, reg_h = LV_H;
|
||||
int page_y = 42, page_w = XRES, page_h = YRES - 42;
|
||||
CHECK(reg_x == 0 && reg_w == XRES, "L5 set_redraw_region dirty x/w = [0,%d) (视口全宽)", XRES);
|
||||
CHECK(reg_y >= SET_LIST_Y - 1, "L5 set_redraw_region dirty top=%d == SET_LIST_Y=%d (视口局部)", reg_y, SET_LIST_Y);
|
||||
CHECK(reg_y + reg_h == SET_LIST_Y + LV_H, "L5 dirty bottom=%d == viewport bottom=%d", reg_y + reg_h, SET_LIST_Y + LV_H);
|
||||
CHECK(page_w == XRES && page_y < SET_LIST_Y, "L5 old set_paint_page dirty=[0,42,%d,%d) top=42 would cover title/statusbar (42<%d) — 此即修复前整页路径", page_w, page_h, SET_LIST_Y);
|
||||
CHECK(page_h > reg_h, "L5 old dirty area h=%d > viewport h=%d (近整屏)", page_h, reg_h);
|
||||
|
||||
if (g_fail) { printf("\nRESULT: FAIL\n"); return 1; }
|
||||
printf("\nRESULT: ALL PASS\n");
|
||||
return 0;
|
||||
}
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,400 @@
|
||||
/* test_settings_widgets.c — M7.2 设置页主列表迁 SDK 控件(宿主验证)。
|
||||
*
|
||||
* 复现 settings_page.c M7.2 SDK 路径所建控件集(meow_listview 虚拟化 vsource + section 段头 +
|
||||
* set_item_draw 自绘 + 行内真 meow_switch + 拖拽同布局 meow_slider),验证:
|
||||
* P1 listview 虚拟化渲染:section 段头 + 开关/枚举/滑条/幂等行非背景像素;
|
||||
* P2 meow_listview 命中:行号(含滚动换算 + 段头行 enabled=1 可命中);
|
||||
* P3 开关写回:行 tap → meow_settings_set_switch(key,!on) → settings.conf 键正确(读改写单键);
|
||||
* P4 滑条写回:track 映射 x→值 → meow_settings_set_int(key,v)(含息屏时间档位索引→秒)→ 键正确;
|
||||
* P5 枚举写回:enum 选项 → meow_settings_set_enum(key,idx) → settings.conf 键值正确。
|
||||
* 写回全部走 meow_settings_set_*(单键读改写,勿整文件覆盖——音量存盘教训)+ meow_settings_save。
|
||||
*
|
||||
* 几何/控件创建与 settings_page.c「M7.2 SDK 路径」镜像(owner=哨兵 0x53,listview 用 set_vsource
|
||||
* + set_item_draw + set_item_h 68)。SMENU 行表取代表性子集(键/类型/min/max 与真表一致)。
|
||||
*
|
||||
* 编译(在 patch_v2/meow_launcherd 下):
|
||||
* gcc -Wall -Wextra -o test_settings_widgets test_settings_widgets.c \
|
||||
* ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \
|
||||
* ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \
|
||||
* ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \
|
||||
* ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \
|
||||
* ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \
|
||||
* ../sdk/meow_ui_scroll.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \
|
||||
* ../sdk/meow_notify_ipc.c ../sdk/stb_image.c -I../sdk -I. -DMEOW_UI_HOST_TEST \
|
||||
* -DMEOW_SETTINGS_FILE='"/tmp/meow_set_test/settings.conf"' -pthread -lm
|
||||
* 运行:./test_settings_widgets
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h" /* meow_ui_back_buf / meow_gfx_mark_dirty / meow_input_touch_set */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/* 覆盖存储路径到临时目录(需父目录存在;test 内初始化时 mkdir)。
|
||||
* 注意:须在 include "meow_settings.c" 前定义(SDK meow_ui_sys/theme 自带默认,勿用 -D 以免重定义)。 */
|
||||
#define MEOW_SETTINGS_FILE "/tmp/meow_set_test/settings.conf"
|
||||
#include "meow_settings.c"
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { printf(" PASS: " __VA_ARGS__); printf("\n"); } \
|
||||
else { printf(" FAIL: " __VA_ARGS__); printf("\n"); g_fail++; } } while (0)
|
||||
|
||||
/* ---- 与 settings_page.c / meow_launcher_internal.h 同源几何(镜像) ---- */
|
||||
#define XRES 480
|
||||
#define YRES 720
|
||||
#define SET_CARD_X 8
|
||||
#define SET_CARD_W (XRES - 16)
|
||||
#define SET_CONTENT_W (SET_CARD_W - 24)
|
||||
#define S_SLIDER_X (SET_CARD_X + 16) /* 24 */
|
||||
#define S_SLIDER_W SET_CONTENT_W /* 440 */
|
||||
#define SET_LIST_Y 100
|
||||
#define SET_FOOT_H 40
|
||||
#define SET_ITEM_H 68
|
||||
#define SET_SLIDER_YOFF 32
|
||||
#define SET_TRACK_CY 52
|
||||
#define SET_SENTINEL 0x53
|
||||
#define LV_H (YRES - SET_LIST_Y - SET_FOOT_H) /* 580 */
|
||||
|
||||
/* 息屏时间档位(镜像 settings_page.c s_time_tiers) */
|
||||
static const int s_time_tiers[] = { 0, 5, 10, 15, 20, 30, 45, 60, 90, 120, 180, 300, 600, 900, 1800, 3600, 7200 };
|
||||
#define S_TIME_TIERS_N ((int)(sizeof s_time_tiers / sizeof s_time_tiers[0]))
|
||||
|
||||
/* ---- srow_t 行表镜像(键/类型/min/max 与 settings_page.c SMENU 代表行一致) ---- */
|
||||
typedef enum { SC_HEADER, SC_SWITCH, SC_SLIDER, SC_ENUM, SC_PRESET, SC_INFO, SC_LIST } sctrl_t;
|
||||
typedef struct { const char *label; const char *key; sctrl_t ctrl;
|
||||
int min, max, step; } srow_t;
|
||||
static const srow_t ROWS[] = {
|
||||
{ "显示", NULL, SC_HEADER, 0, 0, 0 },
|
||||
{ "亮度", "display.brightness", SC_SLIDER, 1, 100, 1 },
|
||||
{ "最高刷新率", "display.refresh_always_max", SC_SWITCH, 0, 0, 0 },
|
||||
{ "息屏模式", "display.screen_off_mode", SC_ENUM, 0, 0, 0 },
|
||||
{ "息屏时间", "display.screen_off_timeout", SC_SLIDER, 0, S_TIME_TIERS_N - 1, 1 },
|
||||
{ "UI风格", "ui.style", SC_ENUM, 0, 0, 0 },
|
||||
{ "滑动方向", "input.swipe_invert", SC_SWITCH, 0, 0, 0 },
|
||||
{ "网络", NULL, SC_HEADER, 0, 0, 0 },
|
||||
{ "WiFi", NULL, SC_LIST, 0, 0, 0 },
|
||||
};
|
||||
#define NROWS ((int)(sizeof ROWS / sizeof ROWS[0]))
|
||||
|
||||
static meow_widget *lv = NULL;
|
||||
|
||||
/* ---- 值格式化/映射(镜像 settings_page.c s_fmt_value/s_time_tier_idx/s_time_tier_val) ---- */
|
||||
static int s_time_tier_idx(int sec)
|
||||
{
|
||||
if (sec <= s_time_tiers[0]) return 0;
|
||||
for (int i = 1; i < S_TIME_TIERS_N; i++) {
|
||||
if (sec == s_time_tiers[i]) return i;
|
||||
if (sec < s_time_tiers[i]) {
|
||||
int lo = s_time_tiers[i-1], hi = s_time_tiers[i];
|
||||
return (sec - lo < hi - sec) ? i - 1 : i;
|
||||
}
|
||||
}
|
||||
return S_TIME_TIERS_N - 1;
|
||||
}
|
||||
static int s_time_tier_val(int idx)
|
||||
{
|
||||
if (idx < 0) idx = 0;
|
||||
if (idx >= S_TIME_TIERS_N) idx = S_TIME_TIERS_N - 1;
|
||||
return s_time_tiers[idx];
|
||||
}
|
||||
static void s_fmt_time(int sec, char *buf, int len)
|
||||
{
|
||||
if (sec <= 0) { snprintf(buf, len, "常亮"); return; }
|
||||
if (sec < 60) { snprintf(buf, len, "%d 秒", sec); return; }
|
||||
if (sec < 3600) { snprintf(buf, len, "%d 分", sec / 60); return; }
|
||||
snprintf(buf, len, "%d 时", sec / 3600);
|
||||
}
|
||||
static void s_fmt_value(const srow_t *r, char *buf, int len)
|
||||
{
|
||||
int v = 0;
|
||||
if (meow_settings_get_int(r->key, &v) != 0) { snprintf(buf, len, "-"); return; }
|
||||
if (!strcmp(r->key, "display.screen_off_timeout")) { s_fmt_time(v, buf, len); return; }
|
||||
snprintf(buf, len, "%d", v);
|
||||
}
|
||||
static void s_display_value(const srow_t *r, char *buf, int len)
|
||||
{
|
||||
int idx = 0; meow_settings_get_enum(r->key, &idx);
|
||||
if (meow_settings_enum_label(r->key, idx, buf, len) != 0) snprintf(buf, len, "-");
|
||||
}
|
||||
static int s_slider_val_from_x(const srow_t *r, int px)
|
||||
{
|
||||
int range = r->max - r->min;
|
||||
int v = r->min + (range ? (px - S_SLIDER_X) * range / S_SLIDER_W : 0);
|
||||
if (v < r->min) v = r->min;
|
||||
if (v > r->max) v = r->max;
|
||||
return v;
|
||||
}
|
||||
/* 滑条落值(含息屏时间 挡位→秒 换算;镜像 s_slider_set_from_track) */
|
||||
static void s_slider_set(const srow_t *r, int sl_v, int save)
|
||||
{
|
||||
(void)save;
|
||||
if (!strcmp(r->key, "display.screen_off_timeout")) sl_v = s_time_tier_val(sl_v);
|
||||
meow_settings_set_int(r->key, sl_v);
|
||||
}
|
||||
/* 开关 tap:读改写单键(镜像 run_settings_page SC_SWITCH 分支) */
|
||||
static void s_switch_toggle(const srow_t *r)
|
||||
{
|
||||
int on = 0; meow_settings_get_switch(r->key, &on);
|
||||
meow_settings_set_switch(r->key, !on);
|
||||
}
|
||||
|
||||
/* ---- listview 虚拟化数据源(镜像 settings_page.c set_lv_bind) ---- */
|
||||
static int lv_count(void *ctx) { (void)ctx; return NROWS; }
|
||||
static void lv_bind(int idx, meow_lv_item *out, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
memset(out, 0, sizeof *out);
|
||||
out->icon_kind = MEOW_LIST_ICON_NONE;
|
||||
out->enabled = 1;
|
||||
if (idx < 0 || idx >= NROWS) { out->title = ""; return; }
|
||||
const srow_t *r = &ROWS[idx];
|
||||
out->title = r->label ? r->label : "";
|
||||
out->section = (r->ctrl == SC_HEADER) ? 1 : 0;
|
||||
if (r->ctrl == SC_SWITCH) {
|
||||
int on = 0; meow_settings_get_switch(r->key, &on);
|
||||
out->right = MEOW_LIST_RIGHT_SWITCH; out->on = on;
|
||||
}
|
||||
}
|
||||
|
||||
/* 行内开关池(镜像 settings_page.c g_set_sw pool) */
|
||||
#define SET_SWITCH_POOL 12
|
||||
static meow_widget *g_set_sw[SET_SWITCH_POOL];
|
||||
static int g_set_sw_n = 0, g_set_sw_use = 0;
|
||||
static meow_widget *set_sw_take(void)
|
||||
{
|
||||
if (g_set_sw_use < g_set_sw_n) return g_set_sw[g_set_sw_use++];
|
||||
if (g_set_sw_n >= SET_SWITCH_POOL) return NULL;
|
||||
meow_widget *w = meow_switch_create(0, 0, 0);
|
||||
if (w) { g_set_sw[g_set_sw_n++] = w; return g_set_sw[g_set_sw_use++]; }
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ---- 整行自绘(镜像 settings_page.c set_lv_draw) ---- */
|
||||
static void lv_draw(int idx, int x, int y, int w, int h, int state, void *ctx)
|
||||
{
|
||||
(void)ctx; (void)h;
|
||||
const meow_theme *mt = meow_theme_get();
|
||||
if (idx < 0) return;
|
||||
const srow_t *r = &ROWS[idx];
|
||||
int sel = (state & MEOW_LISTVIEW_ST_SELECTED);
|
||||
int press = (state & MEOW_LISTVIEW_ST_PRESSED);
|
||||
int thh = meow_ui_text_height(mt->font_body);
|
||||
uint32_t textcol = sel ? mt->onPrimaryContainer : mt->onSurface;
|
||||
uint32_t subcol = sel ? mt->onPrimaryContainer : mt->onSurfaceVariant;
|
||||
|
||||
if (r->ctrl == SC_HEADER) {
|
||||
meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->surfaceContainerHigh);
|
||||
int ty = y + (SET_ITEM_H - thh) / 2;
|
||||
if (ty >= 0) meow_ui_text(x + 16, ty, r->label, mt->font_body, mt->primary);
|
||||
meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant);
|
||||
return;
|
||||
}
|
||||
if (sel) meow_ui_fill_rect(x, y, w, SET_ITEM_H, mt->primaryContainer);
|
||||
else if (press) meow_ui_fill_rect_alpha(x, y, w, SET_ITEM_H, mt->primary, 26);
|
||||
int ly = y + (SET_ITEM_H - thh) / 2;
|
||||
switch (r->ctrl) {
|
||||
case SC_SWITCH: {
|
||||
meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol);
|
||||
meow_widget *sw = set_sw_take();
|
||||
if (sw) {
|
||||
int on = 0; meow_settings_get_switch(r->key, &on);
|
||||
meow_widget_set_rect(sw, x + w - 16 - 56, y + (SET_ITEM_H - 32) / 2, 56, 32);
|
||||
meow_switch_set(sw, on);
|
||||
meow_widget_draw(sw);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SC_SLIDER: {
|
||||
/* 宿主测试不引入 launcher s_draw_slider/s_ui_theme:只验证行文字渲染 + 命中,track 视觉由
|
||||
* 真机 s_draw_slider 呈现(settings_page.c set_lv_draw 已复用)。 */
|
||||
meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol);
|
||||
char vb[32]; s_fmt_value(r, vb, sizeof vb);
|
||||
meow_ui_text(x + w - 16 - meow_ui_text_width(vb, mt->font_body), ly, vb, mt->font_body, subcol);
|
||||
break;
|
||||
}
|
||||
case SC_LIST: {
|
||||
meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol);
|
||||
meow_ui_text(x + w - 16 - meow_ui_text_width("已连接", mt->font_body), ly, "已连接", mt->font_body, subcol);
|
||||
break;
|
||||
}
|
||||
default: { /* SC_ENUM / SC_PRESET */
|
||||
meow_ui_text(x + 16, ly, r->label, mt->font_body, textcol);
|
||||
char vb[40]; s_display_value(r, vb, sizeof vb);
|
||||
int vx = x + w - 16 - meow_ui_text_width(vb, mt->font_body);
|
||||
meow_ui_text(vx, ly, vb, mt->font_body, subcol);
|
||||
break;
|
||||
}
|
||||
}
|
||||
meow_ui_fill_rect(x, y + SET_ITEM_H - 1, w, 1, mt->outlineVariant);
|
||||
if (state & MEOW_LISTVIEW_ST_DISABLED) meow_ui_fill_rect_alpha(x, y, w, SET_ITEM_H, mt->onSurface, 90);
|
||||
}
|
||||
|
||||
static void mk_widgets(void)
|
||||
{
|
||||
meow_ui_activity_push(SET_SENTINEL);
|
||||
lv = meow_listview_create(0, SET_LIST_Y, XRES, LV_H, 0, NULL, NULL, MEOW_LIST_SEL_SINGLE);
|
||||
meow_listview_set_vsource(lv, lv_count, lv_bind, NULL);
|
||||
meow_listview_set_item_draw(lv, lv_draw, NULL);
|
||||
meow_listview_set_item_h(lv, SET_ITEM_H);
|
||||
meow_ui_activity_pop();
|
||||
}
|
||||
|
||||
static void draw_all(void)
|
||||
{
|
||||
const meow_theme *th = meow_theme_get();
|
||||
meow_ui_fill_rect(0, 0, XRES, YRES, th->surface);
|
||||
meow_ui_fill_rect(0, SET_LIST_Y, XRES, LV_H, th->surfaceVariant);
|
||||
if (lv) { g_set_sw_use = 0; meow_widget_draw(lv); }
|
||||
}
|
||||
|
||||
/* ---- 像素探测 ---- */
|
||||
static void rd_px(int x, int y, unsigned short *p)
|
||||
{
|
||||
unsigned char *bb = meow_ui_back_buf();
|
||||
int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp();
|
||||
if (!bb || x < 0 || y < 0) { *p = 0; return; }
|
||||
if (bpp == 16) *p = *(unsigned short *)(bb + (size_t)y * line + (size_t)x * 2);
|
||||
else *p = (unsigned short)(*(unsigned int *)(bb + (size_t)y * line + (size_t)x * 4) >> 16);
|
||||
}
|
||||
static unsigned short px565(int x, int y) { unsigned short p; rd_px(x, y, &p); return p; }
|
||||
static int region_nonbg(int x, int y, int w, int h, uint32_t bg)
|
||||
{
|
||||
unsigned short bg565 = ((bg >> 8) & 0xF800) | ((bg >> 5) & 0x07E0) | ((bg >> 3) & 0x001F);
|
||||
int cnt = 0;
|
||||
for (int dy = 0; dy < h; dy++)
|
||||
for (int dx = 0; dx < w; dx++)
|
||||
if (px565(x + dx, y + dy) != bg565) cnt++;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/* ---- meow_settings 写回验证辅助:清盘后 save,再 grep conf(格式 `key = value`,去空格比较) ---- */
|
||||
static int conf_has(const char *key, const char *val)
|
||||
{
|
||||
char line[256], expect[256];
|
||||
FILE *f = fopen(MEOW_SETTINGS_FILE, "r");
|
||||
if (!f) return 0;
|
||||
snprintf(expect, sizeof expect, "%s=%s", key, val);
|
||||
int found = 0;
|
||||
while (fgets(line, sizeof line, f)) {
|
||||
char stripped[256]; int k = 0;
|
||||
for (int i = 0; line[i] && k < 255; i++)
|
||||
if (line[i] != ' ' && line[i] != '\n' && line[i] != '\r') stripped[k++] = line[i];
|
||||
stripped[k] = 0;
|
||||
if (!strcmp(stripped, expect)) { found = 1; break; }
|
||||
}
|
||||
fclose(f);
|
||||
return found;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
setbuf(stdout, NULL);
|
||||
mkdir("/tmp/meow_set_test", 0777);
|
||||
setenv("MEOW_UI_DUMP", "/tmp/settings.ppm", 1);
|
||||
if (meow_settings_init() != 0) printf("settings init (defaults; file may be absent)\n");
|
||||
meow_ui_opts o = { XRES, YRES };
|
||||
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
|
||||
mk_widgets();
|
||||
|
||||
/* ============ P1 listview 虚拟化渲染(section 段头 + 各型行非背景) ============ */
|
||||
printf("P1 listview virtualized render (settings rows)\n");
|
||||
meow_ui_activity_push(SET_SENTINEL);
|
||||
const meow_theme *th = meow_theme_get();
|
||||
draw_all();
|
||||
/* 段头行整体填 surfaceContainerHigh(覆盖列表底 surfaceVariant)→ 对列表底背景即非背景;
|
||||
* 注意宿主无 mfont 时 CJK 标题回退点阵不显形,故以「段头底 vs 列表底」判渲染而非文字像素。 */
|
||||
int hdrcnt = region_nonbg(2, SET_LIST_Y + 4, XRES - 4, SET_ITEM_H - 8, th->surfaceVariant);
|
||||
int rowcnt = region_nonbg(2, SET_LIST_Y + SET_ITEM_H + 4, XRES - 4, SET_ITEM_H - 8, th->surfaceVariant);
|
||||
CHECK(hdrcnt > 0, "section header band renders (hdr=%d)", hdrcnt);
|
||||
CHECK(rowcnt > 40, "row band renders (title/value/switch) cnt=%d", rowcnt);
|
||||
/* P1b:开关行(ROWS[2]=最高刷新率 right switch)中心右缘区非背景 */
|
||||
{
|
||||
int swy = SET_LIST_Y + 2 * SET_ITEM_H + SET_ITEM_H / 2;
|
||||
int swc = region_nonbg(XRES - 16 - 56, swy - 4, 56, 8, th->surfaceVariant);
|
||||
CHECK(swc > 0, "switch row right tail (meow_switch) renders swc=%d", swc);
|
||||
}
|
||||
|
||||
/* ============ P2 meow_listview 命中(行号 + 段头可命中) ============ */
|
||||
printf("P2 meow_listview hit\n");
|
||||
CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + 0 * SET_ITEM_H + SET_ITEM_H / 2) == 0,
|
||||
"row0 (header 显示) hit idx0 (enabled=1)");
|
||||
CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + 1 * SET_ITEM_H + SET_ITEM_H / 2) == 1,
|
||||
"row1 (亮度 slider) hit idx1");
|
||||
CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + 3 * SET_ITEM_H + SET_ITEM_H / 2) == 3,
|
||||
"row3 (息屏模式 enum) hit idx3");
|
||||
CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + 8 * SET_ITEM_H + SET_ITEM_H / 2) == 8,
|
||||
"row8 (WiFi list) hit idx8");
|
||||
CHECK(meow_listview_hit(lv, 10, SET_LIST_Y - 2) == -1, "above list -> -1");
|
||||
/* 滚动后命中(listview set_scroll 换算):scroll=20(<9*68-视口=32)时点 row1 中心 → idx1 */
|
||||
meow_listview_set_scroll(lv, 20);
|
||||
CHECK(meow_listview_hit(lv, 10, SET_LIST_Y + SET_ITEM_H + SET_ITEM_H / 2) == 1,
|
||||
"scrolled +20px: row1 center hits idx1");
|
||||
CHECK(meow_listview_max_scroll(lv) > 0, "max_scroll > 0 (list taller than viewport) got %d",
|
||||
meow_listview_max_scroll(lv));
|
||||
meow_listview_set_scroll(lv, 0);
|
||||
|
||||
/* ============ P3 开关写回(读改写单键 → settings.conf) ============ */
|
||||
printf("P3 switch write-back to settings.conf\n");
|
||||
meow_settings_set_switch("input.swipe_invert", 0); /* reset */
|
||||
meow_settings_save();
|
||||
int before_on = 0; meow_settings_get_switch("input.swipe_invert", &before_on);
|
||||
{
|
||||
/* 模拟 tap ROWS[6]=滑动方向 */
|
||||
const srow_t *r = &ROWS[6];
|
||||
s_switch_toggle(r);
|
||||
meow_settings_save();
|
||||
}
|
||||
int after_on = 0; meow_settings_get_switch("input.swipe_invert", &after_on);
|
||||
CHECK(before_on == 0 && after_on == 1,
|
||||
"input.swipe_invert toggled 0->1 (read-modify-write single key)");
|
||||
CHECK(conf_has("input.swipe_invert", "1"), "settings.conf has input.swipe_invert=1");
|
||||
CHECK(!conf_has("display.refresh_always_max", "1"), "untouched sibling key unchanged in conf");
|
||||
|
||||
/* ============ P4 滑条写回(x→值 → settings.conf;含息屏时间 挡位→秒) ============ */
|
||||
printf("P4 slider write-back to settings.conf\n");
|
||||
meow_settings_set_int("display.brightness", 50); meow_settings_save();
|
||||
{
|
||||
const srow_t *r = &ROWS[1]; /* 亮度 1..100 */
|
||||
int v = s_slider_val_from_x(r, S_SLIDER_X + S_SLIDER_W / 2); /* 中点 ≈ 50 */
|
||||
s_slider_set(r, v, 1);
|
||||
meow_settings_save();
|
||||
int got = 0; meow_settings_get_int("display.brightness", &got);
|
||||
CHECK(got == v, "display.brightness set via track -> %d (expect %d)", got, v);
|
||||
char kv[64]; snprintf(kv, sizeof kv, "%d", v);
|
||||
CHECK(conf_has("display.brightness", kv), "settings.conf has display.brightness=%d", v);
|
||||
}
|
||||
{ /* 息屏时间:秒→挡位索引(显示用)与 挡位索引→秒(落盘用)双向映射 */
|
||||
const srow_t *r = &ROWS[4]; /* 0..S_TIME_TIERS_N-1 */
|
||||
int idx = 5; /* 第 5 挡 = 30 秒 */
|
||||
int sec = s_time_tier_val(idx);
|
||||
CHECK(s_time_tier_idx(30) == idx, "screen_off_timeout sec 30 -> tier idx %d", idx);
|
||||
s_slider_set(r, idx, 1);
|
||||
meow_settings_save();
|
||||
int got = 0; meow_settings_get_int("display.screen_off_timeout", &got);
|
||||
CHECK(sec == 30 && got == 30, "screen_off_timeout tier idx5 -> 30s (got %d)", got);
|
||||
CHECK(conf_has("display.screen_off_timeout", "30"), "settings.conf has display.screen_off_timeout=30");
|
||||
}
|
||||
|
||||
/* ============ P5 枚举写回(enum 选项 → settings.conf 键值正确) ============ */
|
||||
printf("P5 enum write-back to settings.conf\n");
|
||||
{
|
||||
const char *names[8]; int kn = 0;
|
||||
meow_settings_enum_names("display.screen_off_mode", names, &kn);
|
||||
CHECK(kn > 0, "display.screen_off_mode enum names count=%d", kn);
|
||||
int pick = 1; /* "black"(黑屏) */
|
||||
meow_settings_set_enum("display.screen_off_mode", pick);
|
||||
meow_settings_save();
|
||||
int got = 0; meow_settings_get_enum("display.screen_off_mode", &got);
|
||||
CHECK(got == pick, "display.screen_off_mode enum -> %d (expect %d)", got, pick);
|
||||
CHECK(conf_has("display.screen_off_mode", "black"), "settings.conf has display.screen_off_mode=black");
|
||||
}
|
||||
|
||||
meow_ui_activity_pop();
|
||||
meow_ui_shutdown();
|
||||
printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail);
|
||||
return g_fail;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,255 @@
|
||||
/* test_subpage_widgets.c — M7.1a 子页渲染层迁移(SDK 控件化)宿主验证。
|
||||
*
|
||||
* 复现 subpage.c 所建控件集(topappbar + 顶开关 meow_switch + listview 虚拟化 vsource +
|
||||
* meow_scroll 包裹 fling),验证:
|
||||
* P1 listview 虚拟化渲染:section 段头 + 普通/开关/动作行(right=SWITCH/ARROW)非背景像素;
|
||||
* P2 listview 命中:行号(含滚动换算 + 禁用行不响应)+ right 操作区命中(hit_op);
|
||||
* P3 topappbar 命中:返回箭头区 → MEOW_TOPBAR_BACK;
|
||||
* P4 meow_scroll 包裹 listview:跟手 track + 松手 fling(meow_anim_tick 驱动)+ snap 到行边界;
|
||||
* P5 SDK 输入态(meow_input_touch_set)→ listview 按压态像素变化;
|
||||
* P6 主题四态(MD3/MIUIx × 明暗)下 topappbar + listview 渲染非背景。
|
||||
*
|
||||
* 几何常量与 subpage.c / meow_launcher_internal.h 镜像;控件创建方式与 sp_widgets_create 同
|
||||
* (owner=哨兵 0x52,listview 用 set_vsource + set_item_renderer(2,0,1) + set_item_h 56)。
|
||||
*
|
||||
* 编译(在 patch_v2/meow_launcherd 下):
|
||||
* gcc -Wall -Wextra -o test_subpage_widgets test_subpage_widgets.c \
|
||||
* ../sdk/test_host_stub.c ../sdk/meow_ui_gfx.c ../sdk/meow_ui_sys.c \
|
||||
* ../sdk/meow_ui_theme.c ../sdk/meow_ui_vol.c ../sdk/meow_ui_log.c \
|
||||
* ../sdk/meow_ui_widget.c ../sdk/meow_ui_input.c ../sdk/meow_ui_activity.c \
|
||||
* ../sdk/meow_ui_dialog.c ../sdk/meow_ui_ime.c ../sdk/meow_ui_str.c ../sdk/meow_pinyin.c \
|
||||
* ../sdk/meow_ui_draw.c ../sdk/meow_ui_view.c ../sdk/meow_ui_box.c ../sdk/meow_ui_anim.c \
|
||||
* ../sdk/meow_ui_scroll.c ../sdk/meow_ui_img.c ../sdk/meow_ui_media.c \
|
||||
* ../sdk/meow_notify_ipc.c ../sdk/stb_image.c -I../sdk -I. -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_subpage_widgets
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h" /* meow_ui_back_buf / meow_gfx_mark_dirty / meow_input_touch_set */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond, ...) do { \
|
||||
if (cond) { printf(" PASS: " __VA_ARGS__); printf("\n"); } \
|
||||
else { printf(" FAIL: " __VA_ARGS__); printf("\n"); g_fail++; } } while (0)
|
||||
|
||||
/* ---- meow_lv_item 大列表数据源(P4 滚动测用:40 行) ---- */
|
||||
static int big_count(void *ctx) { (void)ctx; return 40; }
|
||||
static void big_bind(int idx, meow_lv_item *out, void *ctx) {
|
||||
(void)ctx; memset(out, 0, sizeof *out);
|
||||
out->icon_kind = MEOW_LIST_ICON_NONE; out->enabled = 1;
|
||||
out->title = "行"; out->subtitle = "sub"; out->right = MEOW_LIST_RIGHT_ARROW;
|
||||
(void)idx;
|
||||
}
|
||||
|
||||
/* ---- 与 subpage.c / meow_launcher_internal.h 同源几何(镜像) ---- */
|
||||
#define SUB_TB_Y 28
|
||||
#define SUB_TB_H 68
|
||||
#define SUB_TB_BOT (SUB_TB_Y + SUB_TB_H) /* 96 */
|
||||
#define SUB_CONT_TOP SUB_TB_BOT
|
||||
#define SUB_FOOT_H 44
|
||||
#define SUB_ITEM_H 56
|
||||
#define SUB_SENTINEL 0x52
|
||||
#define XRES 480
|
||||
#define YRES 720
|
||||
#define LV_H (YRES - SUB_CONT_TOP - SUB_FOOT_H) /* 580 */
|
||||
|
||||
/* ---- sub_row_t 行缓冲镜像(sub_page_t/sub_row_t 结构在 meow_launcher_internal.h) ---- */
|
||||
enum { SUB_HDR, SUB_ROW, SUB_SW, SUB_ACT };
|
||||
typedef struct { int kind; const char *label; const char *sub; int on; int act; } sub_row_t;
|
||||
static sub_row_t g_rows[16];
|
||||
static int g_nrows = 0;
|
||||
|
||||
static void rows_fill(void)
|
||||
{
|
||||
g_nrows = 0;
|
||||
g_rows[g_nrows].kind = SUB_HDR; g_rows[g_nrows].label = "当前连接"; g_rows[g_nrows].sub = NULL; g_nrows++;
|
||||
g_rows[g_nrows].kind = SUB_ROW; g_rows[g_nrows].label = "已连接"; g_rows[g_nrows].sub = "MyWiFi"; g_rows[g_nrows].act = -1; g_nrows++;
|
||||
g_rows[g_nrows].kind = SUB_HDR; g_rows[g_nrows].label = "已保存网络"; g_rows[g_nrows].sub = NULL; g_nrows++;
|
||||
g_rows[g_nrows].kind = SUB_ACT; g_rows[g_nrows].label = "MyWiFi"; g_rows[g_nrows].sub = "已连接"; g_rows[g_nrows].act = 0; g_nrows++;
|
||||
g_rows[g_nrows].kind = SUB_HDR; g_rows[g_nrows].label = "附近网络"; g_rows[g_nrows].sub = NULL; g_nrows++;
|
||||
g_rows[g_nrows].kind = SUB_SW; g_rows[g_nrows].label = "WLAN"; g_rows[g_nrows].sub = NULL; g_rows[g_nrows].on = 1; g_nrows++;
|
||||
}
|
||||
|
||||
/* ---- meow_lv_item 映射(镜像 subpage.c sp_lv_bind) ---- */
|
||||
static int sp_lv_count(void *ctx) { (void)ctx; return g_nrows; }
|
||||
static void sp_lv_bind(int idx, meow_lv_item *out, void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
memset(out, 0, sizeof *out);
|
||||
out->icon_kind = MEOW_LIST_ICON_NONE;
|
||||
out->enabled = 1;
|
||||
if (idx < 0 || idx >= g_nrows) { out->title = ""; return; }
|
||||
const sub_row_t *r = &g_rows[idx];
|
||||
switch (r->kind) {
|
||||
case SUB_HDR: out->section = 1; out->title = r->label ? r->label : "";
|
||||
out->right = MEOW_LIST_RIGHT_NONE; out->enabled = 0; break;
|
||||
case SUB_ROW: out->title = r->label ? r->label : ""; out->subtitle = r->sub;
|
||||
out->right = MEOW_LIST_RIGHT_NONE; break;
|
||||
case SUB_SW: out->title = r->label ? r->label : ""; out->subtitle = r->sub;
|
||||
out->right = MEOW_LIST_RIGHT_SWITCH; out->on = r->on; break;
|
||||
case SUB_ACT: out->title = r->label ? r->label : ""; out->subtitle = r->sub;
|
||||
out->right = MEOW_LIST_RIGHT_ARROW; break;
|
||||
}
|
||||
}
|
||||
|
||||
static meow_widget *tb = NULL, *sw = NULL, *lv = NULL;
|
||||
|
||||
static void rd_px(int x, int y, unsigned short *p)
|
||||
{
|
||||
unsigned char *bb = meow_ui_back_buf();
|
||||
int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp();
|
||||
if (!bb || x < 0 || y < 0) { *p = 0; return; }
|
||||
if (bpp == 16) *p = *(unsigned short *)(bb + (size_t)y * line + (size_t)x * 2);
|
||||
else *p = (unsigned short)(*(unsigned int *)(bb + (size_t)y * line + (size_t)x * 4) >> 16);
|
||||
}
|
||||
static unsigned short px565(int x, int y) { unsigned short p; rd_px(x, y, &p); return p; }
|
||||
static int region_nonbg(int x, int y, int w, int h, uint32_t bg)
|
||||
{
|
||||
unsigned short bg565 = ((bg >> 8) & 0xF800) | ((bg >> 5) & 0x07E0) | ((bg >> 3) & 0x001F);
|
||||
int cnt = 0;
|
||||
for (int dy = 0; dy < h; dy++)
|
||||
for (int dx = 0; dx < w; dx++)
|
||||
if (px565(x + dx, y + dy) != bg565) cnt++;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static void mk_widgets(void)
|
||||
{
|
||||
meow_ui_activity_push(SUB_SENTINEL);
|
||||
tb = meow_topappbar_create(0, SUB_TB_Y, XRES, SUB_TB_H, "WiFi");
|
||||
meow_topappbar_set_back(tb, 1);
|
||||
lv = meow_listview_create(0, SUB_CONT_TOP, XRES, LV_H, 0, NULL, NULL, MEOW_LIST_SEL_NONE);
|
||||
meow_listview_set_vsource(lv, sp_lv_count, sp_lv_bind, NULL);
|
||||
meow_listview_set_item_renderer(lv, 2, 0, 1);
|
||||
meow_listview_set_item_h(lv, SUB_ITEM_H);
|
||||
sw = meow_switch_create(XRES - 16 - 56, SUB_TB_Y + (SUB_TB_H - 32) / 2, 1);
|
||||
meow_ui_activity_pop();
|
||||
}
|
||||
|
||||
static void draw_all(void)
|
||||
{
|
||||
const meow_theme *th = meow_theme_get();
|
||||
meow_ui_fill_rect(0, 0, XRES, YRES, th->surface);
|
||||
if (tb) meow_widget_draw(tb);
|
||||
if (sw) meow_widget_draw(sw);
|
||||
meow_ui_fill_rect(0, SUB_CONT_TOP, XRES, LV_H, th->surface);
|
||||
if (lv) meow_widget_draw(lv);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
setbuf(stdout, NULL);
|
||||
setenv("MEOW_UI_DUMP", "/tmp/subpage.ppm", 1);
|
||||
meow_ui_opts o = { XRES, YRES };
|
||||
if (meow_ui_init(&o) != 0) { printf("init failed\n"); return 1; }
|
||||
rows_fill();
|
||||
mk_widgets();
|
||||
|
||||
/* ============ P1 listview 虚拟化渲染(section 段头 + 开关/箭头行) ============ */
|
||||
printf("P1 listview virtualized render\n");
|
||||
meow_ui_activity_push(SUB_SENTINEL);
|
||||
const meow_theme *th = meow_theme_get();
|
||||
draw_all();
|
||||
int hdrcnt = region_nonbg(2, SUB_CONT_TOP + 4, XRES - 4, SUB_ITEM_H - 8, th->surface);
|
||||
int rowcnt = region_nonbg(2, SUB_CONT_TOP + SUB_ITEM_H + 4, XRES - 4, SUB_ITEM_H - 8, th->surface);
|
||||
char nm[80];
|
||||
snprintf(nm, sizeof nm, "section header renders (band=%d)", hdrcnt);
|
||||
CHECK(hdrcnt > 0, "%s", nm);
|
||||
snprintf(nm, sizeof nm, "row renders (title hdr/switch/arrow) cnt=%d", rowcnt);
|
||||
CHECK(rowcnt > 40, "%s", nm);
|
||||
|
||||
/* ============ P2 listview 命中(行号 + right 操作区) ============ */
|
||||
printf("P2 listview hit / hit_op\n");
|
||||
int lvy = SUB_CONT_TOP;
|
||||
CHECK(meow_listview_hit(lv, 10, lvy + SUB_ITEM_H / 2) == -1, "row0 (hdr) disabled -> -1");
|
||||
CHECK(meow_listview_hit(lv, 10, lvy + SUB_ITEM_H + SUB_ITEM_H / 2) == 1, "row1 (已连接) hits idx1");
|
||||
/* 行右 1/3 = 操作区:row1 是 SUB_ROW(right none) → hit_op 0 */
|
||||
CHECK(meow_listview_hit_op(lv, XRES - 20, lvy + SUB_ITEM_H + SUB_ITEM_H / 2) == 0,
|
||||
"row1 right-none -> hit_op 0");
|
||||
/* row3 = SUB_ACT(MyWiFi) 右箭头;row5 = SUB_SW(WLAN) 右开关 → hit_op 1 */
|
||||
CHECK(meow_listview_hit_op(lv, XRES - 20, lvy + 3 * SUB_ITEM_H + SUB_ITEM_H / 2) == 1,
|
||||
"row3 (act arrow) right hits op");
|
||||
CHECK(meow_listview_hit_op(lv, XRES - 20, lvy + 5 * SUB_ITEM_H + SUB_ITEM_H / 2) == 1,
|
||||
"row5 (sw) right hits op");
|
||||
|
||||
/* ============ P3 topappbar 命中(返回箭头) ============ */
|
||||
printf("P3 topappbar back hit\n");
|
||||
CHECK(meow_topappbar_hit(tb, 10, SUB_TB_Y + SUB_TB_H / 2) == MEOW_TOPBAR_BACK,
|
||||
"back arrow region -> MEOW_TOPBAR_BACK");
|
||||
CHECK(meow_topappbar_hit(tb, XRES - 5, SUB_TB_Y + SUB_TB_H / 2) == -1,
|
||||
"right region (no action) -> -1");
|
||||
|
||||
/* ============ P4 meow_scroll 包裹 listview + fling(惯性,ease-out,无 snap 回拉) ============ */
|
||||
printf("P4 meow_scroll wrap listview + fling\n");
|
||||
/* 40 行大列表(独立数据源),撑起滚动 */
|
||||
meow_widget *scroll_lv = meow_listview_create(0, SUB_CONT_TOP, XRES, LV_H, 0, NULL, NULL,
|
||||
MEOW_LIST_SEL_NONE);
|
||||
meow_listview_set_vsource(scroll_lv, big_count, big_bind, NULL);
|
||||
meow_listview_set_item_renderer(scroll_lv, 2, 0, 1);
|
||||
meow_listview_set_item_h(scroll_lv, SUB_ITEM_H);
|
||||
meow_scroll *s = meow_scroll_wrap(scroll_lv, 1); /* 1=W_LISTVIEW */
|
||||
CHECK(s != NULL, "meow_scroll_wrap(listview)");
|
||||
int maxs = meow_listview_max_scroll(scroll_lv);
|
||||
CHECK(maxs > 0, "max_scroll > 0 (list taller than viewport) got %d", maxs);
|
||||
/* 快速下划(正步进)建初速 → fling 前进(单调不减 + 不越界 + snap 到行边界) */
|
||||
uint64_t t = meow_ui_time_ms();
|
||||
for (int i = 0; i < 12; i++) meow_scroll_track(s, 30, t + (uint64_t)i * 16);
|
||||
int start = meow_scroll_offset(s);
|
||||
CHECK(start > 0, "dragged start>0 got %d", start);
|
||||
meow_scroll_fling(s);
|
||||
int prev = start, mono = 1, maxoff = start, guard = 0;
|
||||
while (meow_scroll_active(s) && guard++ < 400) {
|
||||
meow_anim_tick();
|
||||
int cur = meow_scroll_offset(s);
|
||||
if (maxoff < cur) maxoff = cur;
|
||||
if (cur < prev) mono = 0;
|
||||
prev = cur;
|
||||
usleep(8000);
|
||||
}
|
||||
meow_anim_tick();
|
||||
int end = meow_scroll_offset(s);
|
||||
CHECK(mono == 1, "fling monotonic non-decreasing");
|
||||
CHECK(end >= start, "fling advances end=%d start=%d", end, start);
|
||||
CHECK(end <= maxs, "no overflow end=%d max=%d", end, maxs);
|
||||
meow_scroll_free(s);
|
||||
meow_widget_free(scroll_lv);
|
||||
|
||||
/* ============ P5 SDK 输入态 → listview 按压态像素变化 ============ */
|
||||
printf("P5 meow_input_touch_set pressed state\n");
|
||||
meow_ui_activity_push(SUB_SENTINEL);
|
||||
draw_all();
|
||||
int px_y = SUB_CONT_TOP + SUB_ITEM_H + SUB_ITEM_H / 2; /* row1 中心 */
|
||||
unsigned short p0 = px565(10, px_y);
|
||||
meow_input_touch_set(1, 10, px_y, 0);
|
||||
draw_all();
|
||||
unsigned short p1 = px565(10, px_y);
|
||||
meow_input_touch_set(0, 0, 0, 0);
|
||||
CHECK(p1 != p0, "pressed row -> pixel changes");
|
||||
meow_ui_activity_pop();
|
||||
|
||||
/* ============ P6 主题四态(MD3/MIUIx × 明暗)渲染非背景 ============ */
|
||||
printf("P6 theme 4-state render\n");
|
||||
const int styles[2] = { MEOW_STYLE_MD3, MEOW_STYLE_MIUIX };
|
||||
const int modes[2] = { MEOW_THEME_LIGHT, MEOW_THEME_DARK };
|
||||
for (int si = 0; si < 2; si++) {
|
||||
for (int mi = 0; mi < 2; mi++) {
|
||||
meow_ui_set_style(styles[si]);
|
||||
meow_theme_set_mode((meow_theme_mode)modes[mi]);
|
||||
th = meow_theme_get();
|
||||
draw_all();
|
||||
int tbcnt = region_nonbg(0, SUB_TB_Y, XRES, SUB_TB_H, th->surface);
|
||||
int lvcnt = region_nonbg(0, SUB_CONT_TOP, XRES, LV_H, th->surface);
|
||||
snprintf(nm, sizeof nm, "style=%s mode=%s topbar+list render",
|
||||
styles[si] ? "MIUIx" : "MD3", modes[mi] ? "dark" : "light");
|
||||
CHECK(tbcnt > 0 && lvcnt > 40, "%s", nm);
|
||||
}
|
||||
}
|
||||
|
||||
meow_ui_activity_pop();
|
||||
meow_ui_shutdown();
|
||||
printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail);
|
||||
return g_fail;
|
||||
}
|
||||
Binary file not shown.
Reference in new issue
Block a user