Files
AskaEth 3d70a3dd2c 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 推卡自刷验收全绿
2026-09-03 12:16:01 +08:00

134 lines
7.4 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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;
}