From 54e8356295e0ad2a2a36e701d0c160be3bcfc7bb Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Sun, 13 Sep 2026 17:04:12 +0800 Subject: [PATCH] =?UTF-8?q?UI=20=E4=BB=A3=E7=A0=81=EF=BC=9A=E8=A1=A5?= =?UTF-8?q?=E5=85=A8=20remo=5Fhandle/cp=5Fdraw=20=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=EF=BC=8C=E6=8B=86=E5=88=86=20cp=5Fdraw=5Fcb=20=E7=BB=98?= =?UTF-8?q?=E5=88=B6=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Drivers/BSP/UI/ui_content_page.c | 166 +++++++++++++++++++++---------- Drivers/BSP/UI/ui_net_sink.c | 46 +++++++-- 2 files changed, 150 insertions(+), 62 deletions(-) diff --git a/Drivers/BSP/UI/ui_content_page.c b/Drivers/BSP/UI/ui_content_page.c index 9d158466..6aa331a1 100644 --- a/Drivers/BSP/UI/ui_content_page.c +++ b/Drivers/BSP/UI/ui_content_page.c @@ -24,6 +24,8 @@ * 2026-09-10 王建锋 网口设置页(node 30):新增 CONTENT_PAGE_NET_CFG 入描述符表; * 新 API ui_content_page_selected_row(页面钩子感知选中行)+ * 描述符 init_cb(开页后调一次,页面模块重置运行态) + * 2026-09-13 王建锋 cp_draw_cb 按区块拆为四个绘制子函数(cp_draw_deco/header/rows/footer), + * 主函数仅编排(行数取一次供行窗口/页脚共用),行为零变化 */ /* 头文件包含区 */ @@ -181,32 +183,34 @@ static void cp_draw_centered_text(lv_layer_t *layer, const char *text, int32_t y } /* - * 函数功能:页面自绘(浅 V 装饰 + 标题/副标题 + 行数据 + 页脚页码 + 页面钩子) - * 入口参数:e - 事件指针 lv_event_t * + * 函数功能:绘制"浅 V"装饰(共享 ui_deco 模块,与菜单根框同几何) + * 入口参数:layer - 绘制层 lv_layer_t * + * d - 页面描述符 const content_page_def_t * * 返回值:无 - * 限定条件:事件为 LV_EVENT_DRAW_MAIN,页面已打开 - * 函数说明: 1. 浅 V 装饰:共享 ui_deco 模块(与菜单根框同几何) - * 2. 行数:静态页取 row_count,动态页取 count_cb(); - * 列表页窗口 = 选中行所在页(首可见行 = (sel/每页数)*每页数), - * 行槽位置固定、数据随页切换;信息页全量静态绘制 - * 3. 行文本:静态页直绘 Flash 表(不拷内存);动态页经 refresh() - * 逐行取入栈缓冲(同帧同步光栅化,安全) - * 4. 选中行整行白带(x=2..156)+ 黑字,非选中白字 - * 5. 各区块按 culling 区跳过不在重绘区的部分;末尾调 draw_cb(页自定义内容) + * 限定条件:页面已打开 + * 函数说明:每页可关(hide_deco;密码页跟随实机:无装饰) */ -static void cp_draw_cb(lv_event_t *e) { - const content_page_def_t *d = s_defs[s_open]; - lv_layer_t *layer = lv_event_get_layer(e); - - /* "浅 V"装饰:共享 ui_deco 模块(与菜单根框同几何);每页可关(密码页跟随实机:无装饰) */ +static void cp_draw_deco(lv_layer_t *layer, const content_page_def_t *d) { if (d->hide_deco == 0u) { ui_deco_draw(layer, &layer->_clip_area); } +} + +/* + * 函数功能:绘制标题/副标题(居中;整宽带 + culling) + * 入口参数:layer - 绘制层 lv_layer_t * + * d - 页面描述符 const content_page_def_t * + * 返回值:无 + * 限定条件:页面已打开 + * 函数说明:标题放置 y 每页可配(title_y,0 = 框架默认 CP_TITLE_Y); + * 副标题仅信息页(subtitle 非空);重绘区不相交则跳过区块 + */ +static void cp_draw_header(lv_layer_t *layer, const content_page_def_t *d) { + lv_area_t t; /* 标题(居中;放置 y 每页可配,0 = 框架默认) */ { int32_t ty = (d->title_y != 0) ? d->title_y : CP_TITLE_Y; - lv_area_t t; t.x1 = 0; t.x2 = CP_SCREEN_W - 1; @@ -219,8 +223,6 @@ static void cp_draw_cb(lv_event_t *e) { /* 副标题(仅信息页) */ if (d->subtitle != NULL) { - lv_area_t t; - t.x1 = 0; t.x2 = CP_SCREEN_W - 1; t.y1 = d->subtitle_y; @@ -229,9 +231,22 @@ static void cp_draw_cb(lv_event_t *e) { cp_draw_centered_text(layer, d->subtitle, d->subtitle_y); } } +} - /* 行数据:列表页窗口跟随选中行所在页,信息页全量 */ - int32_t nrows = (int32_t)cp_row_total(d); +/* + * 函数功能:绘制行数据(列表页窗口跟随选中行所在页,信息页全量;逐行 culling) + * 入口参数:layer - 绘制层 lv_layer_t * + * d - 页面描述符 const content_page_def_t * + * nrows - 总行数 int32_t(调用方取一次,与页脚同帧共用) + * 返回值:无 + * 限定条件:页面已打开 + * 函数说明: 1. 列表页窗口顶行 = (sel/每页行数)*每页行数,末页不足整页截断 + * 2. 行条带(整宽)不在重绘区则跳过(culling) + * 3. 选中行:整行白带(x=2..156,高 band_h)+ 黑字,非选中白字 + * 4. 行文本:静态 = Flash 表(不拷内存);动态 = refresh() 逐行取 + * 入栈缓冲(同帧同步光栅化,安全) + */ +static void cp_draw_rows(lv_layer_t *layer, const content_page_def_t *d, int32_t nrows) { int32_t first = 0; int32_t cnt = nrows; @@ -316,43 +331,84 @@ static void cp_draw_cb(lv_event_t *e) { lv_draw_label(layer, &ld, &strip); } } +} - /* 页脚"第 X 页,共 Y 页":列表页随选中行现算,信息页按参考图固定值;每页可关(密码页跟随实机) */ - { - char buf[32]; - uint8_t cur; - uint8_t pages; - lv_area_t t; +/* + * 函数功能:绘制页脚"第 X 页,共 Y 页" + * 入口参数:layer - 绘制层 lv_layer_t * + * d - 页面描述符 const content_page_def_t * + * nrows - 总行数 int32_t(调用方取一次,与行窗口同帧同值) + * 返回值:无 + * 限定条件:页面已打开 + * 函数说明:列表页随选中行现算,信息页按参考图固定值;每页可关 + * (hide_footer;密码页跟随实机) + */ +static void cp_draw_footer(lv_layer_t *layer, const content_page_def_t *d, int32_t nrows) { + char buf[32]; /* 页行文本缓冲(栈):"第 %u 页,共 %u 页" 最长远不足 32B */ + uint8_t cur; /* 当前页码(1 基) */ + uint8_t pages; /* 总页数 */ + lv_area_t t; /* 页脚条带区域(整宽,放置于 CP_PAGE_LINE_Y) */ - if (d->selectable != 0u) { - cur = (uint8_t)(s_sel / d->rows_per_page + 1u); - pages = (uint8_t)((nrows + (int32_t)d->rows_per_page - 1) / d->rows_per_page); - } - else { - cur = d->fixed_cur_page; - pages = d->fixed_total_pages; - } - - t.x1 = 0; - t.x2 = CP_SCREEN_W - 1; - t.y1 = CP_PAGE_LINE_Y; - t.y2 = CP_PAGE_LINE_Y + 15; - if (d->hide_footer == 0u && cp_areas_intersect(&t, &layer->_clip_area)) { - /* lv_snprintf 而非 snprintf:MSVC 对 printf 族做格式串扫描,中文字面量会误报 C4819 */ - lv_snprintf(buf, sizeof(buf), "第 %u 页,共 %u 页", (unsigned)cur, (unsigned)pages); - - lv_draw_label_dsc_t ld; - - /* ofs 相对 coords 原点(t.y1=CP_PAGE_LINE_Y),垂直偏移取 0 */ - lv_draw_label_dsc_init(&ld); - ld.base.layer = layer; - ld.text = buf; /* 栈缓冲:本页码每次现算 */ - ld.color = lv_color_white(); - ld.ofs_x = 6; - ld.ofs_y = 0; - lv_draw_label(layer, &ld, &t); - } + /* 页码计算,两种来源: + * - 列表页(selectable):随选中行现算——当前页 = 选中行/每页行数 + 1 + * (选中行所在页);总页数 = ceil(nrows/每页行数), + * 写成经典向上取整 (n + p - 1) / p(nrows 恰为整页倍数时不多算一页); + * nrows 由调用方取一次、与行窗口共用,保证同帧页码与窗口一致 + * - 信息/表单页(非 selectable):取参考图固定值(fixed_cur_page / fixed_total_pages) */ + if (d->selectable != 0u) { + cur = (uint8_t)(s_sel / d->rows_per_page + 1u); + pages = (uint8_t)((nrows + (int32_t)d->rows_per_page - 1) / d->rows_per_page); } + else { + cur = d->fixed_cur_page; + pages = d->fixed_total_pages; + } + + /* 页脚条带:整宽,放置于 CP_PAGE_LINE_Y(143,字形顶距已计入放置 y, + * 墨迹与参考图对齐);高 15 = 字体行高 13 + 余量,整行文字一次覆盖 */ + t.x1 = 0; + t.x2 = CP_SCREEN_W - 1; + t.y1 = CP_PAGE_LINE_Y; + t.y2 = CP_PAGE_LINE_Y + 15; + + /* 双重门控:① 本页开启页脚(hide_footer;密码页跟随实机:无页码) + * ② 页脚条带与重绘区相交(culling)——两者皆成立才格式化并绘制 */ + if (d->hide_footer == 0u && cp_areas_intersect(&t, &layer->_clip_area)) { + /* lv_snprintf 而非 snprintf:MSVC 对 printf 族做格式串扫描,中文字面量会误报 C4819 */ + lv_snprintf(buf, sizeof(buf), "第 %u 页,共 %u 页", (unsigned)cur, (unsigned)pages); + + lv_draw_label_dsc_t ld; + + /* ofs 相对 coords 原点(t.x1=0,t.y1=CP_PAGE_LINE_Y): + * 水平 6 = 左对齐、左留白 6px(与实机一致);垂直 0(顶距已计入放置 y) */ + lv_draw_label_dsc_init(&ld); + ld.base.layer = layer; + ld.text = buf; /* 栈缓冲:本页码每次现算,同帧内有效(同步光栅化) */ + ld.color = lv_color_white(); /* 页码恒白字(不像选中行那样反色) */ + ld.ofs_x = 6; + ld.ofs_y = 0; + lv_draw_label(layer, &ld, &t); + } +} + +/* + * 函数功能:页面自绘(编排:浅 V 装饰 + 标题/副标题 + 行数据 + 页脚页码 + 页面钩子) + * 入口参数:e - 事件指针 lv_event_t * + * 返回值:无 + * 限定条件:事件为 LV_EVENT_DRAW_MAIN,页面已打开 + * 函数说明:行数取一次(cp_row_total)供行窗口与页脚页码共用(同帧同值); + * 各区块由子函数绘制(deco/header/rows/footer),均按 culling 区 + * 跳过不在重绘区的部分;末尾调 draw_cb(页自定义内容,在内置区块之后) + */ +static void cp_draw_cb(lv_event_t *e) { + const content_page_def_t *d = s_defs[s_open]; + lv_layer_t *layer = lv_event_get_layer(e); + int32_t nrows = (int32_t)cp_row_total(d); /* 行数取一次:供行窗口与页脚页码共用 */ + + cp_draw_deco(layer, d); + cp_draw_header(layer, d); + cp_draw_rows(layer, d, nrows); + cp_draw_footer(layer, d, nrows); /* 页面钩子:自定义内容(表单/操作页;在内置区块之后绘制) */ if (d->draw_cb != NULL) { diff --git a/Drivers/BSP/UI/ui_net_sink.c b/Drivers/BSP/UI/ui_net_sink.c index dcadce9d..8b3680da 100644 --- a/Drivers/BSP/UI/ui_net_sink.c +++ b/Drivers/BSP/UI/ui_net_sink.c @@ -139,12 +139,24 @@ static void remo_send_init(void) } /* - * 函数功能:处理一帧完整的远端命令(客户端→设备) - * 入口参数:f - 完整帧 [TAG][CMD][LEN][DATA][CRC];n - 帧总长度 + * 函数功能:处理一帧完整的远端命令(客户端→设备),校验通过后按命令分发执行 + * 入口参数:f - 完整帧 [TAG][CMD][LEN][DATA][CRC](数据段自偏移 4 起) + * n - 帧总长度(字节) * 返回值:无 - * 限定条件:调用前帧已通过 CRC 与 TAG 校验 - * 函数说明:按命令分发:KEEPLIVE 回心跳 / INIT 回尺寸 / KEY 注入按键 / - * LCDMEM 回送 1bpp 帧缓冲(经平台后端取帧);非法情况静默丢弃 + * 限定条件:f 指向至少 n 字节可读空间;由解析状态机 rm_parser_feed 在 CRC 通过后调用 + * 函数说明:1. 前置校验(任一失败即静默丢弃:不应答、不断连接): + * a) n >= 5:最短合法帧 5 字节(LEN=0 时 TAG/CMD/LEN_H/LEN_LO/CRC) + * b) 4+len+1 <= n:长度字段与帧长一致,防长度损坏引发越界读 + * c) CRC 与数据段逐字节异或一致(仅覆盖数据段,不含头与 CRC 自身) + * d) TAG 必须为 TAG_CLIENT(0xAA):服务端只收客户端方向帧 + * 状态机已做过等价校验,此处复核使本函数自包含(可独立调用) + * 2. 按命令分发: + * KEEPLIVE(0) - 原样回显空心跳帧,维持连接保活 + * INIT(1) - 回送屏幕几何 [宽u16][高u16][帧字节数u32](大端,6 字节) + * KEY(2) - 数据段首字节按键码经平台后端注入 UI,回空帧作确认 + * LCDMEM(3) - 数据段为 4B 大端偏移;偏移 0=整帧,经平台后端取 1bpp + * 位图后回送 [偏移u32][位图 3200B],非 0 偏移暂不支持 + * 3. 未知命令码静默丢弃 */ static void remo_handle(const uint8_t *f, int n) { @@ -154,49 +166,68 @@ static void remo_handle(const uint8_t *f, int n) const uint8_t *data; uint8_t crc; + /* a) 最小帧长:TAG+CMD+LEN_H+LEN_LO+CRC 共 5 字节,不足必是残帧/噪声 */ if (n < 5) { return; } + + /* 解析帧头:TAG=方向(0xAA 客户端帧 / 0xBB 服务端帧),CMD=命令码, + * LEN=数据段长度(16 位大端),数据段自偏移 4 起 */ tag = f[0]; cmd = f[1]; len = (uint16_t)(((uint16_t)f[2] << 8) | (uint16_t)f[3]); data = f + 4; + + /* b) 长度一致性:整帧长须恰为 4+len+1,防长度字段损坏导致越界读帧缓冲 */ if ((int)(4 + len + 1) > n) { return; } + + /* c) CRC 校验:数据段逐字节异或(LEN=0 时恒 0)须与帧尾一致,坏帧静默丢弃 */ crc = f[4 + len]; if (crc != remo_crc(data, len)) { return; } + + /* d) 方向校验:只接受客户端帧 0xAA;0xBB 为服务端→客户端方向,出现在收包路径即非法 */ if (tag != TAG_CLIENT) { return; } switch (cmd) { case CMD_KEEPLIVE: + /* 心跳保活:原样回显一个空 KEEPLIVE 帧,维持连接活性 */ remo_send(CMD_KEEPLIVE, NULL, 0); break; case CMD_INIT: + /* 查询屏幕参数:回送 [宽u16][高u16][帧字节数u32](大端,共 6 字节) */ remo_send_init(); break; case CMD_KEY: + /* 远端导航按键:数据段首字节为按键码,经平台后端注入 UI 按键队列 */ if (len >= 1) { ui_net_sink_inject_key(data[0]); } + /* 回空帧作“已接收”确认(LEN=0,CRC=0) */ remo_send(CMD_KEY, NULL, 0); break; case CMD_LCDMEM: { + /* 读取显存:数据段为 4 字节大端起始偏移,不足 4 字节判非法丢弃 */ if (len < 4) { break; } { + /* 偏移按大端 32 位拼装(协议约定多字节字段大端) */ uint32_t off = ((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) | ((uint32_t)data[2] << 8) | ((uint32_t)data[3]); + /* 目前仅支持偏移 0(整帧)请求;非 0 偏移暂不支持,静默忽略 */ if (off == 0) { - /* 整帧请求:经平台后端取 1bpp 位图后回送(失败则静默丢弃) */ + /* 整帧请求:经平台后端取 1bpp 位图(MCU: ui_port_get_framebuffer / + * 模拟器: sim_port_get_screen_mono)写入应答缓冲尾部,失败则静默丢弃 */ if (ui_net_sink_get_lcdmem(g_lcdmem_data + 4, UI_MEMSIZE) == 0) { + /* 应答数据段 = [偏移u32(回填请求原值)] + [1bpp 位图 3200B] */ memcpy(g_lcdmem_data, data, 4); remo_send(CMD_LCDMEM, g_lcdmem_data, (uint16_t)(4 + UI_MEMSIZE)); } @@ -205,6 +236,7 @@ static void remo_handle(const uint8_t *f, int n) break; } default: + /* 未知命令码:静默丢弃,保持连接等待后续帧 */ break; } } @@ -374,7 +406,7 @@ int ui_net_sink_start(uint16_t port) * 入口参数:无 * 返回值:无 * 限定条件:ui_net_sink_start() 已成功调用 - * 函数说明:1. 网络代次变化(重建)时关闭旧监听/客户端并重建 + * 函数说明: 1. 网络代次变化(重建)时关闭旧监听/客户端并重建 * 2. 无监听(启动失败)时重试启动 * 3. 无客户端时尝试 accept,成功则复位状态机 * 4. 有客户端时非阻塞 recv 逐字节喂入状态机