diff --git a/patch_v2/files_app/README.md b/patch_v2/files_app/README.md index 87a15da..d20335d 100644 --- a/patch_v2/files_app/README.md +++ b/patch_v2/files_app/README.md @@ -74,8 +74,10 @@ gcc -O2 -Wall -Wextra -DFSX_SD_ROOT='"/tmp/meowfsx_fsx_test"' -Ilogic \ (行数据源 `browser_src2` 填 `meow_list_item`)。图标(目录/文件内置符号)、名称、大小/时间副标题、 右侧 `⋯` 操作钮、虚拟化、空态/页脚、禁用行不响应均由 SDK 收口;`fsx_scan_dir` 仍以有界数组缓存 (`FSX_MAX_ENTRIES=1024`),列表虚拟化只画可见行——大目录不一次全读进内存。 - - **long-press 由 App 层保持**:SDK listview 无内建长按;`browser_tick` 的「按下→≥500ms 未拖动→ - BottomSheet」逻辑不依赖控件渲染,保留在 App 层(触摸命中用 `meow_listview_hit` 行号)。 + - **long-press 已下沉 SDK(§6.3)**:SDK 通用长按判定「按下 ≥ 阈值(默认 500ms,可配)未抬手且 + 位移 ≤ 容忍」→ 经 `meow_ui_set_long_press_cb` 注册的回调带坐标交给本页;`browser_on_long_press` + 用 `meow_listview_hit` 行命中打开 BottomSheet。App 层长按计时(`browser_tick`/`g_press_ms`)已删除, + 其抬手 TAP 由 SDK 抑制(长按不再触发点击)。 2. **中文字形**:SDK 默认 `common` 字库(≈4k)不含 CJK 全量;exFAT 中文文件名/中文文本预览在 common 档会显示缺字豆腐。 预览页不声明 `fonts`(app.json 未加 `"fonts":"full"`/`"gbk"`),如需中文预览可在 app.json 声明大字号档。 3. **预览仅文本**:按扩展名白名单 + 首 512 字节 NUL 嗅探判文本;二进制/图片无预览(提示「not a text file」)。 diff --git a/patch_v2/files_app/app.c b/patch_v2/files_app/app.c index c5c8c17..28b9442 100644 --- a/patch_v2/files_app/app.c +++ b/patch_v2/files_app/app.c @@ -1,7 +1,8 @@ /* app.c — files 应用装配层:main + 组装 + 事件分发。 * * 职责(对齐 hello_app/app.c):main 只做初始化/建页/跑事件循环/清理;把 meow_ui_run 的平铺输入 - * 回调(key/tap/swipe/tick/touch)路由到当前活动页(browser/viewer)。页面切换统一走 + * 回调(key/tap/swipe/tick/touch)路由到当前活动页(browser/viewer);通用长按走独立注册 + * (meow_ui_set_long_press_cb,§6.3)路由到 browser。页面切换统一走 * meow_view_page_push/pop(活动栈 + on_enter/on_leave 唯一驱动)。 * * 铁律(APPS.md §3.4.1 / WORK_STATE.md):物理键禁映射——不处理任何 key 语义;返回=左缘左滑 @@ -66,12 +67,16 @@ static void app_on_touch_move(int x, int y) else if (browser_is_active()) browser_touch_move(x, y); } -/* on_tick:长按检测(browser 页)。 */ -static void app_on_tick(void) +/* 通用长按(§6.3):SDK 判定「按下 ≥ 阈值未抬手且位移 ≤ 容忍」后带坐标回调; + * browser 据此行命中打开 BottomSheet。viewer 无长按语义。 */ +static void app_on_long_press(int x, int y) { - if (browser_is_active()) browser_tick(meow_ui_time_ms()); + if (browser_is_active()) browser_on_long_press(x, y); } +/* on_tick:本 App 无帧动画/时钟(长按计时已下沉 SDK §6.3),保留为空槽。 */ +static void app_on_tick(void) { } + /* ============================================================ */ /* main */ /* ============================================================ */ @@ -95,6 +100,8 @@ int main(void) app_on_key, app_on_tap, app_on_swipe, app_on_tick, app_on_swipe_progress, app_on_touch_down, app_on_touch_move, }; + /* 通用长按走独立注册(§6.3):不进 meow_ui_events(避免位置初始化在 -Wextra 下告警)。 */ + meow_ui_set_long_press_cb(app_on_long_press); meow_ui_run(&ev); /* 清理:meow_view_free 触发各页 on_destroy(控件 + 文本缓冲) */ diff --git a/patch_v2/files_app/files b/patch_v2/files_app/files index ed947c8..8178819 100755 Binary files a/patch_v2/files_app/files and b/patch_v2/files_app/files differ diff --git a/patch_v2/files_app/files_internal.h b/patch_v2/files_app/files_internal.h index c3683de..1f9c9e1 100644 --- a/patch_v2/files_app/files_internal.h +++ b/patch_v2/files_app/files_internal.h @@ -54,7 +54,7 @@ int browser_on_tap(int x, int y); /* 1=已消费 */ void browser_swipe(int dir, int dist); void browser_touch_down(int x, int y); void browser_touch_move(int x, int y); -void browser_tick(uint64_t now); +void browser_on_long_press(int x, int y); /* SDK 通用长按(§6.3)→ 行 BottomSheet */ int browser_go_up(void); /* 1=回到上级;0=已在根(未动作) */ int browser_at_root(void); void browser_reload(void); diff --git a/patch_v2/files_app/ui/browser.c b/patch_v2/files_app/ui/browser.c index 2f02574..ed3d7ba 100644 --- a/patch_v2/files_app/ui/browser.c +++ b/patch_v2/files_app/ui/browser.c @@ -6,7 +6,9 @@ * 「图标 + 名称 + 大小/时间 + 右侧 ⋯ 操作钮」行:行数据源 browser_src2 逐行填充 * meow_list_item(icon_kind=目录/文件符号、副标题=大小/时间、right=⋯)。滚动/选中命中由 * SDK 收口(视口裁剪、局部 dirty、disabled 不响应),App 只在 on_tap/touch_move 驱动 - * scroll/sel 并分发:行体=进目录/开预览;右侧 ⋯ 或长按 → BottomSheet(删除/重命名/新建/属性); + * scroll/sel 并分发:行体=进目录/开预览;右侧 ⋯ → BottomSheet;**长按=SDK 通用长按事件 + * (§6.3,≥500ms 未抬手且位移≤容忍)** 由 SDK 判定后经 on_long_press 回调带坐标交给本页, + * App 据此行命中打开同一 BottomSheet(App 层长按计时逻辑已删,交给 SDK); * - 空态/错误(无卡/空目录/权限)与底部信息行用 meow_draw_custom_register 注册为非控件绘制物 * (app_draw_empty / app_draw_footer)——列表视口不含页脚,行不压页脚;invalidate/paint_full * 自动重画它们; @@ -28,10 +30,8 @@ static fsx_entry g_entries[FSX_MAX_ENTRIES]; static int g_n; /* 当前目录项数 */ static char g_cur[FSX_MAX_PATH]; static int g_err; /* fsx_scan_dir 返回(0=ok,<0=空态码) */ -static int g_press_row; /* 按下行(-1=无);长按检测 */ -static uint64_t g_press_ms; -static int g_press_tapped; /* 长按已消费本按 */ -static int g_press_delta; /* 按下后累计位移(>阈值取消长按) */ +static int g_press_row; /* 按下行(-1=无);按下高亮 + 拖动识别(长按已交 SDK §6.3) */ +static int g_press_delta; /* 按下后累计位移(>阈值清除按下高亮) */ static int g_dragging; /* 本次触摸起点是否在列表(拖动滚列表) */ static int g_drag_last_y; /* 拖拽上一帧 y */ @@ -45,6 +45,7 @@ static char g_title[FSX_MAX_PATH]; /* 预览/操作辅助(前向声明) */ static void browser_open_sheet(int row); +void browser_on_long_press(int x, int y); /* 跨 TU(app.c 经 §6.3 回调调用);files_internal.h */ static void browser_enter_dir(const char *path); static void browser_open_file(const char *path); static void browser_rename_entry(const fsx_entry *e); @@ -57,7 +58,6 @@ static void browser_report_ret(int r); static void browser_drag_reset(void) { g_press_row = -1; - g_press_tapped = 0; g_press_delta = 0; g_dragging = 0; g_drag_last_y = 0; @@ -312,7 +312,6 @@ static void browser_open_file(const char *path) int browser_on_tap(int x, int y) { - if (g_press_tapped) { browser_drag_reset(); return 1; } if (g_tb) { int ah = meow_topappbar_hit(g_tb, x, y); if (ah == MEOW_TOPBAR_BACK) { browser_drag_reset(); browser_go_up(); return 1; } @@ -344,7 +343,6 @@ void browser_touch_down(int x, int y) int row = meow_listview_hit(g_list, x, y); if (row >= 0) { g_press_row = row; - g_press_ms = meow_ui_time_ms(); meow_listview_set_sel(g_list, row); /* 按下高亮(§5.8.3 selected) */ } g_dragging = 1; @@ -357,13 +355,13 @@ void browser_touch_move(int x, int y) { (void)x; if (!g_dragging) return; - if (g_press_row >= 0) { /* 拖动取消长按 */ + if (g_press_row >= 0) { /* 拖动超阈值:清除按下高亮 */ int d = y - g_drag_last_y; if (d < 0) d = -d; - if (d > 0) g_press_delta += d; + g_press_delta += d; if (g_press_delta > 10) { g_press_row = -1; - meow_listview_set_sel(g_list, -1); /* 拖动取消按下高亮 */ + meow_listview_set_sel(g_list, -1); /* 拖动清除按下高亮 */ } } int dy = y - g_drag_last_y; @@ -386,15 +384,19 @@ void browser_swipe(int dir, int dist) meow_ui_invalidate(r_list.x, r_list.y, r_list.w, r_list.h); } -/* on_tick:长按(≥500ms 未拖动)→ 打开该行 BottomSheet。 */ -void browser_tick(uint64_t now) +/* SDK 通用长按(§6.3,≥阈值未抬手且位移≤容忍)→ 打开该行 BottomSheet。 + * App 层长按计时已删(g_press_ms/g_press_tapped 移除);长按触发后 SDK 已抑制其抬手 + * TAP(不再触发行体/打开动作),故这里无需再吞后续 tap;打开前清按下高亮与拖动态。 */ +void browser_on_long_press(int x, int y) { - if (g_press_row >= 0 && !g_press_tapped && now - g_press_ms >= 500) { - g_press_tapped = 1; - int row = g_press_row; - g_press_row = -1; - browser_open_sheet(row); /* 内部:写操作后自行 reload */ - } + if (g_err != 0 || g_n == 0) return; + if (!meow_widget_hit(g_list, x, y)) return; + int row = meow_listview_hit(g_list, x, y); + if (row < 0) return; + g_press_row = -1; + g_dragging = 0; + meow_listview_set_sel(g_list, -1); /* 清除按下高亮(sheet 打开在即) */ + browser_open_sheet(row); /* 内部:写操作后自行 reload */ } /* ---- 页 view 生命周期 ---- */ diff --git a/patch_v2/hello_app/hello b/patch_v2/hello_app/hello index 659611f..78c870b 100755 Binary files a/patch_v2/hello_app/hello and b/patch_v2/hello_app/hello differ diff --git a/patch_v2/sdk/meow_ui.h b/patch_v2/sdk/meow_ui.h index b150aef..d2a2f7b 100644 --- a/patch_v2/sdk/meow_ui.h +++ b/patch_v2/sdk/meow_ui.h @@ -173,6 +173,28 @@ void meow_ui_request_exit(void); /* 请求退出(相当于返回/ * x<=0 时清掉此前画的条(填回背景色)。不发 flush,由调用方 meow_ui_flush_region 上屏。 */ void meow_ui_swipe_hint(int x); +/* ============================================================ */ +/* §6.3 通用长按(2026-08-31) */ +/* ============================================================ */ +/* 控件按下 ≥ 阈值(默认 500ms,可配)未抬手、且按下后位移 ≤ 容忍(超移动/抬手取消) + * → 触发一次长按回调;坐标 = 触发时刻的按下点(App 据此 meow_widget_hit / meow_listview_hit + * 判定命中哪个控件/行,与 on_tap 同一套命中流程)。 + * + * **opt-in**:注册回调(meow_ui_set_long_press_cb)才启用长按判定;未注册时行为与之前完全一致 + * (按压不触发长按、抬手照常 TAP/SWIPE)——hello 等不使用长按的 App 零回归。 + * + * 与 pressed 状态机共存:按压视觉态(MEOW_WS_PRESSED / 按住加深)不变;短按(未达阈值或 + * 抬手先于阈值)仍正常触发 on_tap 点击;长按已触发后其抬手**不再产生 TAP/SWIPE**(SDK 抑制 + * 点击,App 无需自行吞掉后续 tap)。 + * + * 注册经独立 set 接口:**不做进 meow_ui_events**——避免给既有 App(hello/files)按位置初始化 + * meow_ui_events 的写法在 -Wextra 下引入「缺省成员」告警(_missing-field-initializers), + * 保持 SDK 头向后兼容。 */ +typedef void (*meow_long_press_fn)(int x, int y); +void meow_ui_set_long_press_cb(meow_long_press_fn cb); /* 注册/更换回调;NULL=取消 */ +void meow_ui_set_long_press_ms(int ms); /* 长按阈值(ms);<=0 恢复默认 500 */ +int meow_ui_long_press_ms(void); /* 当前阈值(默认 500) */ + /* ============================================================ */ /* §6.1 控件生命周期与归属(Activity 栈 + 三层控件模型) [源:2026-08-30] */ /* ============================================================ */ diff --git a/patch_v2/sdk/meow_ui_input.c b/patch_v2/sdk/meow_ui_input.c index c19d3e2..1629eb7 100644 --- a/patch_v2/sdk/meow_ui_input.c +++ b/patch_v2/sdk/meow_ui_input.c @@ -35,6 +35,8 @@ #define SWIPE_THRESH 65 /* px:抬起位移超过视为滑动(GESTURE_BACK: 90→65,用户实测 90 偏多,65 更顺手) */ #define EDGE_W 24 /* 左滑返回的「左缘起点」宽度(t_dx < EDGE_W 才认返回手势) */ +#define MEOW_LONG_PRESS_MS 500u /* §6.3 通用长按默认阈值(ms;meow_ui_set_long_press_ms 可配) */ +#define MEOW_LONG_PRESS_SLOP 12 /* §6.3 按下后「仍算按住」的最大位移(px);超此取消长按 */ #define QUEUE_N 128 #define MAX_DEVS 8 #define MEOW_APP_ACTIVITY_FILE "/tmp/meow_app_activity" /* M2.71-bug5:App 活动→launcher 重置息屏计时 */ @@ -129,6 +131,13 @@ static int t_origin_valid = 0; /* 原点是否已锁定 */ static uint64_t t_down_ms = 0; /* 按下时刻(monotonic ms),供实时按住时长用 */ /* M2.71-bug2/6:拖动过程事件上次上报坐标(坐标变化才入队 MEOW_EV_TOUCH_MOVE,防刷屏)。 */ static int t_mv_x = -1, t_mv_y = -1; +/* §6.3 通用长按:本次按压是否已触发过长按(触发后抬手不再产生 TAP/SWIPE)。 */ +static int t_long_fired = 0; + +/* §6.3 通用长按:阈值(默认 MEOW_LONG_PRESS_MS;App 可经 meow_ui_set_long_press_ms 配)。 */ +static int g_long_press_ms = MEOW_LONG_PRESS_MS; +/* §6.3 注册的长按回调(NULL=未注册;触发时以 (x,y) 调用,坐标=触发时刻按下点)。 */ +static meow_long_press_fn g_long_press_cb = NULL; /* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。 * t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */ @@ -142,6 +151,7 @@ static void touch_press(int x, int y) t_dx = 0; t_dy = 0; t_down_ms = now_ms(); /* 记按下时刻:供实时按住时长/按住视觉 */ t_mv_x = -1; t_mv_y = -1; /* M2.71:拖动过程起点未定 */ + t_long_fired = 0; /* §6.3:每次新按压复位长按触发标记 */ } /* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 * M2.71-bug2/6:原点锁定即报 MEOW_EV_TOUCH_DOWN(拖动起点);随后坐标变化报 MEOW_EV_TOUCH_MOVE @@ -165,11 +175,18 @@ static void touch_release(void) { if (!t_down) return; t_down = 0; t_press_valid = 0; + int was_long = t_long_fired; /* §6.3:本次按压是否已触发过长按(抬手不再产生点击/滑动) */ + t_long_fired = 0; /* 本次按下从未锁到有效坐标(幽灵触摸/坐标未就绪):丢弃,且清空全部轨迹。 */ if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1; return; } t_origin_valid = 0; int mdx = t_cx - t_dx, mdy = t_cy - t_dy; int adx = mdx < 0 ? -mdx : mdx, ady = mdy < 0 ? -mdy : mdy; + /* 长按已触发:本次手势由长按消费,抬手不再产生 TAP/SWIPE/NOTIF_PULL(抑制点击,§6.3)。 */ + if (was_long) { + t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1; + return; + } /* 手势判定只在「抬手(release)边沿」做:按下/移动过程不触发 SWIPE,仅报 PROGRESS。 * * 返回手势("左滑"语义):原点确实在左缘 + 向右移动 ≥SWIPE_THRESH。设备实际为 @@ -227,17 +244,52 @@ unsigned int meow_input_touch_hold_ms(void) void meow_input_touch_set(int down, int x, int y, unsigned hold_ms) { if (down) { - t_down = 1; t_press_valid = 1; t_origin_valid = 1; + t_down = 1; t_press_valid = 1; t_origin_valid = 1; t_long_fired = 0; t_cx = x; t_cy = y; t_dx = x; t_dy = y; uint64_t now = now_ms(); t_down_ms = (now > (uint64_t)hold_ms) ? (now - (uint64_t)hold_ms) : 0; } else { - t_down = 0; t_press_valid = 0; t_origin_valid = 0; + t_down = 0; t_press_valid = 0; t_origin_valid = 0; t_long_fired = 0; t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; t_down_ms = 0; t_mv_x = -1; t_mv_y = -1; } } +/* 宿主单测/内部注入:模拟按下后移动到 (x,y)(保原点、更新当前坐标;位移超长按容忍即取消)。 + * 对应输入线程 touch_update 的位移分支(这里不产生语义事件,仅更新坐标状态)。 */ +void meow_input_touch_move(int x, int y) +{ + if (!t_down || !t_origin_valid) return; + t_cx = x; t_cy = y; + t_mv_x = x; t_mv_y = y; +} + +/* 宿主单测/内部注入:模拟一次抬手(真实 touch_release;含长按后抑制 TAP/SWIPE)。 */ +void meow_input_touch_release(void) { touch_release(); } + +/* ---- §6.3 通用长按公共 API + 阈值跨越检测 ---- + * meow_input_long_press_poll:主循环(meow_ui_run 每帧)与宿主单测调用。读触摸状态机: + * 按下、原点/坐标已就绪、未触发过长按、位移 ≤ 容忍、按住时长 ≥ 阈值 → 置 t_long_fired, + * 入队 MEOW_EV_LONG_PRESS(a=x,b=y=当前按下点),返回 1;否则返回 0。幂等(一次按压一次)。 */ +void meow_ui_set_long_press_cb(meow_long_press_fn cb) { g_long_press_cb = cb; } +void meow_ui_set_long_press_ms(int ms) +{ g_long_press_ms = (ms > 0) ? ms : (int)MEOW_LONG_PRESS_MS; } +int meow_ui_long_press_ms(void) { return g_long_press_ms; } + +int meow_input_long_press_poll(void) +{ + if (!g_long_press_cb) return 0; /* 未注册回调:长按整体关闭,保持旧点击语义(短按/长按都 TAP) */ + if (!t_down || t_long_fired || !t_origin_valid || t_cx < 0 || t_cy < 0) return 0; + int mdx = t_cx - t_dx, mdy = t_cy - t_dy; + int amx = mdx < 0 ? -mdx : mdx, amy = mdy < 0 ? -mdy : mdy; + int move = amx > amy ? amx : amy; + if (move > MEOW_LONG_PRESS_SLOP) return 0; /* 位移超容忍:取消 */ + if (meow_input_touch_hold_ms() < (unsigned)g_long_press_ms) return 0; /* 未达阈值 */ + t_long_fired = 1; + push_ev(MEOW_EV_LONG_PRESS, t_cx, t_cy); + return 1; +} + /* ---- 输入线程:poll 所有 event 设备 + 解析 ---- */ static void *input_thread(void *arg) { @@ -338,8 +390,12 @@ void meow_ui_run(const meow_ui_events *ev) while (!g_exit) { /* agent-119:实时触摸按压/按住/释放 → 即时重绘命中按钮区(不等 app 重绘)。 - * IME 打开时软键盘为覆盖层且拦截触摸,不驱动背后按钮视觉,跳过。 */ - if (!meow_ime_active()) meow_widget_touch_refresh(); + * IME 打开时软键盘为覆盖层且拦截触摸,不驱动背后按钮视觉,跳过。 + * §6.3 通用长按:每帧检测「按下 ≥ 阈值且位移 ≤ 容忍」跨越(IME 打开不驱动;启动窗口不触发)。 */ + if (!meow_ime_active()) { + meow_widget_touch_refresh(); + if (now_ms() >= startup_cooldown_until) meow_input_long_press_poll(); + } meow_input_ev e; int got = 0; @@ -407,6 +463,12 @@ void meow_ui_run(const meow_ui_events *ev) if (g_events_set && g_events.on_swipe_progress) g_events.on_swipe_progress(x0, dx); meow_ui_flush_region(0, meow_ui_statusbar_height(), meow_ui_width(), 6); + } else if (e.type == MEOW_EV_LONG_PRESS) { + /* §6.3 通用长按:按下 ≥ 阈值未抬手触发。坐标=触发时刻按下点(App 用 + * meow_widget_hit/meow_listview_hit 判定命中控件/行)。已注册回调则分发; + * 其抬手已被 touch_release 抑制(不再产生 TAP/SWIPE),此处无需吞后续 tap。 */ + if (in_startup) continue; + if (g_long_press_cb) g_long_press_cb(e.a, e.b); } } diff --git a/patch_v2/sdk/meow_ui_internal.h b/patch_v2/sdk/meow_ui_internal.h index 9e7fc22..2dac50f 100644 --- a/patch_v2/sdk/meow_ui_internal.h +++ b/patch_v2/sdk/meow_ui_internal.h @@ -142,6 +142,7 @@ int meow_gfx_mfont_ready(void); #define MEOW_EV_SWIPE_PROGRESS 4 /* a=按下X(t_dx), b=当前X(t_cx);dx=b-a(>0=左缘向内划=返回手势) */ #define MEOW_EV_TOUCH_DOWN 6 /* a=x, b=y:按下后首个有效坐标(拖动起点,M2.71-bug2/6) */ #define MEOW_EV_TOUCH_MOVE 7 /* a=x, b=y:拖动过程坐标变化(坐标变才入队;跟手用) */ +#define MEOW_EV_LONG_PRESS 8 /* a=x, b=y:按下 ≥ 阈值未抬手(长按触发;其抬手不再产生 TAP/SWIPE) */ typedef struct meow_input_ev { uint8_t type; @@ -175,6 +176,15 @@ unsigned int meow_input_touch_hold_ms(void); /* 按下已持续毫秒 * (把按下时刻回拨),便于测按住态;down=0 复位。设备运行时无调用方。 */ void meow_input_touch_set(int down, int x, int y, unsigned hold_ms); +/* ---- 通用长按(§6.3,2026-08-31)---- + * 主循环/宿主调用:检测「按下 ≥ 阈值且位移 ≤ 容忍」的跨越;触发则入队 MEOW_EV_LONG_PRESS + * 并返回 1(幂等:一次按压只触发一次)。供 meow_ui_run 每帧调用与宿主单测模拟时序。 */ +int meow_input_long_press_poll(void); +/* 宿主单测/内部注入:模拟一次抬手(真实 touch_release;含长按后抑制 TAP/SWIPE)。设备运行时无调用方。 */ +void meow_input_touch_release(void); +/* 宿主单测/内部注入:模拟按下后移动到 (x,y)(保原点、更新当前坐标;位移超长按容忍即取消)。设备运行时无调用方。 */ +void meow_input_touch_move(int x, int y); + /* ---- 控件层实时刷新(meow_ui_widget.c)---- * 每帧由 meow_ui_run 调用(主线程):实时触摸按下/释放/按住达阈值时,重绘命中的按钮区 * 并局部上屏,让按压/按住/释放视觉即时反映,无需 app 在 on_tick/on_tap 重绘(agent-119)。 diff --git a/patch_v2/sdk/meow_ui_widget.c b/patch_v2/sdk/meow_ui_widget.c index b3aa381..e3fc97b 100644 --- a/patch_v2/sdk/meow_ui_widget.c +++ b/patch_v2/sdk/meow_ui_widget.c @@ -23,9 +23,9 @@ enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3, /* 容器(TopAppBar 动作 / NavigationBar 项 / TabBar 页签)子项上限。 */ #define MEOW_CONTAINER_MAX 8 -/* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层)。 - * 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */ -#define MEOW_BTN_HOLD_MS 500u +/* 按钮「按住」判定阈值:视觉上区分「短按按下」与「按住」(更深状态层)。 + * 阈值取 meow_ui_long_press_ms()(§6.3 通用长按默认 500ms,App 可配)——与长按事件同源, + * 避免「长按已触发但视觉未加深」的不一致。触发语义不变(on_tap 仍按抬手判定)。 */ /* 圆形 ProgressBar 环带厚 / 外扩边距(bgg 修复 M2.71-bug1:控件几何必须覆盖真实像素)。 * 实际像素范围 = 中心 ± (r + thk/2);margin = thk/2 + 1 保证外接框覆盖环带外缘与圆帽。 */ @@ -249,7 +249,7 @@ void meow_widget_touch_refresh(void) } int down = meow_input_touch_down(); meow_widget *hit = down ? live_btn_hit(meow_input_touch_x(), meow_input_touch_y()) : NULL; - int hold = down && hit && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS; + int hold = down && hit && meow_input_touch_hold_ms() >= (unsigned)meow_ui_long_press_ms(); if (!down) { /* 释放:把最近按压按钮恢复为常态 */ @@ -1218,7 +1218,7 @@ void meow_widget_draw(meow_widget *w) meow_input_touch_x() >= w->x && meow_input_touch_x() < w->x + w->w && meow_input_touch_y() >= w->y && meow_input_touch_y() < w->y + w->h; int press = (w->state & MEOW_WS_PRESSED) || w->pressed || live; - int hold = live && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS; + int hold = live && meow_input_touch_hold_ms() >= (unsigned)meow_ui_long_press_ms(); if (disabled) press = 0; /* §5.8.6 五型:filled=primary/onPrimary;tonal=secondaryContainer/onSecondaryContainer; * outlined=透明底+primary 文+outline 描边;text=透明底+primary 文;elevated=surfaceContainerLow+primary 文。 diff --git a/patch_v2/sdk/test_ime_fix b/patch_v2/sdk/test_ime_fix new file mode 100755 index 0000000..87d348a Binary files /dev/null and b/patch_v2/sdk/test_ime_fix differ diff --git a/patch_v2/sdk/test_meow_ui_widgets b/patch_v2/sdk/test_meow_ui_widgets new file mode 100755 index 0000000..6b0b860 Binary files /dev/null and b/patch_v2/sdk/test_meow_ui_widgets differ diff --git a/patch_v2/sdk/test_meow_ui_widgets.c b/patch_v2/sdk/test_meow_ui_widgets.c index e111176..266edb8 100644 --- a/patch_v2/sdk/test_meow_ui_widgets.c +++ b/patch_v2/sdk/test_meow_ui_widgets.c @@ -92,6 +92,9 @@ static void free_all(void) g_wn = 0; } +/* §6.3 通用长按测试回调(开闸长按判定用;事件本身经 meow_input_pop 断言)。 */ +static void lp_recv(int x, int y) { (void)x; (void)y; } + int main(void) { setenv("MEOW_UI_DUMP", "/tmp/b2.ppm", 1); @@ -301,6 +304,63 @@ int main(void) meow_sheet_free(sh); printf("PASS dialog/sheet: create/set_items/add_item (blocking show skipped)\n"); + /* ---------- §6.3 通用长按:触发/取消/短按不受影响(模拟时序) ---------- + * 用 meow_input_touch_set(按下+已按住时长回拨)驱动 SDK 触摸状态机,meow_input_long_press_poll + * 检测阈值跨越;meow_input_touch_release 走真实 touch_release(含长按后抑制 TAP/SWIPE)。 + * 断言:① 长按触发(poll=1 + 入队 LONG_PRESS);② 长按后抬手不再产生 TAP;③ 短按不受影响; + * ④ 移动超容忍取消长按;⑤ 阈值可配;⑥ 未注册回调 = 长按关闭(opt-in,hello 等零回归)。 */ + { + meow_input_ev e; + meow_input_drain(); + meow_input_touch_set(0, 0, 0, 0); /* 复位(清任何残留按住态) */ + meow_ui_set_long_press_ms(500); /* 显式回默认,防其它段干扰 */ + + /* ⑥ opt-in:未注册回调 → 长按判定关闭:poll=0(即使 hold≥阈值),抬手照常 TAP。 */ + assert(meow_input_long_press_poll() == 0); + meow_input_touch_set(1, 130, 310, 700); + assert(meow_input_long_press_poll() == 0); /* 未注册回调:不触发 */ + meow_input_touch_release(); + assert(meow_input_pop(&e) == 1 && e.type == MEOW_EV_TAP); + meow_ui_set_long_press_cb(lp_recv); + + /* ① 长按触发:hold=700ms(≥500)→ poll=1,LONG_PRESS 入队且坐标=按下点。 */ + meow_input_touch_set(1, 120, 300, 700); + assert(meow_input_long_press_poll() == 1); + assert(meow_input_pop(&e) == 1 && e.type == MEOW_EV_LONG_PRESS && e.a == 120 && e.b == 300); + + /* ② 长按后抬手:点击被抑制(不再产生 TAP)。 */ + meow_input_touch_release(); + assert(meow_input_pop(&e) == 0); + + /* ③ 短按不受影响:hold=100ms(<500)→ poll=0(不触发);抬手 → TAP 正常。 */ + meow_input_touch_set(1, 200, 400, 100); + assert(meow_input_long_press_poll() == 0); + meow_input_touch_release(); + assert(meow_input_pop(&e) == 1 && e.type == MEOW_EV_TAP && e.a == 200 && e.b == 400); + + /* ④ 长按取消(移动超容忍):hold=700ms 但按下后位移 30px(>12)→ poll=0;抬手仍 TAP + * (位移 < SWIPE_THRESH 65,手势判定为点击)。 */ + meow_input_touch_set(1, 300, 500, 700); + meow_input_touch_move(300 + 30, 500); + assert(meow_input_long_press_poll() == 0); + meow_input_touch_release(); + assert(meow_input_pop(&e) == 1 && e.type == MEOW_EV_TAP); + + /* ⑤ 阈值可配:设 200ms → hold=250ms 也触发;恢复默认。 */ + meow_ui_set_long_press_ms(200); + assert(meow_ui_long_press_ms() == 200); + meow_input_touch_set(1, 180, 260, 250); + assert(meow_input_long_press_poll() == 1); + meow_input_touch_release(); + meow_ui_set_long_press_ms(500); + assert(meow_ui_long_press_ms() == 500); + + meow_ui_set_long_press_cb(NULL); /* 复位,避免影响其它段 */ + meow_input_touch_set(0, 0, 0, 0); /* 复位,避免影响后续绘制 */ + meow_input_drain(); + printf("PASS long-press: trigger/suppress-tap/short-tap/cancel-move/configurable/opt-in\n"); + } + /* ---------- 绘制所有控件到 back_buf(5x7 兜底),flush 出屏幕 ---------- */ const meow_theme *th = meow_theme_get(); meow_ui_clear(th->bg); diff --git a/patch_v2/sdk/test_p1p2p3_repro b/patch_v2/sdk/test_p1p2p3_repro new file mode 100755 index 0000000..591539e Binary files /dev/null and b/patch_v2/sdk/test_p1p2p3_repro differ