diff --git a/meow_ui_gfx.c b/meow_ui_gfx.c index e26f796..a31e971 100644 --- a/meow_ui_gfx.c +++ b/meow_ui_gfx.c @@ -353,6 +353,49 @@ void meow_ui_fill_rect_alpha(int x, int y, int w, int h, uint32_t color, int alp #define MUI_HINT_H 5 static int g_hint_last = 0; /* 上次画过的条长(清条时只清到它,避免残留) */ +/* 顶部条区「快照恢复」:画条前把 [0..宽)×[hy..hy+MUI_HINT_H) 拍进小缓冲区;回缩/清空时 + * 回填快照还原「真实底色」而非 th->bg —— 全屏页面底=主题 accent,若填 th->bg(黑) 会在 + * accent 底上留下黑色残条(直到下次区域刷新才消失)。快照只在首现条时拍一次(后续 grow + * 不再覆盖快照),每行/全长布局全保留;缓冲为一次性分配(宽×H 行,480×5×2B≈4.8KB)。 */ +static unsigned char *g_hint_snap = NULL; /* 快照(g_line×MUI_HINT_H 字节;NULL=未拍/分配失败) */ +static int g_hint_snap_y = 0; /* 快照行的起始 y(本次手势时的 hy) */ + +/* 拍下「顶部条区」当前 back_buf 内容(只在 g_hint_last==0 的首现条时调用)。 */ +static void hint_snapshot(void) +{ + int hy = meow_ui_statusbar_height(); + if (hy < 0) hy = 0; + g_hint_snap_y = hy; + if (hy + MUI_HINT_H > g_yres) return; /* 越界:放弃(回缩走 bg 兜底) */ + size_t len = (size_t)MUI_HINT_H * (size_t)g_line; + if (!g_hint_snap) { + g_hint_snap = malloc(len); + if (!g_hint_snap) return; + } + memcpy(g_hint_snap, g_fb + (size_t)hy * g_line, len); +} + +/* 从快照回填列区间 [x0,x1) 的 MUI_HINT_H 行(还原条下真实底色)。x0/x1 已夹到屏宽。 */ +static void hint_restore(int x0, int x1) +{ + if (x1 <= x0) return; + if (x0 < 0) x0 = 0; + if (x1 > g_xres) x1 = g_xres; + if (!g_hint_snap) { /* 无快照(分配失败):兜底填背景(旧行为) */ + const meow_theme *th = meow_theme_get(); + meow_ui_fill_rect(x0, g_hint_snap_y, x1 - x0, MUI_HINT_H, th->bg); + return; + } + int bpp = g_bpp / 8; + for (int r = 0; r < MUI_HINT_H; r++) { + size_t row = (size_t)(g_hint_snap_y + r) * g_line; + memcpy(g_fb + row + (size_t)x0 * bpp, + g_hint_snap + (size_t)r * g_line + (size_t)x0 * bpp, + (size_t)(x1 - x0) * bpp); + } + meow_gfx_mark_dirty(x0, g_hint_snap_y, x1 - x0, MUI_HINT_H); /* 标脏:调用方 flush_region 上屏 */ +} + void meow_ui_swipe_hint(int x) { if (!g_init_ok) return; @@ -361,20 +404,19 @@ void meow_ui_swipe_hint(int x) * 有状态栏(非全屏)→ y = 状态栏高度;收合(collapsed)→ y=提示线下缘 * (MUI_SBAR_COLLAPSE_H,与提示线不重叠);无状态栏(全屏/隐藏)→ y=0。 */ int hy = meow_ui_statusbar_height(); - /* 单一实现(与非全屏 Hello 完全一致,全屏仅 hy 不同):grow 画 0..x、shrunk 先清旧长 - * 再画 0..x、x<=0 清空。x<0 视为清空;条长上限=屏宽。 */ + /* 单一实现(与非全屏 Hello 完全一致,全屏仅 hy 不同):grow 画 0..x、shrunk 先把 + * 超出部分还原为快照、x<=0 整条还原。x<0 视为清空;条长上限=屏宽。条色仍取 th->fg。 */ if (x > 0) { if (x > g_xres) x = g_xres; - /* 往回滑(新长度 < 上次)时,先清掉「上次画到旧长度、但本次超出的部分」, - * 否则条只增不减(右滑变长、左滑回缩实时可见)。 */ - if (g_hint_last > x) meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg); - /* 条色取主题前景(fg):fg 定义为与背景对比,能同时覆盖「非全屏(底=主题bg)」与 - * 「全屏 Hello 类(底=主题accent)」两种情况——accent 在 Hello 全屏 accent 底上同色不可见。 */ + if (g_hint_last == 0) hint_snapshot(); /* 首现条:拍下未污染区,供此后回填 */ + /* 往回滑(新长度 < 上次)时,把「上次画到旧长度、本次超出的 [x,old)」还原为快照 + * 真实底色(全屏 accent),而非填 th->bg —— 否则左滑回缩在 accent 底上漏黑。 */ + if (g_hint_last > x) hint_restore(x, g_hint_last); meow_ui_fill_rect(0, hy, x, MUI_HINT_H, th->fg); g_hint_last = x; } else if (g_hint_last > 0) { - /* 手指回原点/抬手未达标/抬手达标:清掉此前画的条(填回背景色) */ - meow_ui_fill_rect(0, hy, g_hint_last, MUI_HINT_H, th->bg); + /* 手指回原点/抬手未达标/抬手达标:整条还原为快照真实底色(全屏 accent 上无黑残) */ + hint_restore(0, g_hint_last); g_hint_last = 0; } /* 不发 flush:由调用方 meow_ui_flush_region(0,0,宽,6) 只刷顶部条 */ @@ -832,6 +874,7 @@ void meow_ui_shutdown(void) if (g_fb_fd >= 0) close(g_fb_fd); if (g_back_buf) free(g_back_buf); if (g_dump_path) { free(g_dump_path); g_dump_path = NULL; } + if (g_hint_snap) { free(g_hint_snap); g_hint_snap = NULL; } g_back_buf = NULL; g_fb = NULL; g_fb_mem = NULL; g_fb_fd = -1; g_init_ok = 0; g_mfont_ready = 0; dirty_reset(); diff --git a/meow_ui_input.c b/meow_ui_input.c index 1f27d2d..aa19944 100644 --- a/meow_ui_input.c +++ b/meow_ui_input.c @@ -125,6 +125,7 @@ static int t_press_valid = 0; /* BTN_TOUCH=1 已见(本次按下的 gate static int t_dx = 0, t_dy = 0; /* 按下原点(BTN_TOUCH 后首个有效 ABS 锁定) */ static int t_cx = 0, t_cy = 0; /* 当前坐标(-1=本次尚未见到有效坐标) */ static int t_origin_valid = 0; /* 原点是否已锁定 */ +static uint64_t t_down_ms = 0; /* 按下时刻(monotonic ms),供实时按住时长用 */ /* 按下边沿:只标记按下,绝不用传入坐标立即锁原点(避免用上一手势残留坐标当原点)。 * t_cx/t_cy 归 -1 —— 本次手势以「无坐标」起步,只接受随后到来的有效 ABS。 */ @@ -136,6 +137,7 @@ static void touch_press(int x, int y) t_origin_valid = 0; t_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; + t_down_ms = now_ms(); /* 记按下时刻:供实时按住时长/按住视觉 */ } /* 由 ABS_X/ABS_Y 驱动:若本次按下且原点未锁且坐标有效 → 锁原点;再更新当前坐标。 */ static void touch_update(int x, int y) @@ -151,7 +153,7 @@ 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; return; } + if (!t_origin_valid) { t_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; 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; @@ -192,7 +194,34 @@ 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_cx = t_cy = -1; t_dx = t_dy = 0; t_down_ms = 0; +} + +/* ---- 实时触摸态(内部:控件绘制按压/按住视觉用)---- + * 直接读触摸状态机当前值(与语义事件队列无关;无按下/坐标未就绪时 x/y 返回 -1)。 */ +int meow_input_touch_down(void) { return t_down; } +int meow_input_touch_x(void) { return t_cx; } +int meow_input_touch_y(void) { return t_cy; } +unsigned int meow_input_touch_hold_ms(void) +{ + if (!t_down) return 0; + uint64_t e = now_ms() - t_down_ms; + if (e > 0xFFFFFFFFu) e = 0xFFFFFFFFu; + return (unsigned int)e; +} + +/* 宿主单测/内部注入:模拟按压(down=1 时 x,y=压点、hold_ms=已按住时长),设备运行时无调用方。 */ +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_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_cx = -1; t_cy = -1; t_dx = 0; t_dy = 0; t_down_ms = 0; + } } /* ---- 输入线程:poll 所有 event 设备 + 解析 ---- */ diff --git a/meow_ui_internal.h b/meow_ui_internal.h index 849c6d2..7767568 100644 --- a/meow_ui_internal.h +++ b/meow_ui_internal.h @@ -52,6 +52,18 @@ void meow_input_inject(int type, int a, int b); /* 清空队列(IME 打开前丢弃残留触摸,避免误敲)。 */ void meow_input_drain(void); +/* ---- 实时触摸态(供控件绘制按压/按住视觉;输入线程写、app 主线读)---- + * 与语义事件队列无关:这些直接读输入线程的触摸状态机当前值,让 meow_widget_draw + * 在「手指按下期间」能如实反映按钮的按下/按住视觉,而无需 app 自行追踪按下。 */ +int meow_input_touch_down(void); /* 当前是否有触摸按下 */ +int meow_input_touch_x(void); /* 当前 X(无按下=-1) */ +int meow_input_touch_y(void); /* 当前 Y(无按下=-1) */ +unsigned int meow_input_touch_hold_ms(void); /* 按下已持续毫秒(抬起后 0) */ + +/* 宿主单测/内部注入:模拟一次按压。down=1 时 x,y 为压点、hold_ms 可指定「已按住时长」 + * (把按下时刻回拨),便于测按住态;down=0 复位。设备运行时无调用方。 */ +void meow_input_touch_set(int down, int x, int y, unsigned hold_ms); + /* ---- IME(meow_ui_ime.c)---- */ /* 当前是否显示软键盘。 */ int meow_ime_active(void); diff --git a/meow_ui_widget.c b/meow_ui_widget.c index e7d0cac..58c2f4d 100644 --- a/meow_ui_widget.c +++ b/meow_ui_widget.c @@ -11,6 +11,20 @@ enum { W_BTN = 0, W_SLIDER = 1, W_SWITCH = 2, W_LIST = 3 }; +/* 按钮「按住」(长按)判定阈值(ms):超过即进入更强态(更深状态层 + 描边环)。 + * 视觉区分「短按按下」与「按住」;触发语义不变(on_tap 仍按抬手判定)。 */ +#define MEOW_BTN_HOLD_MS 500u + +/* MD3 状态层混色:c 为基色、o 为状态色、t 为 o 的占比(0-100)。 + * 用于把主题 accent 向黑/白调出「按下/按住」状态层(日/夜主题各配色)。 */ +static uint32_t blend_color(uint32_t c, uint32_t o, int t) +{ + int rr = (((c >> 16) & 0xFF) * (100 - t) + ((o >> 16) & 0xFF) * t) / 100; + int gg = (((c >> 8) & 0xFF) * (100 - t) + ((o >> 8) & 0xFF) * t) / 100; + int bb = ((c & 0xFF) * (100 - t) + (o & 0xFF) * t) / 100; + return ((uint32_t)rr << 16) | ((uint32_t)gg << 8) | (uint32_t)bb; +} + struct meow_widget { int type; int x, y, w, h; @@ -89,8 +103,31 @@ void meow_widget_draw(meow_widget *w) const meow_theme *th = meow_theme_get(); switch (w->type) { case W_BTN: { - uint32_t bg = w->pressed ? th->dim : th->accent; - meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, bg); + /* 按下/按住 = 视觉态(不改 on_tap/btn_pressed/hit 语义)。按下来源两路: + * - w->pressed:应用在 on_tap 命中后标记(既有语义,保留); + * - 实时触摸命中:SDK 输入层当前正按在该控件内(无需 app 追踪)。 + * 按住=实时触摸命中且持续 ≥MEOW_BTN_HOLD_MS;按下=任一来源。 */ + int live = meow_input_touch_down() && + 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->pressed || live; + int hold = live && meow_input_touch_hold_ms() >= MEOW_BTN_HOLD_MS; + if (hold) { + /* 按住:更强态——更深状态层 + 内缩圆角描边环(浅 accent),贴 MD3 强调; + * 动效取舍:状态层按规范即时切换(MD3 ripple 属额外动画,依赖 on_tick 逐帧, + * 非本「加视觉态」范围),不做 1-2 帧插值。 */ + uint32_t hbg = blend_color(th->accent, 0x000000, 24); + meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, hbg); + if (w->w > 6 && w->h > 6) + meow_ui_draw_rect_round(w->x + 1, w->y + 1, w->w - 2, w->h - 2, 9, + blend_color(th->accent, 0xFFFFFF, 45)); + } else if (press) { + /* 按下:轻状态层(accent 向黑调 12%,即变深/偏移),释放恢复;MD3 表面按压。 */ + meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, + blend_color(th->accent, 0x000000, 12)); + } else { + meow_gfx_fill_round(w->x, w->y, w->w, w->h, 10, th->accent); + } 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, 0xFFFFFF); break;