diff --git a/patch_v2/hello_app/hello b/patch_v2/hello_app/hello index 074f141..4b98a74 100755 Binary files a/patch_v2/hello_app/hello and b/patch_v2/hello_app/hello differ diff --git a/patch_v2/hello_app/hello.c b/patch_v2/hello_app/hello.c index 86489e2..388a014 100644 --- a/patch_v2/hello_app/hello.c +++ b/patch_v2/hello_app/hello.c @@ -79,6 +79,14 @@ static int g_demo_ct = 0; /* 内容视口顶(屏幕 y,= statusbar+5 static int g_demo_cb = 0; /* 内容视口底(屏幕 y,=608,NavBar 上方留白) */ static int g_demo_content_h = 0; /* 内容总高(base 空间;决定 max_scroll) */ +/* M2.71-bug2/6:拖动过程(跟手)状态。on_touch_down 锁一种拖拽,on_touch_move 期间实时更新。 */ +static int g_drag_slider = 0; /* 手指按在 slider:拖拽改值(按即定位) */ +static int g_drag_list = 0; /* 手指按在 LazyColumn:拖拽滚列表 */ +static int g_drag_page = 0; /* 手指按在页内容(非 slider/list):拖拽滚页面 */ +static int g_drag_list_last_y = 0; /* 列表拖拽上一帧 y(求增量) */ +static int g_drag_page_last_y = 0; /* 页面拖拽上一帧 y */ +static int g_drag_page_delta = 0; /* 页面拖拽累计位移(节流重建,≥8px 才重建一次) */ + /* SDK 控件不透明:App 自记几何,用于局部 flush / 命中 / scroll 重建。 */ typedef struct { int x, y, w, h; } rect_t; #define RX(x,y,w,h) ((rect_t){ (x), (y), (w), (h) }) @@ -86,8 +94,8 @@ typedef struct { int x, y, w, h; } rect_t; /* demo 各控件跨重建保留的状态(app 持有;重建后按此恢复) */ static struct { int sw_on; int slider; char tf[32]; - int ck; int r1, r2; int btn[5]; int card; int tab; int list; int prog; -} g_ds = { .sw_on = 1, .slider = 40, .r1 = 1, .tab = 1, .list = 0, .prog = 0 }; + int ck; int r1, r2; int btn[5]; int card; int tab; int list; int list_sel; int prog; +} g_ds = { .sw_on = 1, .slider = 40, .r1 = 1, .tab = 1, .list = 0, .list_sel = 0, .prog = 0 }; /* demo 内容布局常量(base 空间) */ #define DCL_X 16 @@ -136,6 +144,7 @@ static meow_sheet *g_sheet; static void redraw(void); static void demo_rebuild_to(int scroll); static void demo_paint(void); +static void demo_slider_drag(int x); /* M2.51:统一日志改用 SDK meow_log()。 */ static void on_sigterm(int s) { (void)s; meow_ui_request_exit(); } @@ -280,7 +289,11 @@ static void demo_build_at(int scroll) /* ---- ProgressBar(线性 + 圆形,on_tick 动画) ---- */ d_title(DCL_X, SY(yb), "PROGRESS (animated)"); yb += 26; r_prl = RX(DCL_X, SY(yb), DFW, 8); g_dprl = meow_progress_create(r_prl.x, r_prl.y, r_prl.w, r_prl.h, g_ds.prog); SLOT(g_dprl, r_prl.x, r_prl.y, r_prl.w, r_prl.h); yb += 16; - r_prc = RX(DCL_X + 400, SY(yb), 34, 34); g_dprc = meow_progress_circular_create(r_prc.x + 17, r_prc.y + 17, 17, g_ds.prog); SLOT(g_dprc, r_prc.x, r_prc.y, r_prc.w, r_prc.h); yb += 48; + /* 圆形进度:r_prc = 整圆像素外接框(中心 ± (r+margin),r=17 → 40×40),使 d_redraw 的 + * flush 区与控件 bbox(=真实绘制范围)一致,不漏刷外圈(bgg M2.71-bug1)。中心仍取原视觉点。 */ + r_prc = RX(DCL_X + 400 + 17 - 20, SY(yb) + 17 - 20, 40, 40); + g_dprc = meow_progress_circular_create(r_prc.x + 20, r_prc.y + 20, 17, g_ds.prog); + SLOT(g_dprc, r_prc.x, r_prc.y, r_prc.w, r_prc.h); yb += 48; /* ---- Container(Card / Badge / TabBar) ---- */ d_title(DCL_X, SY(yb), "CONTAINER"); yb += 26; @@ -311,6 +324,7 @@ static void demo_build_at(int scroll) r_list = RX(DCL_X, SY(yb), DFW, 150); g_dlc = meow_lazycolumn_create(r_list.x, r_list.y, r_list.w, r_list.h, 40, 100, demo_list_src, NULL); SLOT(g_dlc, r_list.x, r_list.y, r_list.w, r_list.h); meow_list_set_scroll(g_dlc, g_ds.list); + meow_list_set_selected(g_dlc, g_ds.list_sel); /* M2.71-bug8:跨重建保留选中行 */ yb += 158; g_demo_content_h = yb; @@ -383,20 +397,7 @@ static int demo_content_tap(int x, int y) if (meow_widget_hit(g_dr2, x, y)) { meow_widget_set_selected(g_dr2, 1); meow_widget_set_selected(g_dr1, 0); g_ds.r2 = 1; g_ds.r1 = 0; d_redraw(g_dr1, r_dr1); d_redraw(g_dr2, r_dr2); return 1; } if (meow_widget_hit(g_dcb, x, y)) { d_toggle_sel(g_dcb, r_dcb); g_ds.ck = meow_widget_selected(g_dcb); return 1; } if (meow_widget_hit(g_dsw, x, y)) { g_ds.sw_on = !meow_switch_get(g_dsw); meow_switch_set(g_dsw, g_ds.sw_on); d_redraw(g_dsw, r_dsw); return 1; } - if (meow_widget_hit(g_dslider, x, y)) { - int frac = x - r_slider.x; if (frac < 0) frac = 0; if (frac > r_slider.w) frac = r_slider.w; - g_ds.slider = frac * 100 / r_slider.w; - meow_slider_set(g_dslider, g_ds.slider); - d_redraw(g_dslider, r_slider); - { - const meow_theme *th = meow_theme_get(); - char buf[28]; snprintf(buf, sizeof buf, "slider = %d", g_ds.slider); - meow_ui_fill_rect(r_slider_txt.x, r_slider_txt.y, r_slider_txt.w, r_slider_txt.h, th->bg); - meow_ui_text(r_slider_txt.x, r_slider_txt.y, buf, 20, th->dim); - meow_ui_flush_region(r_slider_txt.x, r_slider_txt.y, r_slider_txt.w, r_slider_txt.h); - } - return 1; - } + if (meow_widget_hit(g_dslider, x, y)) { demo_slider_drag(x); return 1; } if (meow_widget_hit(g_dtf, x, y)) { meow_textfield_activate(g_dtf); /* 阻塞 IME,返回后刷新 + 状态回写 */ strncpy(g_ds.tf, meow_textfield_text(g_dtf), sizeof g_ds.tf - 1); @@ -411,7 +412,12 @@ static int demo_content_tap(int x, int y) } if (meow_widget_hit(g_dbtn_dlg, x, y)) { meow_dialog_show(g_dlg); meow_log(MEOW_LOG_INFO, "dialog result=%d", meow_dialog_result(g_dlg)); d_redraw(g_dbtn_dlg, r_dlg); return 1; } if (meow_widget_hit(g_dbtn_sheet, x, y)) { int r = meow_sheet_open(g_sheet); meow_log(MEOW_LOG_INFO, "sheet result=%d", r); d_redraw(g_dbtn_sheet, r_sheet); return 1; } - if (meow_widget_hit(g_dlc, x, y)) { g_demo_focus = 1; return 1; } + if (meow_widget_hit(g_dlc, x, y)) { + g_demo_focus = 1; + int li = meow_list_tap_at(g_dlc, x, y); /* M2.71-bug8:命中行→置选中并高亮 */ + if (li >= 0) { g_ds.list_sel = li; d_redraw(g_dlc, r_list); } + return 1; + } return 0; } @@ -450,14 +456,64 @@ static void demo_tick(uint64_t now) meow_progress_set(g_dprc, g_demo_prog); d_redraw(g_dprc, r_prc); } -/* demo 页 on_swipe:LEFT=回 home;UP/DOWN=列表聚焦滚列表,否则页滚动 */ +/* 按触碰 X 落 slider 值并局部刷(按下即定位 + 拖动实时,M2.71-bug2) */ +static void demo_slider_drag(int x) +{ + int frac = x - r_slider.x; if (frac < 0) frac = 0; if (frac > r_slider.w) frac = r_slider.w; + g_ds.slider = frac * 100 / r_slider.w; + meow_slider_set(g_dslider, g_ds.slider); + d_redraw(g_dslider, r_slider); + const meow_theme *th = meow_theme_get(); + char buf[28]; snprintf(buf, sizeof buf, "slider = %d", g_ds.slider); + meow_ui_fill_rect(r_slider_txt.x, r_slider_txt.y, r_slider_txt.w, r_slider_txt.h, th->bg); + meow_ui_text(r_slider_txt.x, r_slider_txt.y, buf, 20, th->dim); + meow_ui_flush_region(r_slider_txt.x, r_slider_txt.y, r_slider_txt.w, r_slider_txt.h); +} + +/* demo 页 on_touch_down:锁一种拖拽(slider > list > 页内容)。非 demo 态跳过。 */ +static void demo_touch_down(int x, int y) +{ + g_drag_slider = 0; g_drag_list = 0; g_drag_page = 0; g_drag_page_delta = 0; + if (!g_demo) return; + if (meow_widget_hit(g_dslider, x, y)) { g_drag_slider = 1; demo_slider_drag(x); return; } + /* 命中列表,或「已聚焦列表」时(g_demo_focus=1)→ 拖动滚列表(与原 on_swipe 的焦点语义一致) */ + if (g_dlc && (meow_widget_hit(g_dlc, x, y) || g_demo_focus)) { + g_demo_focus = 1; g_drag_list = 1; g_drag_list_last_y = y; return; + } + /* 页内容视口内非 slider/list:页面拖拽跟手(按钮亦视为可滚动起点,阈值由 SDK tap/swipe 分流) */ + if (y >= g_demo_ct && y < g_demo_cb && x >= 0 && x < meow_ui_width()) { + g_drag_page = 1; g_drag_page_last_y = y; + } +} + +/* demo 页 on_touch_move:拖动过程实时更新(slider 值 / 列表滚动 / 页面滚动节流重建)。 */ +static void demo_touch_move(int x, int y) +{ + if (!g_demo) return; + if (g_drag_slider) { demo_slider_drag(x); return; } + if (g_drag_list) { + int dy = y - g_drag_list_last_y; g_drag_list_last_y = y; + if (dy) { meow_list_scroll_by(g_dlc, -dy); g_ds.list = meow_list_scroll(g_dlc); d_redraw(g_dlc, r_list); } + return; + } + if (g_drag_page) { + int dy = y - g_drag_page_last_y; g_drag_page_last_y = y; + g_drag_page_delta += -dy; /* 手指上移(y 减) → scroll 增(显示更低内容),与 on_swipe 一致 */ + if (g_drag_page_delta >= 8 || g_drag_page_delta <= -8) { + demo_rebuild_to(g_demo_scroll + g_drag_page_delta); + g_drag_page_delta = 0; + } + } +} + +/* demo 页 on_swipe:LEFT=回 home;UP/DOWN=列表聚焦滚列表,否则页滚动。 + * M2.71-bug6:拖动过程已在 on_touch_move 实时跟手滚过,抬手不再重复滚(否则叠加一次全距跳变)。 */ static void demo_swipe(int dir, int dist) { if (dir == MEOW_SWIPE_LEFT) { g_demo = 0; meow_ui_activity_pop(); redraw(); return; } - int del = 0; - if (dir == MEOW_SWIPE_UP) del = dist; /* 手指上移 → 偏移增大 */ - else if (dir == MEOW_SWIPE_DOWN) del = -dist; - if (del == 0) return; + if (dir != MEOW_SWIPE_UP && dir != MEOW_SWIPE_DOWN) return; + if (g_drag_page || g_drag_list) return; /* 本次手势已实时跟手滚动过 */ + int del = (dir == MEOW_SWIPE_UP) ? dist : -dist; /* 手指上移 → 偏移增大 */ if (g_demo_focus && g_dlc) { meow_list_scroll_by(g_dlc, del); g_ds.list = meow_list_scroll(g_dlc); @@ -558,6 +614,16 @@ static void on_swipe_progress(int x_start, int dx) meow_ui_swipe_hint(x_start + dx); } +/* M2.71-bug2/6:拖动过程回调(只对 demo 页有意义;home/全屏不拖拽)。 */ +static void on_touch_down(int x, int y) +{ + if (g_demo) demo_touch_down(x, y); +} +static void on_touch_move(int x, int y) +{ + if (g_demo) demo_touch_move(x, y); +} + static void on_swipe(int dir, int dx, int dy) { (void)dy; @@ -608,7 +674,8 @@ int main(void) demo_init(); redraw(); - meow_ui_events ev = { on_key, on_tap, on_swipe, on_tick, on_swipe_progress }; + meow_ui_events ev = { on_key, on_tap, on_swipe, on_tick, on_swipe_progress, + on_touch_down, on_touch_move }; meow_ui_run(&ev); meow_widget_free(g_demo_btn); diff --git a/patch_v2/meow_launcherd/app_launcher.c b/patch_v2/meow_launcherd/app_launcher.c index 44bae0a..719f25e 100644 --- a/patch_v2/meow_launcherd/app_launcher.c +++ b/patch_v2/meow_launcherd/app_launcher.c @@ -909,6 +909,18 @@ int launch_app(struct app *a) } } } + /* M2.71-bug5:App 前台触摸/交互(launcher 看不到应用内触摸,SDK 写标记)→ 重置自动息屏 + * 计时;若已自动息屏,按 key-wake 同款路径唤醒(面板上电 + 落 buffer0)。 */ + if (access(MEOW_APP_ACTIVITY_FILE, F_OK) == 0) { + unlink(MEOW_APP_ACTIVITY_FILE); + app_last_input = now_ms(); + if (meow_auto_slept) { + logmsg("auto screen-off (app): touch wake"); + meow_panel_on(); + meow_auto_slept = 0; + fb_pan_buf0_force(); + } + } if (vold && !app_prev_vold) { unsigned long long nw = now_ms(); if (app_seq_hits == 0) app_vol0 = vol_snapshot(); diff --git a/patch_v2/meow_launcherd/meow_launcher_internal.h b/patch_v2/meow_launcherd/meow_launcher_internal.h index d724470..69089e3 100644 --- a/patch_v2/meow_launcherd/meow_launcher_internal.h +++ b/patch_v2/meow_launcherd/meow_launcher_internal.h @@ -234,6 +234,8 @@ extern "C" { #define MEOW_IPC_CONNS 10 /* 同时接入连接上限(backlog 4 + 余量) */ #define MEOW_IPC_BACKLOG 4 #define MEOW_SBAR_MODE_FILE "/tmp/meowapp_sbar" +/* M2.71-bug5:App 前台触摸/交互 → SDK 写该标记;launcher 前台 app 循环消费(unlink + 重置息屏计时) */ +#define MEOW_APP_ACTIVITY_FILE "/tmp/meow_app_activity" /* ---- 共享结构体/枚举(来自 meow_launcherd.c,拆分后统一放置)---- */ struct kd_state { int raw, stable, down; int longpressed; unsigned long long since, down_at; }; diff --git a/patch_v2/meow_launcherd/meow_launcherd b/patch_v2/meow_launcherd/meow_launcherd index 4a5ee31..3a7b65f 100755 Binary files a/patch_v2/meow_launcherd/meow_launcherd and b/patch_v2/meow_launcherd/meow_launcherd differ diff --git a/patch_v2/sdk/meow_ui.h b/patch_v2/sdk/meow_ui.h index 6b16ef0..5813388 100644 --- a/patch_v2/sdk/meow_ui.h +++ b/patch_v2/sdk/meow_ui.h @@ -158,6 +158,11 @@ typedef struct meow_ui_events { void (*on_swipe)(int dir, int dx, int dy); /* 左滑=返回(左缘起点+抬手触发) */ void (*on_tick)(void); /* 空闲周期回调(动画/时钟) */ void (*on_swipe_progress)(int x_start, int dx);/* 左缘返回手势过程实时回调;x_start=按下X, dx=当前X-按下X */ + /* M2.71-bug2/6:拖动过程回调(跟手)。on_touch_down=按下后首个有效坐标(起点); + * on_touch_move=拖动中坐标变化(坐标变了才回调)。用于 slider 拖动 / 列表/页面滚动跟手。 + * 均在 IME 弹出或模态阻塞时被吞(键盘/模态优先喂)。可 NULL。 */ + void (*on_touch_down)(int x, int y); + void (*on_touch_move)(int x, int y); } meow_ui_events; void meow_ui_run(const meow_ui_events *ev); /* 阻塞:事件循环,直到 meow_ui_request_exit */ @@ -500,6 +505,13 @@ int meow_list_max_scroll(meow_widget *w); /* max(0, count*item - void meow_list_scroll_by(meow_widget *w, int dy); /* 相对滚动(App on_swipe 用) */ int meow_list_set_item_h(meow_widget *w, int item); /* 改行高/列宽 */ int meow_list_hit_index(meow_widget *w, int x, int y); /* 命中项下标(含滚动换算);-1=未命中项/越界 */ +/* M2.71-bug8:列表行选中 + 行点击回调。选中行已有高亮(现有绘制读 w->sel)。 + * meow_list_tap_at:命中一行则置选中并回调 tap_cb(若设),返回该行 idx;未命中返回 -1。 */ +void meow_list_set_selected(meow_widget *w, int idx); /* 选中行(越界 clamp);标脏 */ +int meow_list_selected(meow_widget *w); +typedef void (*meow_list_tap_fn)(int idx, void *ctx); +void meow_list_set_on_tap(meow_widget *w, meow_list_tap_fn cb, void *ctx); /* 命中行→回调 */ +int meow_list_tap_at(meow_widget *w, int x, int y); /* 命中行:置选中+回调,返回 idx/-1 */ /* ============================================================ */ /* §5.8.6 模态(App 层,非系统叠加):Dialog / BottomSheet diff --git a/patch_v2/sdk/meow_ui_dialog.c b/patch_v2/sdk/meow_ui_dialog.c index 3facc68..bca80c6 100644 --- a/patch_v2/sdk/meow_ui_dialog.c +++ b/patch_v2/sdk/meow_ui_dialog.c @@ -20,6 +20,39 @@ #define MEOW_MODAL_ROW 48 #define MEOW_MODAL_BTN 44 +/* M2.71-bug3:模态(Dialog/BottomSheet)背景快照/恢复。 + * 旧实现直接往 back_buf 画 scrim+卡片并 flush:①每次 open 再现叠一层 α150 遮罩(叠加变黑); + * ②关闭后 scrim+卡片像素留在屏上(残影);③要整屏刷新才盖掉。此处 open 前把整帧背景拍下, + * 关闭后回填 + 整帧上屏 → scrim 每次只叠一层、关闭即还原、无残留。 */ +static unsigned char *g_modal_cap = NULL; +static int g_modal_cap_line = 0, g_modal_cap_h = 0, g_modal_cap_bpp = 0; + +static void modal_capture(void) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(); + int h = meow_ui_height(); + int bpp = meow_ui_back_buf_bpp(); + if (!bb || line <= 0 || h <= 0) { g_modal_cap = NULL; return; } + size_t n = (size_t)line * (size_t)h; + if (!g_modal_cap || g_modal_cap_line != line || g_modal_cap_h != h || g_modal_cap_bpp != bpp) { + free(g_modal_cap); + g_modal_cap = (unsigned char *)malloc(n); + g_modal_cap_line = line; g_modal_cap_h = h; g_modal_cap_bpp = bpp; + } + if (g_modal_cap) memcpy(g_modal_cap, bb, n); +} + +static void modal_restore(void) +{ + unsigned char *bb = meow_ui_back_buf(); + if (!g_modal_cap || !bb) return; + size_t n = (size_t)g_modal_cap_line * (size_t)g_modal_cap_h; + memcpy(bb, g_modal_cap, n); + meow_gfx_mark_full(); + meow_ui_flush(); +} + /* ---- Dialog ---- */ struct meow_dialog { meow_dlg_t type; @@ -62,58 +95,73 @@ static void dlg_calc(meow_dialog *d, const meow_theme *th, dlg_geom *g) g->btn_w = (g->dw - 2 * pad - 12) / 2; } -static void dlg_paint(meow_dialog *d, const meow_theme *th) +/* 只画卡片内容(不含遮罩)——供「仅高亮变化」时重绘卡片区,不会把遮罩再叠一层(防叠加变黑)。 */ +static void dlg_card(meow_dialog *d, const meow_theme *th, const dlg_geom *g) { - int W = meow_ui_width(), H = meow_ui_height(); - meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150); /* 遮罩压暗 */ - dlg_geom g; - dlg_calc(d, th, &g); int pad = 24; - meow_gfx_fill_round(g.dx, g.dy, g.dw, g.dh, th->radius_card, th->surfaceContainerHighest); - meow_ui_draw_rect_round(g.dx, g.dy, g.dw, g.dh, th->radius_card, th->outline); - meow_ui_text(g.dx + pad, g.dy + pad, d->title, th->font_title, th->onSurface); + meow_gfx_fill_round(g->dx, g->dy, g->dw, g->dh, th->radius_card, th->surfaceContainerHighest); + meow_ui_draw_rect_round(g->dx, g->dy, g->dw, g->dh, th->radius_card, th->outline); + meow_ui_text(g->dx + pad, g->dy + pad, d->title, th->font_title, th->onSurface); if (d->type == MEOW_DLG_SELECT) { for (int i = 0; i < d->nitems; i++) { - int ry = g.row_y + i * g.rowh; - if (ry + g.rowh > g.dy + g.dh - 12) break; + int ry = g->row_y + i * g->rowh; + if (ry + g->rowh > g->dy + g->dh - 12) break; if (i == d->cur) { - meow_ui_fill_rect(g.dx + 8, ry, g.dw - 16, g.rowh, th->primary); - meow_ui_text(g.dx + pad, ry + (g.rowh - meow_ui_text_height(th->font_body)) / 2, + meow_ui_fill_rect(g->dx + 8, ry, g->dw - 16, g->rowh, th->primary); + meow_ui_text(g->dx + pad, ry + (g->rowh - meow_ui_text_height(th->font_body)) / 2, d->items[i], th->font_body, 0xFFFFFF); } else { - meow_ui_text(g.dx + pad, ry + (g.rowh - meow_ui_text_height(th->font_body)) / 2, + meow_ui_text(g->dx + pad, ry + (g->rowh - meow_ui_text_height(th->font_body)) / 2, d->items[i], th->font_body, th->onSurface); } - meow_ui_fill_rect(g.dx + pad, ry + g.rowh - 1, g.dw - 2 * pad, 1, th->outlineVariant); + meow_ui_fill_rect(g->dx + pad, ry + g->rowh - 1, g->dw - 2 * pad, 1, th->outlineVariant); } } else { /* INFO/CONFIRM:正文行;INPUT:文本预览 */ int nmsg = (d->type == MEOW_DLG_INFO) ? (d->nitems > 0 ? d->nitems : 1) : (d->nitems > 0 ? d->nitems : 1); - int y = g.row_y; + int y = g->row_y; if (d->type == MEOW_DLG_INPUT) { char buf[300]; snprintf(buf, sizeof buf, "%s", d->ime.buf[0] ? d->ime.buf : "(tap OK to edit...)"); - meow_ui_text(g.dx + pad, y, buf, th->font_body, th->onSurfaceVariant); + meow_ui_text(g->dx + pad, y, buf, th->font_body, th->onSurfaceVariant); } else { for (int i = 0; i < nmsg; i++) { if (i < d->nitems && d->items[i][0]) - meow_ui_text(g.dx + pad, y, d->items[i], th->font_body, th->onSurfaceVariant); + meow_ui_text(g->dx + pad, y, d->items[i], th->font_body, th->onSurfaceVariant); y += meow_ui_text_height(th->font_body) + 8; } } /* 取消(左,text 型)/ 确定(右,filled) */ - int bx = g.dx + pad, cx = bx + g.btn_w + 12, by = g.btn_y; - meow_ui_draw_rect_round(bx, by, g.btn_w, g.btn_h, 10, th->primary); - meow_ui_text_center(bx + g.btn_w / 2, by + (g.btn_h - meow_ui_text_height(th->font_body)) / 2, + int bx = g->dx + pad, cx = bx + g->btn_w + 12, by = g->btn_y; + meow_ui_draw_rect_round(bx, by, g->btn_w, g->btn_h, 10, th->primary); + meow_ui_text_center(bx + g->btn_w / 2, by + (g->btn_h - meow_ui_text_height(th->font_body)) / 2, "CANCEL", th->font_body, th->primary); - meow_gfx_fill_round(cx, by, g.btn_w, g.btn_h, 10, th->primary); - meow_ui_text_center(cx + g.btn_w / 2, by + (g.btn_h - meow_ui_text_height(th->font_body)) / 2, + meow_gfx_fill_round(cx, by, g->btn_w, g->btn_h, 10, th->primary); + meow_ui_text_center(cx + g->btn_w / 2, by + (g->btn_h - meow_ui_text_height(th->font_body)) / 2, "OK", th->font_body, th->onPrimary); } } +/* 整帧绘制:遮罩 + 卡片。 */ +static void dlg_paint(meow_dialog *d, const meow_theme *th) +{ + meow_ui_fill_rect_alpha(0, 0, meow_ui_width(), meow_ui_height(), 0x000000, 150); /* 遮罩压暗 */ + dlg_geom g; + dlg_calc(d, th, &g); + dlg_card(d, th, &g); +} + +/* 仅重绘卡片区(遮罩不变),flush 卡片矩形:高亮变化用,不叠加遮罩。 */ +static void dlg_repaint(meow_dialog *d, const meow_theme *th) +{ + dlg_geom g; + dlg_calc(d, th, &g); + dlg_card(d, th, &g); + meow_ui_flush_region(g.dx, g.dy, g.dw, g.dh); +} + meow_dialog *meow_dialog_create(meow_dlg_t t, const char *title) { meow_dialog *d = (meow_dialog *)calloc(1, sizeof *d); @@ -155,12 +203,14 @@ void meow_dialog_show(meow_dialog *d) meow_input_start(); meow_input_drain(); /* 排空残留触摸(goodix 长尾) */ d->done = 0; d->result = 0; + modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原(scrim 只叠一层/无残影) */ dlg_paint(d, th); meow_ui_flush(); while (!d->done) { meow_input_ev e; while (meow_input_pop(&e)) { + meow_ui_activity_ping(); /* M2.71-bug5:模态期间交互也重置息屏计时 */ if (e.type == MEOW_EV_TAP) { int x = e.a, y = e.b; dlg_geom g; @@ -168,8 +218,13 @@ void meow_dialog_show(meow_dialog *d) int in_card = x >= g.dx && x < g.dx + g.dw && y >= g.dy && y < g.dy + g.dh; if (!in_card) { d->done = 1; d->result = 0; break; } /* 遮罩外=取消 */ if (d->type == MEOW_DLG_SELECT) { + /* M2.71-bug3:点项 → 立即刷「选中」高亮(不下线、不叠加遮罩); + * 再点同一项 → 确认(返回该项下标)。点卡片外=取消。 */ int idx = (y - g.row_y) / g.rowh; - if (idx >= 0 && idx < d->nitems) { d->cur = idx; d->result = idx; d->done = 1; } + if (idx >= 0 && idx < d->nitems) { + if (idx == d->cur) { d->result = idx; d->done = 1; } + else { d->cur = idx; dlg_repaint(d, th); } + } break; } /* 非选择:按钮命中 */ @@ -195,6 +250,7 @@ void meow_dialog_show(meow_dialog *d) if (d->done) break; usleep(16000); } + modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */ } int meow_dialog_result(meow_dialog *d) { return d ? d->result : 0; } @@ -257,6 +313,7 @@ int meow_sheet_open(meow_sheet *s) int W = meow_ui_width(), H = meow_ui_height(); int ph = sheet_h(s, th); int py = H - ph, pad = 24; + modal_capture(); /* M2.71-bug3:拍下背景,关闭即还原 */ /* 首次绘制:遮罩 + 底部面板(顶部圆角) */ meow_ui_fill_rect_alpha(0, 0, W, H, 0x000000, 150); meow_gfx_fill_round(0, py, W, ph, th->radius_card, th->surfaceContainerHighest); @@ -273,6 +330,7 @@ int meow_sheet_open(meow_sheet *s) while (!s->done) { meow_input_ev e; while (meow_input_pop(&e)) { + meow_ui_activity_ping(); /* M2.71-bug5:模态期间交互也重置息屏计时 */ if (e.type == MEOW_EV_TAP) { int y = e.b; if (y < py) { s->done = 1; s->result = -1; break; } /* 点面板外=取消 */ @@ -288,6 +346,7 @@ int meow_sheet_open(meow_sheet *s) if (s->done) break; usleep(16000); } + modal_restore(); /* M2.71-bug3:关闭还原背景,无残影 */ return s->result; } void meow_sheet_free(meow_sheet *s) { if (s) free(s); } diff --git a/patch_v2/sdk/meow_ui_gfx.c b/patch_v2/sdk/meow_ui_gfx.c index b3b3ba5..fdcd3fc 100644 --- a/patch_v2/sdk/meow_ui_gfx.c +++ b/patch_v2/sdk/meow_ui_gfx.c @@ -179,6 +179,10 @@ void meow_gfx_mark_dirty(int x, int y, int w, int h) { dirty_add(x, y, w, h); } void meow_gfx_mark_full(void) { dirty_full(); } void meow_gfx_dirty_reset(void) { dirty_reset(); } +/* back_buf 每行字节数 / 位深(M2.71-bug3:模态/IME 整帧快照恢复用)。 */ +int meow_ui_back_buf_pitch(void) { return g_line; } +int meow_ui_back_buf_bpp(void) { return g_bpp; } + /* launcher(系统组件)与 SDK 共用同一 back_buf:launcher 的 fb 绘制指针指向它, * 从而 launcher 与 app 统一渲染栈(同一离屏 + 同一 present)。 */ unsigned char *meow_ui_back_buf(void) { return g_back_buf; } @@ -393,6 +397,23 @@ void meow_ui_draw_rect_round(int x, int y, int w, int h, int r, uint32_t color) dirty_add(x1, y1, x2 - x1, y2 - y1); } +/* 圆角矩形半透明填充(M2.71-bug4):按圆角裁剪后逐像素混合 α。供「禁用变暗」等状态层用, + * 避免在圆角外的方形区域也叠色(旧 fill_rect_alpha 会在圆角外露出灰方块)。 */ +void meow_gfx_fill_round_alpha(int x, int y, int w, int h, int r, uint32_t color, int alpha) +{ + if (!g_init_ok) return; + if (alpha <= 0) return; + if (alpha >= 255) { meow_gfx_fill_round(x, y, w, h, r, color); return; } + if (w <= 0 || h <= 0) return; + int x1 = x, y1 = y, x2 = x + w, y2 = y + h; + if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; + if (x2 > g_xres) x2 = g_xres; if (y2 > g_yres) y2 = g_yres; + for (int py = y1; py < y2; py++) + for (int px = x1; px < x2; px++) + if (rr_inside(px, py, x, y, w, h, r)) put_px_blend(px, py, color, alpha); + dirty_add(x1, y1, x2 - x1, y2 - y1); +} + /* ================= §5.8.6 batch2:圆形/线段/圆弧环(软渲,无 -lm) ================= * 无 GPU 合成、构建只链 libz+pthread(无 -lm),故不用 math.h:COS 用象限对称查表、 * 圆用 bbox 距离测试、弧带用「沿弧采样成粗线(圆帽)」近似。 */ @@ -462,7 +483,9 @@ void meow_gfx_ring_arc(int cx, int cy, int r, int th, int start_deg, int sweep_d if (!g_init_ok || r <= 0) return; int sw = sweep_deg % 360; if (sw < 0) sw += 360; - if (sw >= 360) { /* 整环:bbox 距离带(无角度判定,最快) */ + /* 整环(|sweep|>=360):bbox 距离带(无角度判定,最快)。注意 sweep=360 → sw=0, + * 若靠 sw>=360 判整环会落下走弧分支只画一个点(顺带让 Radio 外圈/圆形进度轨道环渲染成点)。 */ + if (sweep_deg >= 360 || sweep_deg <= -360) { int r1 = r - th / 2, r2 = r + th / 2; if (r1 < 0) r1 = 0; int x1 = cx - r2, y1 = cy - r2, x2 = cx + r2 + 1, y2 = cy + r2 + 1; @@ -478,6 +501,7 @@ void meow_gfx_ring_arc(int cx, int cy, int r, int th, int start_deg, int sweep_d dirty_add(x1, y1, x2 - x1, y2 - y1); return; } + if (sw == 0) return; /* 零弧长:不画(sweep=0;整环已在上面处理) */ /* 弧带:沿弧采样相邻点、粗线连接(圆帽)近似。方向:deg 右(0)→上(90)→左(180)→下(270),顺时针。 */ int n = sw / 2 + 1; if (n < 2) n = 2; diff --git a/patch_v2/sdk/meow_ui_ime.c b/patch_v2/sdk/meow_ui_ime.c index ff60b95..87a5a8e 100644 --- a/patch_v2/sdk/meow_ui_ime.c +++ b/patch_v2/sdk/meow_ui_ime.c @@ -22,6 +22,36 @@ #define GAP 4 /* 键间距 */ #define GCOL 48 /* 列步进 = GW+GAP */ +/* M2.71-bug7:键盘区背景快照/恢复。旧实现关闭后键盘像素留在屏底(残影)。此处打开前拍下 + * 键盘区 [KB_TOP,H),关闭后回填 + 上屏 → 无残影。 */ +static unsigned char *g_ime_cap = NULL; +static int g_ime_cap_line = 0, g_ime_cap_h = 0; + +static void ime_capture(void) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(); + int h = meow_ui_height() - KB_TOP; + if (!bb || line <= 0 || h <= 0) { g_ime_cap = NULL; return; } + size_t n = (size_t)line * (size_t)h; + if (!g_ime_cap || g_ime_cap_line != line || g_ime_cap_h != h) { + free(g_ime_cap); + g_ime_cap = (unsigned char *)malloc(n); + g_ime_cap_line = line; g_ime_cap_h = h; + } + if (g_ime_cap) memcpy(g_ime_cap, bb + (size_t)line * KB_TOP, n); +} + +static void ime_restore(void) +{ + unsigned char *bb = meow_ui_back_buf(); + if (!g_ime_cap || !bb) return; + size_t n = (size_t)g_ime_cap_line * (size_t)g_ime_cap_h; + memcpy(bb + (size_t)g_ime_cap_line * KB_TOP, g_ime_cap, n); + meow_gfx_mark_dirty(0, KB_TOP, meow_ui_width(), g_ime_cap_h); + meow_ui_flush(); +} + typedef enum { K_CH, K_SHIFT, K_SPACE, K_BACK, K_ENTER, K_ESC } kkind; struct kbkey { int x, y, w, h; kkind kind; char ch; }; @@ -91,18 +121,18 @@ static void build_kb(meow_ime_mode mode) add_key(x + GCOL, y + GCOL * 4, (GW + GAP) * 6, GH, K_SPACE, ' '); add_key(x + GCOL * 8, y + GCOL * 4, 2 * GW + GAP, GH, K_ENTER, '>'); } - } else { /* MEOW_IME_NUM */ + } else { /* MEOW_IME_NUM:ESC(取消/关闭) + 空格 + 退格;单独一行回车(OK=提交并关闭)。 + * M2.71-bug7:修掉旧版「同格重叠的 BACK+ENTER」与重复回车。 */ add_ch_row(y, "1234567890"); add_ch_row(y + GCOL, "-/.:*#+="); { int total = 10 * GW + 9 * GAP; int x = (480 - total) / 2; - add_key(x, y + GCOL * 2, GW, GH, K_ESC, 'X'); - add_key(x + GCOL, y + GCOL * 2, (GW + GAP) * 7, GH, K_SPACE, ' '); - add_key(x + GCOL * 9, y + GCOL * 2, GW, GH, K_BACK, '<'); - add_key(x + GCOL * 9, y + GCOL * 2, GW, GH, K_ENTER, '>'); /* 覆盖:回车 */ + add_key(x, y + GCOL * 2, GW, GH, K_ESC, 'X'); /* 取消/关闭(丢弃) */ + add_key(x + GCOL, y + GCOL * 2, (GW + GAP) * 7, GH, K_SPACE, ' '); /* 空格 */ + add_key(x + GCOL * 8, y + GCOL * 2, (GW + GAP) * 2 - GAP, GH, K_BACK, '<'); /* 退格 */ } - /* 单独一行回车 */ + /* 回车(OK=提交并关闭) */ { int total = 10 * GW + 9 * GAP; int x = (480 - total) / 2; @@ -149,25 +179,27 @@ void meow_ime_draw(void) { if (!g_ime_active) return; const meow_theme *th = meow_theme_get(); - /* 面板 */ - meow_ui_fill_rect(0, KB_TOP, 480, 720 - KB_TOP, 0x1C1C22); - /* 预览行 */ - meow_ui_fill_rect(0, KB_TOP, 480, 42, 0x2A2A33); - char preview[256]; + /* M2.71-bug7:配色全走 meow_theme(深/浅主题自动切),不再硬编码黑底白键。 */ + meow_ui_fill_rect(0, KB_TOP, 480, 720 - KB_TOP, th->surfaceContainerHighest); /* 面板 */ + meow_ui_fill_rect(0, KB_TOP, 480, 42, th->surfaceContainerHigh); /* 回显条 */ + char preview[260]; /* g_ime_buf(≤255) + '_' + NUL → 需 ≥257,防截断告警 */ + /* 回显=用户已输入的字符(live echo,非候选/联想),随键入实时刷新。 */ snprintf(preview, sizeof preview, "%s_", g_ime_buf); - meow_ui_text(14, KB_TOP + 9, preview, th->font_body, 0xFFFFFF); + meow_ui_text(14, KB_TOP + 9, preview, th->font_body, th->onSurface); /* 键 */ for (int i = 0; i < g_nkeys; i++) { const struct kbkey *k = &g_keys[i]; - int is_fn = (k->kind != K_CH); - uint32_t bg = is_fn ? 0x33333C : 0xFFFFFF; + uint32_t bg, fg; + if (k->kind == K_ENTER) { bg = th->primary; fg = th->onPrimary; } /* OK=提交 */ + else if (k->kind == K_CH) { bg = th->surfaceContainerLow; fg = th->onSurface; } /* 字符键 */ + else { bg = th->primaryContainer; fg = th->onPrimaryContainer; } /* 功能键(ESC/SHIFT/空格/退格) */ meow_gfx_fill_round(k->x, k->y, k->w, k->h, 8, bg); char label[2] = { k->ch, 0 }; if (is_letter(k->ch)) label[0] = g_shift ? (char)toupper((unsigned char)k->ch) : (char)tolower((unsigned char)k->ch); - uint32_t fg = is_fn ? 0xFFFFFF : 0x1A1A1E; if (k->kind == K_SPACE) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "SPACE", th->font_body, fg); } else if (k->kind == K_ENTER) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "OK", 20, fg); } + else if (k->kind == K_ESC) { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, "X", 20, fg); } else { meow_ui_text_center(k->x + k->w / 2, k->y + (k->h - meow_ui_text_height(20)) / 2, label, 20, fg); } } } @@ -199,6 +231,7 @@ void meow_ime_close(int result) g_ime_orig->buf[sizeof g_ime_orig->buf - 1] = 0; } g_ime_active = 0; g_ime_done = 0; + ime_restore(); /* M2.71-bug7:关闭还原键盘区背景,无残影 */ } void meow_ui_text_input_async(meow_ime_ctx *ctx) @@ -211,6 +244,7 @@ void meow_ui_text_input_async(meow_ime_ctx *ctx) g_maxlen = ctx->maxlen > 0 ? ctx->maxlen : 255; g_shift = 0; g_ime_result = 0; g_ime_done = 0; g_ime_active = 1; build_kb(ctx->mode); + ime_capture(); /* M2.71-bug7:打开前拍下键盘区背景(关闭即还原) */ meow_input_start(); meow_input_drain(); } @@ -223,6 +257,7 @@ int meow_ui_text_input(meow_ime_ctx *ctx) while (meow_ime_active()) { meow_input_ev e; while (meow_input_pop(&e)) { + meow_ui_activity_ping(); /* M2.71-bug5:IME 打字交互也重置息屏计时 */ if (e.type == MEOW_EV_TAP) meow_ime_handle_tap(e.a, e.b); } meow_ime_draw(); diff --git a/patch_v2/sdk/meow_ui_input.c b/patch_v2/sdk/meow_ui_input.c index 80b32ae..a7bbcb2 100644 --- a/patch_v2/sdk/meow_ui_input.c +++ b/patch_v2/sdk/meow_ui_input.c @@ -37,6 +37,7 @@ #define EDGE_W 24 /* 左滑返回的「左缘起点」宽度(t_dx < EDGE_W 才认返回手势) */ #define QUEUE_N 128 #define MAX_DEVS 8 +#define MEOW_APP_ACTIVITY_FILE "/tmp/meow_app_activity" /* M2.71-bug5:App 活动→launcher 重置息屏计时 */ /* ── 内部事件:系统级「下拉展开通知中心」(M2.5x 接线)── * 触摸状态机在抬手判定处识别「SWIPE_DOWN 起始于顶部热区」后压入;事件循环按系统手势处理 @@ -126,6 +127,8 @@ static int t_dx = 0, t_dy = 0; /* 按下原点(BTN_TOUCH 后首个有效 A static int t_cx = 0, t_cy = 0; /* 当前坐标(-1=本次尚未见到有效坐标) */ 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; /* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。 * t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */ @@ -138,22 +141,32 @@ static void touch_press(int x, int y) t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; t_down_ms = now_ms(); /* 记按下时刻:供实时按住时长/按住视觉 */ + t_mv_x = -1; t_mv_y = -1; /* M2.71:拖动过程起点未定 */ } -/* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 */ +/* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 + * M2.71-bug2/6:原点锁定即报 MEOW_EV_TOUCH_DOWN(拖动起点);随后坐标变化报 MEOW_EV_TOUCH_MOVE + * (坐标变了才报,省队列)。这两类事件供 slider 拖动 / 列表&页面滚动跟手。 */ static void touch_update(int x, int y) { if (t_down && !t_origin_valid && x >= 0 && y >= 0) { t_dx = x; t_dy = y; t_origin_valid = 1; + t_mv_x = x; t_mv_y = y; + push_ev(MEOW_EV_TOUCH_DOWN, x, y); } if (x >= 0) t_cx = x; if (y >= 0) t_cy = y; + if (t_down && t_origin_valid && t_cx >= 0 && t_cy >= 0 && + (t_cx != t_mv_x || t_cy != t_mv_y)) { + t_mv_x = t_cx; t_mv_y = t_cy; + push_ev(MEOW_EV_TOUCH_MOVE, t_cx, t_cy); + } } static void touch_release(void) { if (!t_down) return; t_down = 0; t_press_valid = 0; /* 本次按下从未锁到有效坐标(幽灵触摸/坐标未就绪):丢弃,且清空全部轨迹。 */ - if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; return; } + 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; @@ -194,7 +207,7 @@ static void touch_release(void) push_ev(MEOW_EV_TAP, t_cx, t_cy); } /* 手势级干净起点:清空全部轨迹,下一手势从零开始,杜绝污染无自愈。 */ - t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; + t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; t_mv_x = t_mv_y = -1; } /* ---- 实时触摸态(内部:控件绘制按压/按住视觉用)---- @@ -221,6 +234,7 @@ void meow_input_touch_set(int down, int x, int y, unsigned hold_ms) } else { t_down = 0; t_press_valid = 0; t_origin_valid = 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; } } @@ -301,6 +315,19 @@ void meow_input_start(void) /* ---- 事件循环 ---- */ void meow_ui_request_exit(void) { g_exit = 1; } +/* M2.71-bug5:App 活动信号(节流 ≤2/s 写标记文件,限 I/O)。 + * 仅真机写(宿主/qemu 无 launcher 消费,避免噪音);launcher 前台 app 循环消费它重置息屏计时。 */ +static uint64_t g_activity_last_ms = 0; +void meow_ui_activity_ping(void) +{ + uint64_t now = now_ms(); + if (now - g_activity_last_ms < 500) return; /* 节流:任意交互连续触摸也只 ~2 次/s */ + g_activity_last_ms = now; + if (!meow_gfx_is_device()) return; /* 离屏/宿主:无 launcher 消费 */ + int fd = open(MEOW_APP_ACTIVITY_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd >= 0) { if (write(fd, "1", 1) < 0) { } close(fd); } +} + void meow_ui_run(const meow_ui_events *ev) { if (ev) { g_events = *ev; g_events_set = 1; } @@ -319,6 +346,7 @@ void meow_ui_run(const meow_ui_events *ev) while (meow_input_pop(&e)) { if (g_exit) break; /* 已请求退出:不再分发剩余事件,杜绝「退出后又触发」的偶发再进/退 */ got = 1; + meow_ui_activity_ping(); /* M2.71-bug5:任意交互事件都喂 launcher 重置息屏计时(节流) */ int in_startup = (now_ms() < startup_cooldown_until); /* M2.22(C) */ if (e.type == MEOW_EV_TAP) { if (in_startup) continue; /* 启动窗口也吞 TAP:旧手势残留点按(如上一次左滑抬起) @@ -354,6 +382,17 @@ void meow_ui_run(const meow_ui_events *ev) /* 系统级「下拉展开通知中心」:起始于顶部热区的下滑为系统保留手势,不下发 * APP on_swipe;路由到系统钩子(当前为记录占位,通知中心 UI 为后续里程碑)。 */ meow_ui_sys_notif_pull(); + } else if (e.type == MEOW_EV_TOUCH_DOWN || e.type == MEOW_EV_TOUCH_MOVE) { + /* M2.71-bug2/6:拖动过程(跟手)。IME 弹出时触摸优先喂键盘(键盘覆盖层拦截, + * on_touch_* 丢弃,避免按到键盘却在背后控件上锁拖拽);启动窗口也吞(防残留点按 + * 误锁拖拽)。模态(dialog/sheet)期间本主循环被阻塞,天然不在此分发。 */ + if (in_startup) continue; + if (meow_ime_active()) continue; + if (g_events_set && g_events.on_touch_down && e.type == MEOW_EV_TOUCH_DOWN) + g_events.on_touch_down(e.a, e.b); + if (g_events_set && g_events.on_touch_move && e.type == MEOW_EV_TOUCH_MOVE) + g_events.on_touch_move(e.a, e.b); + got = 1; /* 交互事件:不睡眠,保持跟手帧同步 */ } else if (e.type == MEOW_EV_SWIPE_PROGRESS) { if (in_startup) continue; /* 启动窗口进度条静音 */ int x0 = e.a, xc = e.b, dx = xc - x0; /* dx=当前X-按下X */ diff --git a/patch_v2/sdk/meow_ui_internal.h b/patch_v2/sdk/meow_ui_internal.h index 9a4a610..62dbf3c 100644 --- a/patch_v2/sdk/meow_ui_internal.h +++ b/patch_v2/sdk/meow_ui_internal.h @@ -108,9 +108,14 @@ void meow_gfx_dirty_reset(void); /* 返回离屏 back_buf 指针(launcher 系统组件与 SDK 共用同一 back_buf 用)。 */ unsigned char *meow_ui_back_buf(void); +/* back_buf 的每行字节数(pitch)与位深(16/32)。M2.71-bug3:模态/IME 快照恢复用。 */ +int meow_ui_back_buf_pitch(void); +int meow_ui_back_buf_bpp(void); /* 实心圆角矩形(控件按钮底色用;公共 §3 只有空心 draw_rect_round)。[D3] */ void meow_gfx_fill_round(int x, int y, int w, int h, int r, uint32_t color); +/* 圆角矩形半透明填充(M2.71-bug4):禁用变暗等状态层用,圆角外不叠色(防灰方块)。 */ +void meow_gfx_fill_round_alpha(int x, int y, int w, int h, int r, uint32_t color, int alpha); /* §5.8.6 batch2:软渲圆形/线段/圆弧环原语(无 GPU、无 -lm,内部整数/查表实现)。 * 供 Radio(外圈/内点)、Badge(红点)、ProgressBar(环形轨道/进度弧)、Checkbox(勾线段)使用。 */ @@ -131,6 +136,8 @@ int meow_gfx_mfont_ready(void); #define MEOW_EV_TAP 2 /* a=x, b=y(触摸抬起落点) */ #define MEOW_EV_SWIPE 3 /* a=MEOW_SWIPE_*, b=距离(px) */ #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:拖动过程坐标变化(坐标变才入队;跟手用) */ typedef struct meow_input_ev { uint8_t type; @@ -146,6 +153,12 @@ void meow_input_inject(int type, int a, int b); /* 清空队列(IME 打开前丢弃残留触摸,避免误敲)。 */ void meow_input_drain(void); +/* ---- M2.71-bug5:App 活动 → 通知 launcher 重置息屏计时 ---- + * launcher 前台 app 会话的 app_last_input 只在物理键边沿重置(它看不到应用内触摸)。SDK 在 + * 收到任意交互事件(tap/swipe/拖动/IME/模态)时节流 touch 出 /tmp/meow_app_activity 标记; + * launcher app 循环探测到即 unlink + 重置计时(若已自动息屏则照 key-wake 路径唤醒)。 */ +void meow_ui_activity_ping(void); + /* ---- 实时触摸态(供控件绘制按压/按住视觉;输入线程写、app 主线读)---- * 与语义事件队列无关:这些直接读输入线程的触摸状态机当前值,让 meow_widget_draw * 在「手指按下期间」能如实反映按钮的按下/按住视觉,而无需 app 自行追踪按下。 */ diff --git a/patch_v2/sdk/meow_ui_widget.c b/patch_v2/sdk/meow_ui_widget.c index f4f5bb3..ef9c5d7 100644 --- a/patch_v2/sdk/meow_ui_widget.c +++ b/patch_v2/sdk/meow_ui_widget.c @@ -26,6 +26,11 @@ enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3, * 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */ #define MEOW_BTN_HOLD_MS 500u +/* 圆形 ProgressBar 环带厚 / 外扩边距(bgg 修复 M2.71-bug1:控件几何必须覆盖真实像素)。 + * 实际像素范围 = 中心 ± (r + thk/2);margin = thk/2 + 1 保证外接框覆盖环带外缘与圆帽。 */ +#define MEOW_PROGRESS_ARC_THK 4 +#define MEOW_PROGRESS_ARC_MARGIN (MEOW_PROGRESS_ARC_THK / 2 + 1) + /* MD3 状态层混色:c 为基色、o 为状态色、t 为 o 的占比(0-100)。 * 用于把主题 accent 向黑/白调出「按下/按住」状态层(日/夜主题各配色)。 */ static uint32_t blend_color(uint32_t c, uint32_t o, int t) @@ -73,6 +78,9 @@ struct meow_widget { char items[MEOW_CONTAINER_MAX][24]; /* W_TOPAPPBAR 动作 / W_NAVBAR 项 / W_TABBAR 页签 */ int nitems; /* 上述项数 */ int selected; /* W_NAVBAR / W_TABBAR 选中下标 */ + /* M2.71-bug8:W_LIST 行点击回调(可选)。 */ + meow_list_tap_fn tap_cb; /* 命中行回调(可 NULL) */ + void *tap_ctx; /* 回调 ctx */ }; /* ---- §5.8 batch1 内部辅助:opts 取值(opts_used==0 时按控件内建默认)---- @@ -584,7 +592,13 @@ meow_widget *meow_progress_circular_create(int cx, int cy, int r, int value) { meow_widget *t = (meow_widget *)calloc(1, sizeof *t); if (!t) return NULL; - t->type = W_PROGRESS; t->x = cx; t->y = cy; t->w = r; t->h = r; + /* bbox = 整圆像素外接框(中心 ± (r + margin)):使 w->x/y/w/h 即真实绘制范围, + * 于是 meow_progress_set 的 mark_dirty 与 App 的 flush_region(w->x,w->y,w->w,w->h) + * 都天然对齐,不漏刷/不多刷(修复 bgg M2.71 圆形进度脏区)。 */ + int m = MEOW_PROGRESS_ARC_MARGIN; + t->type = W_PROGRESS; + t->x = cx - r - m; t->y = cy - r - m; + t->w = 2 * (r + m); t->h = 2 * (r + m); t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->variant = 1; /* 圆形 */ @@ -791,6 +805,34 @@ int meow_list_hit_index(meow_widget *w, int x, int y) return (idx >= 0 && idx < w->count) ? idx : -1; } +/* M2.71-bug8:选中行(越界 clamp)+ 标脏(高亮刷新)。 */ +void meow_list_set_selected(meow_widget *w, int idx) +{ + if (!w || w->type != W_LIST) return; + if (idx < 0) idx = 0; + if (w->count > 0 && idx >= w->count) idx = w->count - 1; + if (idx == w->sel) return; + w->sel = idx; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); +} +int meow_list_selected(meow_widget *w) { return (w && w->type == W_LIST) ? w->sel : -1; } + +void meow_list_set_on_tap(meow_widget *w, meow_list_tap_fn cb, void *ctx) +{ + if (!w || w->type != W_LIST) return; + w->tap_cb = cb; w->tap_ctx = ctx; +} + +/* 命中行:置选中 + 回调(若设)+ 返回 idx;未命中返回 -1(不置选中)。 */ +int meow_list_tap_at(meow_widget *w, int x, int y) +{ + int idx = meow_list_hit_index(w, x, y); + if (idx < 0) return -1; + meow_list_set_selected(w, idx); + if (w->tap_cb) w->tap_cb(idx, w->tap_ctx); + return idx; +} + static int hit(meow_widget *w, int x, int y) { if (!w) return 0; @@ -816,7 +858,9 @@ void meow_slider_set(meow_widget *w, int val) { if (!w || w->type != W_SLIDER) return; if (val < w->min) val = w->min; if (val > w->max) val = w->max; + if (val == w->val) return; w->val = val; + meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); /* 滑块/轨道随值变化:标脏 */ } int meow_slider_get(meow_widget *w) { return w && w->type == W_SLIDER ? w->val : 0; } void meow_switch_set(meow_widget *w, int on) @@ -899,8 +943,8 @@ void meow_widget_draw(meow_widget *w) if (sw > 0 && sc) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, sc); if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary); - /* 禁用:整体变暗(状态层向 onSurface 叠 α) */ - if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + /* 禁用:整体变暗(圆角 α,防在圆角外叠出灰方块——M2.71-bug4) */ + if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90); break; } case W_SLIDER: { @@ -922,7 +966,7 @@ void meow_widget_draw(meow_widget *w) int knob_r = w->h / 2 - 3; int kx = w->on ? w->x + w->w - knob_r * 2 - 3 : w->x + 3; meow_gfx_fill_round(kx, w->y + 3, knob_r * 2, knob_r * 2, knob_r, 0xFFFFFF); - if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, w->h / 2, th->onSurface, 90); break; } case W_LIST: { @@ -995,7 +1039,7 @@ void meow_widget_draw(meow_widget *w) int tw = meow_ui_text_width(w->buf, th->font_body); meow_ui_fill_rect(tx + tw, ty, 2, meow_ui_text_height(th->font_body), th->primary); } - if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90); break; } case W_CARD: { @@ -1008,7 +1052,7 @@ void meow_widget_draw(meow_widget *w) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->primary, 24); if (w->nchildren > 0 && w->children[0]) meow_widget_draw(w->children[0]); if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary); - if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90); break; } case W_DIVIDER: { @@ -1046,7 +1090,7 @@ void meow_widget_draw(meow_widget *w) meow_ui_draw_rect_round(w->x, w->y, sz, sz, r, th->outline); } if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, sz, sz, r, th->primary); - if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, sz, sz, th->onSurface, 90); + if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, sz, sz, r, th->onSurface, 90); break; } case W_RADIO: { @@ -1054,13 +1098,16 @@ void meow_widget_draw(meow_widget *w) int ccx = w->x + w->w / 2, ccy = w->y + w->h / 2; meow_gfx_ring_arc(ccx, ccy, 12, 2, 0, 360, sel ? th->primary : th->outline); if (sel) meow_gfx_fill_circle(ccx, ccy, 6, th->primary); - if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, w->w / 2, th->onSurface, 90); break; } case W_PROGRESS: { - if (w->variant == 1) { /* 圆形 */ - int ccx = w->x, ccy = w->y, rr = w->w; - int thk = 4; + if (w->variant == 1) { /* 圆形(几何 = 整圆像素外接框;由此解回圆心/半径) */ + int thk = MEOW_PROGRESS_ARC_THK; + int m = MEOW_PROGRESS_ARC_MARGIN; + int ccx = w->x + w->w / 2, ccy = w->y + w->h / 2; + int rr = w->w / 2 - m; /* 与 create 一致:回到调用方传入的半径 r */ + if (rr <= 0) break; meow_gfx_ring_arc(ccx, ccy, rr, thk, 0, 360, th->surfaceVariant); if (w->value < 0) { /* 不定:按时间推进一段弧(App 在 on_tick 里重绘驱动) */ int start = (int)((meow_ui_time_ms() / 30) % 360); @@ -1094,7 +1141,7 @@ void meow_widget_draw(meow_widget *w) meow_ui_text_center(w->x + w->w / 2, w->y + (w->h - meow_ui_text_height(th->font_body)) / 2, w->label, th->font_body, tc); if (w->state & MEOW_WS_FOCUSED) meow_ui_draw_rect_round(w->x, w->y, w->w, w->h, r, th->primary); - if (disabled) meow_ui_fill_rect_alpha(w->x, w->y, w->w, w->h, th->onSurface, 90); + if (disabled) meow_gfx_fill_round_alpha(w->x, w->y, w->w, w->h, r, th->onSurface, 90); break; } case W_TOPAPPBAR: { diff --git a/patch_v2/sdk/test_meow_ui_widgets.c b/patch_v2/sdk/test_meow_ui_widgets.c index 168ef5b..4b1b447 100644 --- a/patch_v2/sdk/test_meow_ui_widgets.c +++ b/patch_v2/sdk/test_meow_ui_widgets.c @@ -144,11 +144,18 @@ int main(void) assert(meow_list_hit_index(lc, 30, 480) == ((480 - 460 + 3500) / 40)); /* 可见项换算 */ meow_list_set_item_h(lc, 50); assert(meow_widget_hit(lc, 30, 470) == 1); + /* M2.71-bug8:行选中 + 命中点击回调 */ + meow_list_set_selected(lc, 5); + assert(meow_list_selected(lc) == 5); + /* 验证 tap_at 返回命中下标并置选中 */ + int hidx = meow_list_tap_at(lc, 30, 480); + assert(hidx == ((480 - 460 + meow_list_scroll(lc)) / 50)); + assert(meow_list_selected(lc) == hidx); /* LazyRow 横向 */ meow_widget *lr = W(meow_lazyrow_create(20, 640, 380, 40, 60, 100, list_src, NULL)); meow_list_scroll_by(lr, 200); assert(meow_list_hit_index(lr, 40, 660) == ((40 - 20 + 200) / 60)); - printf("PASS lazy list: scroll clamp/virtualize/hit_index (col+row)\n"); + printf("PASS lazy list: scroll clamp/virtualize/hit_index/tap_at/select (col+row)\n"); /* ---------- Dialog / BottomSheet 纯逻辑(create/set/add;show 阻塞故不调) ---------- */ meow_dialog *dlg = meow_dialog_create(MEOW_DLG_SELECT, "Pick one"); diff --git a/patch_v2/sdk/test_sdk_fixes.c b/patch_v2/sdk/test_sdk_fixes.c new file mode 100644 index 0000000..bd7c42b --- /dev/null +++ b/patch_v2/sdk/test_sdk_fixes.c @@ -0,0 +1,158 @@ +/* test_sdk_fixes.c — M2.71 修复项宿主验证(离屏 480x720,16bpp)。 + * 覆盖: + * bug4 disabled 按钮圆角外无灰方块(角点 = 页面背景而非叠暗); + * bug1 圆形进度环带确实画到 = 半径+thk/2 处(bbox 与真实像素一致,漏刷修复的几何前提); + * bug3 modal(Dialog)open→cancel 后背景还原(scrim 不累积/无残影); + * bug7 IME 回显实时同步(on_change 随键入触发)+ 关闭后键盘区背景还原。 + * + * 编译(宿主 gcc,链真实 gfx/sys/theme/vol/log/widget/input/activity/dialog/ime + 打桩 font/global): + * gcc -Wall -o test_sdk_fixes test_sdk_fixes.c test_host_stub.c \ + * meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \ + * meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c \ + * -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread + */ +#include "meow_ui.h" +#include "meow_ui_internal.h" +#include +#include +#include +#include +#include +#include + +static void rd_px(int x, int y, int *r, int *g, int *b) +{ + unsigned char *bb = meow_ui_back_buf(); + int line = meow_ui_back_buf_pitch(), bpp = meow_ui_back_buf_bpp(); + assert(bb && line > 0); + if (bpp == 16) { + uint16_t px = *(uint16_t *)(bb + (size_t)y * line + (size_t)x * 2); + *r = (((px >> 11) & 0x1F) << 3) | ((px >> 11) & 0x1F) >> 2; + *g = (((px >> 5) & 0x3F) << 2) | ((px >> 5) & 0x3F) >> 4; + *b = ((px & 0x1F) << 3) | ((px & 0x1F) >> 2); + } else { + uint32_t px = *(uint32_t *)(bb + (size_t)y * line + (size_t)x * 4); + *r = (px >> 16) & 0xFF; *g = (px >> 8) & 0xFF; *b = px & 0xFF; + } +} +static int near(int a, int b) { int d = a - b; if (d < 0) d = -d; return d <= 12; } + +/* ---- 为阻塞模态/IME 在后台注入事件 ---- */ +static int g_inj_tap_x = 0, g_inj_tap_y = 0, g_inj_delay = 0; +static void *inject_thread(void *arg) +{ + (void)arg; + usleep((useconds_t)g_inj_delay); + meow_input_inject(MEOW_EV_TAP, g_inj_tap_x, g_inj_tap_y); + return NULL; +} +static void inject_after(int ms, int x, int y) +{ + pthread_t th; g_inj_tap_x = x; g_inj_tap_y = y; g_inj_delay = ms; + pthread_create(&th, NULL, inject_thread, NULL); + pthread_detach(th); +} + +static void test_disabled_corner(void) +{ + const meow_theme *th = meow_theme_get(); + meow_ui_clear(th->bg); + meow_widget *b = meow_btn_create(100, 100, 200, 60, "DISABLED"); + meow_widget_set_enabled(b, 0); /* 禁用:old 用方形 α 盖整 bbox → 圆角外露灰 */ + meow_widget_draw(b); + meow_ui_flush(); + int r, g, bb; + rd_px(100, 100, &r, &g, &bb); /* bbox 左上角(圆角外):应为页面背景 */ + printf("[bug4] disabled corner rgb=%02X%02X%02X (bg=%02X%02X%02X) ", + r, g, bb, (th->bg >> 16) & 0xFF, (th->bg >> 8) & 0xFF, th->bg & 0xFF); + assert(near(r, (th->bg >> 16) & 0xFF) && near(g, (th->bg >> 8) & 0xFF) && near(bb, th->bg & 0xFF)); + printf("PASS\n"); + meow_widget_free(b); +} + +static void test_circular_extent(void) +{ + const meow_theme *th = meow_theme_get(); + meow_ui_clear(th->bg); + /* r=30, thk=4 → 环外缘 = r+thk/2 = 32;create 几何外接框 = 中心±(r+margin)=±33 */ + int cx = 240, cy = 200, r = 30; + meow_widget *pg = meow_progress_circular_create(cx, cy, r, 100); + meow_widget_draw(pg); + meow_ui_flush(); + int rd, g, b; + rd_px(cx - 32, cy, &rd, &g, &b); /* 最左环外缘:应被画(非背景) */ + int bg_r = (th->bg >> 16) & 0xFF, bg_g = (th->bg >> 8) & 0xFF, bg_b = th->bg & 0xFF; + printf("[bug1] ring-left-extent rgb=%02X%02X%02X bg=%02X%02X%02X ", rd, g, b, bg_r, bg_g, bg_b); + assert(!(near(rd, bg_r) && near(g, bg_g) && near(b, bg_b))); /* 环带已画到外缘 */ + printf("PASS\n"); fflush(stdout); + meow_widget_free(pg); +} + +static void test_modal_restore(void) +{ + const meow_theme *th = meow_theme_get(); + meow_ui_clear(th->bg); + meow_ui_flush(); + meow_dialog *d = meow_dialog_create(MEOW_DLG_CONFIRM, "Sure?"); + /* 两次 open→cancel:背景应两次均还原(scrim 不累积、无残影) */ + for (int i = 0; i < 2; i++) { + inject_after(30, 1, 1); /* (1,1) 在居中卡片外 → 取消 */ + meow_dialog_show(d); + int r, g, b; + rd_px(400, 100, &r, &g, &b); /* 卡片外右上:应 == 背景 */ + printf("[bug3] open%d cancel bg rgb=%02X%02X%02X bg=%02X%02X%02X ", i, r, g, b, + (th->bg >> 16) & 0xFF, (th->bg >> 8) & 0xFF, th->bg & 0xFF); + assert(near(r, (th->bg >> 16) & 0xFF) && near(g, (th->bg >> 8) & 0xFF) && near(b, th->bg & 0xFF)); + } + printf("PASS\n"); + meow_dialog_free(d); +} + +/* IME 回显同步回调 */ +static char g_echo[64] = ""; +static void ime_cb(const char *t, void *ctx) { (void)ctx; snprintf(g_echo, sizeof g_echo, "%s", t); } + +static void test_ime_echo_and_restore(void) +{ + const meow_theme *th = meow_theme_get(); + meow_ui_clear(th->bg); + meow_ui_flush(); + meow_ime_ctx c; memset(&c, 0, sizeof c); + c.mode = MEOW_IME_ASCII; c.maxlen = 32; c.on_change = ime_cb; + /* --- 异步:验『回显/输入框实时同步(on_change 随键入触发)』--- */ + meow_ui_text_input_async(&c); + assert(meow_ime_active() == 1); + meow_input_inject(MEOW_EV_TAP, 20, 580); /* ASCII 第3行第1键 = 'A'(shift 关 → 缓冲 'A') */ + meow_ime_handle_tap(20, 580); + assert(strcmp(g_echo, "A") == 0 && strcmp(meow_ime_text(), "A") == 0); + printf("[bug7] ime echo live-sync after 'A' -> \"%s\" (on_change fired) PASS\n", g_echo); fflush(stdout); + meow_input_inject(MEOW_EV_TAP, 20, 680); /* ESC(X) → 取消 */ + meow_ime_handle_tap(20, 680); + assert(meow_ime_active() == 0); + /* --- 阻塞:验『关闭后键盘区背景还原』--- */ + meow_ui_clear(th->bg); + meow_ui_flush(); + inject_after(30, 20, 680); /* ESC 关闭 */ + int rc = meow_ui_text_input(&c); + assert(rc == 0); /* 取消 */ + int r, g, b; + rd_px(240, 600, &r, &g, &b); /* 键盘区内一点:应 == 背景 */ + printf("[bug7] ime close restore px rgb=%02X%02X%02X bg=%02X%02X%02X ", r, g, b, + (th->bg >> 16) & 0xFF, (th->bg >> 8) & 0xFF, th->bg & 0xFF); + assert(near(r, (th->bg >> 16) & 0xFF) && near(g, (th->bg >> 8) & 0xFF) && near(b, th->bg & 0xFF)); + printf("PASS\n"); +} + +int main(void) +{ + setenv("MEOW_UI_DUMP", "/tmp/fixes.ppm", 1); + meow_ui_opts o = { 480, 720 }; + assert(meow_ui_init(&o) == 0); + test_disabled_corner(); + test_circular_extent(); + test_modal_restore(); + test_ime_echo_and_restore(); + meow_ui_shutdown(); + printf("ALL PASS\n"); + return 0; +}