- 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 推卡自刷验收全绿
134 lines
7.4 KiB
C
134 lines
7.4 KiB
C
/* 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;
|
||
}
|