完成了,菜单内容的初级显示
This commit is contained in:
@@ -10,13 +10,20 @@ if(MSVC)
|
||||
add_compile_options(/utf-8)
|
||||
endif()
|
||||
|
||||
# 可选:启用 DEBUG 宏,用于条件编译输出调试信息(cmake -DENABLE_DEBUG=ON ..)
|
||||
option(ENABLE_DEBUG "Enable debug printf output" OFF)
|
||||
if(ENABLE_DEBUG)
|
||||
add_compile_definitions(DEBUG)
|
||||
endif()
|
||||
|
||||
add_executable(DTU-HMI
|
||||
src/main.c
|
||||
src/thread_utils.c
|
||||
src/remoteDisplay.c
|
||||
src/Drv/menu.c
|
||||
src/Drv/lcd.c
|
||||
src/Drv/Ascii.c
|
||||
src/thread_utils.c
|
||||
src/remoteDisplay.c
|
||||
src/Drv/display.c
|
||||
src/TCP/tcp.c
|
||||
)
|
||||
|
||||
|
||||
397
README.md
397
README.md
@@ -1,64 +1,375 @@
|
||||
# PC_HMI – 本地 HMI 菜单逻辑的 PC 端模拟
|
||||
# DTU-HMI
|
||||
|
||||
本目录提供一个 **纯 C 语言的控制台程序**,在 PC 上模拟嵌入式 HMI 的菜单交互逻辑,方便调试和理解 `Menu_Route` / `Menu_Show_Proc` 的行为。
|
||||
PC 端 HMI 菜单逻辑模拟程序,纯 C 实现,支持菜单树、LCD 显示、TCP 远程显示(RemoDispBus 协议)及 UTF-8 汉字库。
|
||||
|
||||
## 环境搭建
|
||||
---
|
||||
|
||||
### 1. 安装 CMake
|
||||
|
||||
1. 访问 CMake 官网下载页:[`https://cmake.org/download/`](https://cmake.org/download/)
|
||||
2. 下载适用于 Windows 的安装包(通常为 `cmake-*-windows-x86_64.msi`),安装时**勾选将 CMake 添加到 PATH**。
|
||||
3. 安装完成后打开终端(PowerShell / CMD),执行:
|
||||
|
||||
```powershell
|
||||
cmake --version
|
||||
```
|
||||
|
||||
能看到版本信息即表示安装成功。
|
||||
|
||||
### 2. 编译器与编码说明
|
||||
|
||||
- Windows 下默认使用 **Visual Studio / MSVC** 工具链进行编译。
|
||||
- 源文件采用 **UTF‑8 编码并包含中文注释**,`CMakeLists.txt` 中已经为 MSVC 打开 `/utf-8` 选项,无需额外配置即可正常编译。
|
||||
|
||||
|
||||
## 目录结构
|
||||
|
||||
- `CMakeLists.txt` – CMake 构建配置
|
||||
- `include/menu.h` – 菜单数据结构与接口声明
|
||||
- `include/tcp.h` – TCP 通信接口(客户端/服务端)
|
||||
- `src/menu.c` – 简单菜单树与菜单调度逻辑(PC 版)
|
||||
- `src/main.c` – 主程序入口,从键盘读取按键并驱动菜单
|
||||
- `src/tcp.c` – TCP 实现(Windows Winsock / Linux socket)
|
||||
|
||||
## 构建步骤
|
||||
|
||||
### 1. Windows 示例(当前工程默认方式)
|
||||
|
||||
在仓库根目录下执行(注意路径中有空格时要加引号):
|
||||
## 快速开始
|
||||
|
||||
```powershell
|
||||
mkdir build
|
||||
cd "D:\Code\DTU 程序\DTU-HMI\build"
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
### 2. 通用(Linux / macOS 等)
|
||||
生成可执行文件 `DTU-HMI.exe`(Windows)或 `DTU-HMI`(Linux)。
|
||||
|
||||
---
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
DTU-HMI/
|
||||
├── CMakeLists.txt
|
||||
├── gen_utf8_hz12.py # 12×12 UTF-8 汉字库生成脚本
|
||||
├── remo_disp_server.py # 远程显示 Python 服务端
|
||||
├── include/
|
||||
│ └── types.h
|
||||
├── src/
|
||||
│ ├── main.c
|
||||
│ ├── thread_utils.c/h
|
||||
│ ├── remoteDisplay.c/h
|
||||
│ ├── TCP/tcp.c, tcp.h
|
||||
│ └── Drv/
|
||||
│ ├── menu.c/h
|
||||
│ ├── display.c/h
|
||||
│ ├── lcd.c/h
|
||||
│ ├── Ascii.c/h
|
||||
│ └── utf8_hz12_data.c/h # 由脚本生成
|
||||
└── build/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 环境要求
|
||||
|
||||
- **CMake** 3.10+
|
||||
- **编译器**:Windows 默认 MSVC;Linux 需 GCC/Clang
|
||||
- **编码**:源文件 UTF-8,CMake 已配置 MSVC `/utf-8`
|
||||
|
||||
---
|
||||
|
||||
## 构建步骤
|
||||
|
||||
### Windows
|
||||
|
||||
```powershell
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
### Linux / macOS
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ../
|
||||
cmake ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
生成的可执行文件名为:
|
||||
|
||||
```text
|
||||
pc_hmi # Windows / Linux 下均可
|
||||
```
|
||||
---
|
||||
|
||||
## TCP 通信
|
||||
|
||||
程序监听端口 **7003**(RemoDispBus 默认)。使用 `remo_disp_server.py` 连接后可实时查看 LCD 显存画面。协议支持 `CMD_INIT`、`CMD_LCDMEM`、`CMD_KEY`、`CMD_KEEPLIVE`。
|
||||
|
||||
---
|
||||
|
||||
## 菜单系统
|
||||
|
||||
### 1. 概述
|
||||
|
||||
菜单由**静态表** `g_tMenuModelTab` 定义,经 `Menu_Main_Creat_01` 构建为**可遍历树** `g_tMenuItem[]`。每个结点有 `ptHigher/ptLower/ptBefore/ptBehind` 四个指针,同级首尾成环。
|
||||
|
||||
### 2. 数据结构
|
||||
|
||||
#### 2.1 静态菜单定义(tagMenuModel)
|
||||
|
||||
```c
|
||||
typedef struct
|
||||
{
|
||||
uint8_t byClass; // 菜单分级标志 0/1/2/3
|
||||
uint8_t byName[50]; // 菜单字符串
|
||||
uint8_t byTip[50]; // 菜单提示文本
|
||||
uint8_t byAttrib; // 菜单属性
|
||||
uint16_t wPassword; // 访问密码,0x0000 表示无密码
|
||||
uint16_t wPara; // 菜单执行函数参数
|
||||
FUNCPTR pfnWinProc; // 界面执行函数指针
|
||||
} tagMenuModel, *tagPMenuModel;
|
||||
|
||||
/* 注意:表定义时顺序不能乱,需从 0 级开始,一级一级按顺序写入 */
|
||||
const tagMenuModel g_tMenuModelTab[] = { ... };
|
||||
```
|
||||
|
||||
#### 2.2 菜单遍历树(tagMenuItem)
|
||||
|
||||
```c
|
||||
/* 每个菜单包含:一、上下前后等级关系;二、属性与内容;三、显示坐标 */
|
||||
typedef struct _MENU_ITEM_
|
||||
{
|
||||
struct _MENU_ITEM_ *ptHigher; // 上级菜单指针
|
||||
struct _MENU_ITEM_ *ptLower; // 下级菜单指针
|
||||
struct _MENU_ITEM_ *ptBefore; // 同级上方指针
|
||||
struct _MENU_ITEM_ *ptBehind; // 同级下方指针
|
||||
|
||||
uint8_t byClass;
|
||||
uint8_t byName[50];
|
||||
uint8_t byTip[50];
|
||||
uint8_t byAttrib;
|
||||
uint16_t wPassword;
|
||||
uint16_t wPara;
|
||||
FUNCPTR pfnWinProc;
|
||||
|
||||
uint16_t wPos, wNum;
|
||||
uint16_t wSPosX, wSPosY, wEPosX, wEPosY;
|
||||
} tagMenuItem, *tagPMenuItem;
|
||||
|
||||
tagMenuItem g_tMenuItem[300]; /* 所有菜单存储于此数组 */
|
||||
```
|
||||
|
||||
### 3. 菜单构建示例
|
||||
|
||||
#### 3.1 静态表(g_tMenuModelTab)
|
||||
|
||||
```c
|
||||
const tagMenuModel g_tMenuModelTab[] =
|
||||
{
|
||||
{ 0, " ", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 1, "装置信息", "查看装置信息", 0, 0x0000, EN_ANA_0, (FUNCPTR)MenuProc_See_AppInfo },
|
||||
{ 1, "实时数据", "装置实时数据", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "交流量", "查看遥测交流量", EN_MEA_AC, 0x0000, EN_ANA_0, (FUNCPTR)MenuProc_See_YC },
|
||||
{ 2, "直流量", "查看遥测直流量", EN_MEA_DC, 0x0000, EN_ANA_0, (FUNCPTR)MenuProc_See_YC },
|
||||
{ 2, "遥信量", "查看遥信开入量", EN_INPUT_RLY_ALL, 0x0000, EN_INPUT_0, (FUNCPTR)MenuProc_See_Input },
|
||||
{ 1, "参数定值", "保护参数查看与修改", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "整定", "整定装置保护参数", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 3, "参数", "查看设备参数定值", EN_FIGURE_SET, CN_USER_PWD, EN_SIDE_BASIC, (FUNCPTR)MenuProc_Set_Value },
|
||||
{ 3, "定值", "设置装置数值定值", EN_FIGURE_SET, CN_USER_PWD, EN_SIDE_DEF, (FUNCPTR)MenuProc_Set_Value },
|
||||
{ 3, "控制字", "设置装置控制字", EN_SOFT_SET, CN_USER_PWD, EN_SIDE_DEF, (FUNCPTR)MenuProc_Set_Value },
|
||||
{ 3, "软压板", "设置软压板", 0, CN_USER_PWD, EN_SOFT_PRO, (FUNCPTR)MenuProc_Set_Soft },
|
||||
{ 2, "查看", "查看装置保护参数", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 3, "参数", "设置设备参数定值", EN_FIGURE_SET, 0x0000, EN_SIDE_BASIC, (FUNCPTR)MenuProc_See_Set },
|
||||
{ 3, "定值", "查看数值型定值", EN_FIGURE_SET, 0x0000, EN_SIDE_DEF, (FUNCPTR)MenuProc_See_Set },
|
||||
{ 3, "控制字", "查看控制字定值", EN_SOFT_SET, 0x0000, EN_SIDE_DEF, (FUNCPTR)MenuProc_See_Set },
|
||||
{ 3, "软压板", "查看软压板", 0, 0x0000, EN_SOFT_PRO, (FUNCPTR)MenuProc_See_Soft },
|
||||
{ 1, "三遥设置", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "遥测死区", "设置遥测量死区门槛", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YC_SetSqValue },
|
||||
{ 2, "遥测系数", "设置遥测量微调系数", EN_MEA_ADJ, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YC_SetAdjCoe },
|
||||
{ 2, "遥信类型", "设置遥信类型", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YX_SetCommType },
|
||||
{ 2, "遥信防抖", "设置遥信防抖时间", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YX_SetWidth },
|
||||
{ 2, "双点遥信", "设置双点遥信虚端子", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_YX_SetTwin },
|
||||
{ 1, "装置维护", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "时钟设置", "设置系统时钟", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_Time },
|
||||
{ 2, "强制复归", "可复归未返回事件", EN_REV_FORCE, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_RevEvent },
|
||||
{ 2, "手动录波", "启动手动录波", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_ManualWave },
|
||||
{ 2, "清除记录", "", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_ClrRec },
|
||||
{ 1, "通讯参数", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "通讯设置", "外部通讯设置", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_ComPara },
|
||||
{ 2, "网口设置", "", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_EditIP },
|
||||
{ 2, "SNTP设置", "", 0, CN_USER_PWD, 0, (FUNCPTR)MenuProc_Cfg_EditSntp },
|
||||
{ 1, "记录查询", "查看各种装置记录", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "SOE记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecSOE },
|
||||
{ 2, "事故记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecAct },
|
||||
{ 2, "操作记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecOpt },
|
||||
{ 2, "保护告警", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecAlm },
|
||||
{ 2, "保护启动", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecStart },
|
||||
{ 2, "遥控记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecYK },
|
||||
{ 2, "自检记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecChk },
|
||||
{ 2, "运行记录", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecRun },
|
||||
{ 2, "运行报告", "", 0, 0x0000, 0, (FUNCPTR)MenuProc_See_RecFault },
|
||||
{ 0, "厂家设置", "设置装置相关参数", 0, CN_COP_PWD, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 1, "元件配置", "配置元件配置", 0, CN_COP_PWD, EN_FACTORY_PASSWORD,(FUNCPTR)MenuProc_Cfg_CellConf },
|
||||
{ 1, "恢复默认", "恢复默认元件定值参数", 0, CN_COP_PWD, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "全部恢复", "全部参数恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_AllInf_Default },
|
||||
{ 2, "默认参数", "当前参数恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Para_Default },
|
||||
{ 2, "默认定值", "当前定值区恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Set_Default },
|
||||
{ 2, "软压板", "当前软压板恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Resume_Soft },
|
||||
{ 2, "元件配置", "元件配置恢复默认", 0, CN_COP_PWD, EN_NO_USER_PWD, (FUNCPTR)MenuProc_Cfg_CellDef },
|
||||
{ 1, "交流显示", "交流显示方式设置", 0, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Cfg_ShowAnaType },
|
||||
{ 1, "装置调试", "调试装置", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "虚拟遥信", "设置虚拟遥信值", 0, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_XuYX },
|
||||
{ 2, "交流虚遥测", "设置虚拟交流遥测值", EN_MEA_AC, CN_COP_PWD, EN_ANA_0, (FUNCPTR)MenuProc_Dbg_XuYC },
|
||||
{ 2, "直流虚遥测", "设置虚拟直流遥测值", EN_MEA_DC, CN_COP_PWD, EN_ANA_0, (FUNCPTR)MenuProc_Dbg_XuYC },
|
||||
{ 2, "电度虚遥测", "设置虚拟电度遥测值", EN_MEA_POWER, CN_COP_PWD, EN_ANA_0, (FUNCPTR)MenuProc_Dbg_XuYC },
|
||||
{ 2, "动作虚事件", "设置虚拟动作事件", EN_ACT_REC, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_XuEvent },
|
||||
{ 2, "告警虚事件", "设置虚拟告警事件", EN_ALM_REC, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_XuEvent },
|
||||
{ 2, "动作出口", "进入此菜单保护退出", EN_OUTPUT_TRIP, CN_COP_PWD, EN_INPUT_0, (FUNCPTR)MenuProc_Dbg_Relay },
|
||||
{ 2, "信号出口", "进入此菜单保护退出", EN_OUTPUT_SIGN, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_Relay },
|
||||
{ 1, "版本信息", "查看板件版本信息", 0, CN_COP_PWD, 0, (FUNCPTR)MenuProc_See_VersionBoard },
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.2 解析后的树形结构
|
||||
|
||||
根据上表,`Menu_Main_Creat_01` 解析后的菜单树为:
|
||||
|
||||
```text
|
||||
0 级目录 {
|
||||
1.装置信息
|
||||
1.实时数据 {
|
||||
2.交流量
|
||||
2.直流量
|
||||
2.遥信量
|
||||
}
|
||||
1.参数定值 {
|
||||
2.整定 {
|
||||
3.参数
|
||||
3.定值
|
||||
3.控制字
|
||||
3.软压板
|
||||
}
|
||||
2.查看 {
|
||||
3.参数
|
||||
3.定值
|
||||
3.控制字
|
||||
3.软压板
|
||||
}
|
||||
}
|
||||
1.三遥设置 {
|
||||
2.遥测死区
|
||||
2.遥测系数
|
||||
2.遥信类型
|
||||
2.遥信防抖
|
||||
2.双点遥信
|
||||
}
|
||||
1.装置维护 {
|
||||
2.时钟设置
|
||||
2.强制复归
|
||||
2.手动录波
|
||||
2.清除记录
|
||||
2.通讯参数
|
||||
2.通讯设置
|
||||
2.网口设置
|
||||
2.SNTP设置
|
||||
}
|
||||
1.记录查询 {
|
||||
2.SOE记录
|
||||
2.事故记录
|
||||
2.操作记录
|
||||
2.保护告警
|
||||
2.保护启动
|
||||
2.遥控记录
|
||||
2.自检记录
|
||||
2.运行记录
|
||||
2.运行报告
|
||||
}
|
||||
}
|
||||
|
||||
0.厂家设置 {
|
||||
1.元件配置
|
||||
1.恢复默认 {
|
||||
2.全部恢复
|
||||
2.默认参数
|
||||
2.默认定值
|
||||
2.软压板
|
||||
2.元件配置
|
||||
}
|
||||
1.交流显示
|
||||
1.装置调试 {
|
||||
2.虚拟遥信
|
||||
2.交流虚遥测
|
||||
2.直流虚遥测
|
||||
2.电度虚遥测
|
||||
2.动作虚事件
|
||||
2.告警虚事件
|
||||
2.动作出口
|
||||
2.信号出口
|
||||
}
|
||||
1.版本信息
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3 单例解析示例:直流量
|
||||
|
||||
以 `{2,"直流量","查看遥测直流量",EN_MEA_DC,0x0000,EN_ANA_0,(FUNCPTR)MenuProc_See_YC}` 为例,构建后该菜单项的指针与属性为:
|
||||
|
||||
```text
|
||||
{
|
||||
ptHigher = 1.实时数据;
|
||||
ptLower = NULL;
|
||||
ptBefore = 2.交流量;
|
||||
ptBehind = 2.遥信量;
|
||||
|
||||
byClass = 2;
|
||||
byName = 直流量;
|
||||
byTip = 查看遥测直流量;
|
||||
byAttrib = EN_MEA_DC;
|
||||
wPassword = 0x0000;
|
||||
wPara = 0;
|
||||
pfnWinProc = MenuProc_See_YC;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4 同级首尾成环
|
||||
|
||||
同级菜单中,首尾通过 `ptBefore/ptBehind` 相连形成环。例如 2 级子菜单:
|
||||
|
||||
```text
|
||||
2.虚拟遥信
|
||||
2.交流虚遥测
|
||||
2.直流虚遥测
|
||||
2.电度虚遥测
|
||||
2.动作虚事件
|
||||
2.告警虚事件
|
||||
2.动作出口
|
||||
2.信号出口
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- `2.虚拟遥信` 的 `ptBefore` 指向 `2.信号出口`(首的前一个是尾)
|
||||
- `2.信号出口` 的 `ptBehind` 指向 `2.虚拟遥信`(尾的后一个是首)
|
||||
|
||||
### 4. 构建流程(Menu_Main_Creat_01)
|
||||
|
||||
按 `g_tMenuModelTab` 的定义顺序逐项处理,通过比较 `byCurClass` 与 `byNextClass` 设置每个菜单的层级指针。
|
||||
|
||||
#### 情况 1:byCurClass < byNextClass(下一项更深,进入子菜单)
|
||||
|
||||
```text
|
||||
ptCurrent ptNextNode
|
||||
(当前) (下一项,更深一级)
|
||||
│ │
|
||||
│ ptLower ──────────────────►│
|
||||
│◄───────────────── ptHigher │
|
||||
│ │
|
||||
该级尾不变 新一级的 首=尾=ptNextNode
|
||||
```
|
||||
|
||||
#### 情况 2:byCurClass == byNextClass(同级,并列)
|
||||
|
||||
```text
|
||||
ptCurrent ─── ptBehind ──► ptNextNode
|
||||
│ │
|
||||
│ ptBefore ◄───────────┤
|
||||
│ │
|
||||
└──── ptHigher (同) ────────┘
|
||||
该级 尾 更新为 ptNextNode
|
||||
```
|
||||
|
||||
#### 情况 3:byCurClass > byNextClass(下一项更浅,回到上层)
|
||||
|
||||
```text
|
||||
... byCurClass 级 ... byNextClass 级 ...
|
||||
ptLast[byNextClass] 已存在
|
||||
│
|
||||
│ ptBehind ──► ptNextNode(新)
|
||||
│ │
|
||||
│◄── ptBefore ────┤
|
||||
│ ptHigher = 该级尾的 ptHigher
|
||||
同时:从 byCurClass 到 byNextClass+1 各级首尾成环
|
||||
ptLast[级]──►ptFirst[级],ptFirst[级]──►ptLast[级]
|
||||
```
|
||||
|
||||
#### 最后:各级首尾成环
|
||||
|
||||
表遍历完后,从 **0 级到当前结点所在级**,把该级首尾连成环:
|
||||
|
||||
```text
|
||||
ptFirst[级] ◄──────────────► ptLast[级]
|
||||
│ │
|
||||
└──── ptBehind ───────────────┘
|
||||
◄──────── ptBefore ────────────┘
|
||||
```
|
||||
|
||||
@@ -79,20 +79,12 @@ enum _STR_FLAG_ {
|
||||
|
||||
} enumStrType;
|
||||
|
||||
// 主菜单定义
|
||||
//============================================================
|
||||
// byMenuClass; 菜单分级标志(最大级数不能超过3级);
|
||||
// byName[50]; 菜单字符串;
|
||||
// byTip[36]; 菜单提示文本;
|
||||
// byAttrib; 菜单属性,设置菜单特殊显示效果;
|
||||
// wPassword; 访问密码,0x0000表示没有密码;
|
||||
// wPara; 菜单执行函数参数;
|
||||
// pfnWinProc; 界面执行函数指针;
|
||||
// 如果CN_HAVE_HIDE_MENU 为 TRUE ,0 级菜单的最后一组菜单为隐藏菜单
|
||||
//===========================================================
|
||||
/* 静态菜单表
|
||||
注意:该表定义时顺序不能乱,需要从 0 级开始,一级一经按顺序写入
|
||||
*/
|
||||
const tagMenuModel g_tMenuModelTab[] =
|
||||
{
|
||||
{ 0, " ", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 0, "主界面", "", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 1, "装置信息", "查看装置信息", 0, 0x0000, EN_ANA_0, (FUNCPTR)MenuProc_See_AppInfo },
|
||||
{ 1, "实时数据", "装置实时数据", 0, 0x0000, 0, (FUNCPTR)Menu_NonPfunc },
|
||||
{ 2, "交流量", "查看遥测交流量", EN_MEA_AC, 0x0000, EN_ANA_0, (FUNCPTR)MenuProc_See_YC },
|
||||
@@ -154,3 +146,4 @@ const tagMenuModel g_tMenuModelTab[] =
|
||||
{ 2, "信号出口", "进入此菜单保护退出", EN_OUTPUT_SIGN, CN_COP_PWD, 0, (FUNCPTR)MenuProc_Dbg_Relay },
|
||||
{ 1, "版本信息", "查看板件版本信息", 0, CN_COP_PWD, 0, (FUNCPTR)MenuProc_See_VersionBoard },
|
||||
};
|
||||
const uint32_t MENU_MAX_ITEM = sizeof(g_tMenuModelTab) / sizeof(tagMenuModel);
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "../../include/types.h"
|
||||
|
||||
|
||||
// 菜单常量表定义
|
||||
/* 静态菜定义需要的属性 */
|
||||
typedef struct
|
||||
{
|
||||
uint8_t byClass; // 菜单分级标志;
|
||||
@@ -16,6 +16,12 @@ typedef struct
|
||||
FUNCPTR pfnWinProc; // 界面执行函数指针;
|
||||
}tagMenuModel,*tagPMenuModel;
|
||||
|
||||
extern const tagMenuModel g_tMenuModelTab[];
|
||||
extern const uint32_t MENU_MAX_ITEM;
|
||||
|
||||
|
||||
|
||||
|
||||
//菜单密码设置
|
||||
//==============================================================================
|
||||
|
||||
|
||||
301
src/Drv/lcd.c
301
src/Drv/lcd.c
@@ -1,7 +1,7 @@
|
||||
#include "lcd.h"
|
||||
#include <string.h>
|
||||
#include "ascii.h"
|
||||
|
||||
#include <math.h>
|
||||
tagScreenControl g_tCVsr; // 当前界面结构指针
|
||||
tagDspAttrib g_tDspAttrib; // 显示属性
|
||||
|
||||
@@ -23,26 +23,6 @@ void Lcd_Init(void)
|
||||
g_tCVsr.wASCIIFontHeight = 12;
|
||||
g_tDspAttrib.rowSpace = 2;
|
||||
}
|
||||
|
||||
/* 填充矩形区域 */
|
||||
void Lcd_FillRect(uint16_t lx, uint16_t ty, uint16_t rx, uint16_t by, uint32_t dwRGB)
|
||||
{
|
||||
uint16_t h, l, err; /* 行/列循环变量;CheckPosi 返回值(本函数未使用) */
|
||||
uint32_t dwRRGB; /* 保存进入前的字体色,用于退出时恢复 */
|
||||
|
||||
dwRRGB = g_tCVsr.wFontColor;
|
||||
g_tCVsr.wFontColor = dwRGB;
|
||||
|
||||
/* 按行扫描矩形区域,逐点用当前颜色绘制(__Lcd_PixelFT 使用当前字体色作为像素色) */
|
||||
for ( h = ty; h <= by; h++ )
|
||||
{
|
||||
for ( l = lx; l <= rx; l++ )
|
||||
{
|
||||
Lcd_SetPixel(l, h, g_tCVsr.wFontColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Lcd_SetPixel(uint16_t x, uint16_t y, uint16_t color)
|
||||
{
|
||||
if (x >= LCD_SIZE_X || y >= LCD_SIZE_Y)
|
||||
@@ -65,8 +45,287 @@ uint16_t Lcd_GetPixel(uint16_t x, uint16_t y)
|
||||
/* 一个字节一个像素点 */
|
||||
return g_tCVsr.pwbyLCDMemory[y * LCD_LINE_SIZE + x];
|
||||
}
|
||||
/******************************************************************************
|
||||
* 函数名: Lcd_FillRect
|
||||
* 功能: 在显存中用指定颜色填充一个矩形区域
|
||||
* 参数: lx - 左上角 X 坐标 (left x)
|
||||
* ty - 左上角 Y 坐标 (top y)
|
||||
* rx - 右下角 X 坐标 (right x)
|
||||
* by - 右下角 Y 坐标 (bottom y)
|
||||
* color- 填充颜色
|
||||
* 返回: 无
|
||||
* 说明: 对 [left_x, right_x] × [top_y, bottom_y] 区域内的每个像素逐点调用 Lcd_SetPixel 进行填充,
|
||||
* 仅操作显存,不负责刷屏输出,由上层根据需要统一刷新。
|
||||
*****************************************************************************/
|
||||
void Lcd_FillRect(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y, uint32_t color)
|
||||
{
|
||||
for(uint16_t y = top_y; y <= bottom_y; y++ )
|
||||
{
|
||||
for(uint16_t x = left_x; x <= right_x; x++ )
|
||||
{
|
||||
Lcd_SetPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
/******************************************************************************
|
||||
* 宏名: M_GuiSwap
|
||||
* 功能: 交换两个整型变量的值(按位异或方式)
|
||||
* 说明: 要求 a、b 为同一类型的可写左值,且 a、b 不能是同一地址
|
||||
*****************************************************************************/
|
||||
#define M_GuiSwap(a, b) { a^=b; b^=a; a^=b; }
|
||||
|
||||
/******************************************************************************
|
||||
* 函数名: Lcd_LineH
|
||||
* 功能: 在显存中绘制一条水平实线(可指定线宽)
|
||||
* 参数: wXStart - 线段起始 X 坐标(左端)
|
||||
* wXEnd - 线段结束 X 坐标(右端,若小于起始则自动交换)
|
||||
* wYStart - 线段上边缘 Y 坐标
|
||||
* wWidth - 线宽(沿 Y 方向的像素高度)
|
||||
* 返回: 无
|
||||
* 说明: 1. 若 wXEnd < wXStart,会先交换,保证从左向右绘制
|
||||
* 2. 实际绘制区域为 [wXStart, wXEnd) × [wYStart, wYStart + wWidth)
|
||||
* 3. 颜色使用全局当前字体颜色 g_tCVsr.wFontColor
|
||||
*****************************************************************************/
|
||||
void Lcd_LineH(uint16_t wXStart, uint16_t wXEnd, uint16_t wYStart, uint16_t wWidth, uint16_t color)
|
||||
{
|
||||
uint16_t wYEnd = wYStart + wWidth; /* 计算水平线在 Y 方向的结束位置 = 起始 Y + 线宽 */
|
||||
|
||||
if( wXEnd < wXStart ) /* 若结束 X 小于起始 X,说明调用参数顺序反了,需要交换 */
|
||||
{
|
||||
M_GuiSwap(wXEnd, wXStart); /* 使用异或交换宏,将 wXStart 与 wXEnd 的值互换 */
|
||||
}
|
||||
for(uint16_t x = wXStart; x < wXEnd; x++ ) /* 从左到右,遍历线段覆盖的每一列 X 坐标 */
|
||||
{
|
||||
for(uint16_t y = wYStart; y < wYEnd; y++ ) /* 在当前 X 列内,从上到下按线宽遍历每个像素行 */
|
||||
{
|
||||
Lcd_SetPixel(x, y, color); /* 将当前像素点设置为当前字体颜色,实现实心线绘制 */
|
||||
}
|
||||
}
|
||||
}
|
||||
/******************************************************************************
|
||||
* 函数名: Lcd_LineV
|
||||
* 功能: 在显存中绘制一条垂直实线(可指定线宽)
|
||||
* 参数: wYStart - 线段起始 Y 坐标(上端)
|
||||
* wYEnd - 线段结束 Y 坐标(下端,若小于起始则自动交换)
|
||||
* wXStart - 线段左边缘 X 坐标
|
||||
* wWidth - 线宽(沿 X 方向的像素宽度)
|
||||
* 返回: 无
|
||||
* 说明: 1. 若 wYEnd < wYStart,会先交换,保证从上向下绘制
|
||||
* 2. 实际绘制区域为 [wXStart, wXStart + wWidth) × [wYStart, wYEnd)
|
||||
* 3. 颜色使用全局当前字体颜色 g_tCVsr.wFontColor
|
||||
*****************************************************************************/
|
||||
void Lcd_LineV(uint16_t wYStart, uint16_t wYEnd, uint16_t wXStart, uint16_t wWidth, uint16_t color)
|
||||
{
|
||||
uint16_t wXEnd = wXStart + wWidth;
|
||||
|
||||
if( wXEnd < wXStart ) /* 若结束 X 小于起始 X,说明调用参数顺序反了,需要交换 */
|
||||
{
|
||||
M_GuiSwap(wYEnd, wYStart); /* 使用异或交换宏,将 wYStart 与 wYEnd 的值互换 */
|
||||
}
|
||||
|
||||
for(uint16_t x = wXStart; x < wXEnd; x++) /* 从左到右,遍历线段覆盖的每一列 X 坐标 */
|
||||
{
|
||||
for(uint16_t y = wYStart; y < wYEnd; y++) /* 从上到下,遍历线段覆盖的每一行 Y 坐标 */
|
||||
{
|
||||
Lcd_SetPixel(x, y, color); /* 将当前像素点设置为当前字体颜色,实现实心线绘制 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* 函数名: Lcd_Line
|
||||
* 功能: 在显存中绘制一条任意斜率的直线,并支持指定线宽
|
||||
* 参数: wXstart - 起点 X 坐标
|
||||
* wYstart - 起点 Y 坐标
|
||||
* wXend - 终点 X 坐标
|
||||
* wYend - 终点 Y 坐标
|
||||
* wWidth - 线宽(像素),以线段中轴为中心向两侧扩展
|
||||
* 返回: 无
|
||||
* 说明: 1. 对水平线/垂直线分别调用 Lcd_LineH / Lcd_LineV 进行优化绘制
|
||||
* 2. 其它情况使用类 Bresenham 算法,从两端向中间对称绘制,并按线宽加粗
|
||||
* 3. 颜色使用全局当前字体颜色 g_tCVsr.wFontColor
|
||||
*****************************************************************************/
|
||||
void Lcd_Line(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend, uint16_t wWidth, uint16_t color)
|
||||
{
|
||||
uint16_t wDX; /* X 方向差值(水平偏移量的绝对值) */
|
||||
uint16_t wDY; /* Y 方向差值(垂直偏移量的绝对值) */
|
||||
uint16_t wSignY; /* 纵向增量符号(+1 或 -1),决定向上/向下绘制 */
|
||||
uint16_t wSignX; /* 横向增量符号(+1 或 -1),决定向左/向右绘制 */
|
||||
uint16_t wDecision; /* 误差累积量,类似 Bresenham 算法中的判别值 */
|
||||
uint16_t wCurx, wCury, wNextx, wNexty, wPY, wPX; /* 当前端点、对称端点以及循环中使用的临时坐标 */
|
||||
|
||||
/* 若起点和终点 Y 相同,说明是水平直线,直接调用水平画线函数即可 */
|
||||
if( wYstart == wYend )
|
||||
{
|
||||
Lcd_LineH(wXstart, wXend, wYstart, wWidth, color);
|
||||
return;
|
||||
}
|
||||
/* 若起点和终点 X 相同,说明是垂直直线,直接调用垂直画线函数即可 */
|
||||
if( wXstart == wXend )
|
||||
{
|
||||
Lcd_LineV(wYstart, wYend, wXstart, wWidth, color);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 计算水平和垂直方向的距离,后续根据 wDX / wDY 决定主增量方向 */
|
||||
wDX = abs(wXstart - wXend);
|
||||
wDY = abs(wYstart - wYend);
|
||||
/* 为了统一从“左到右 / 上到下”的绘制方向,对某些象限的线段进行起终点交换 */
|
||||
if (((wDX >= wDY && (wXstart > wXend)) || /* 以 X 为主增量,且起点在右侧,则交换 */
|
||||
((wDY > wDX) && (wYstart > wYend)))) /* 以 Y 为主增量,且起点在下侧,则交换 */
|
||||
{
|
||||
M_GuiSwap(wXend, wXstart); /* 交换 X 坐标,使起点在左/上 */
|
||||
M_GuiSwap(wYend, wYstart); /* 交换 Y 坐标,对应调整终点 */
|
||||
}
|
||||
/* 计算每一步在 Y 方向上的符号:向下(+1) 或向上(-1) */
|
||||
wSignY = (wYend - wYstart) / wDY;
|
||||
/* 计算每一步在 X 方向上的符号:向右(+1) 或向左(-1) */
|
||||
wSignX = (wXend - wXstart) / wDX;
|
||||
|
||||
/* 情况一:X 方向偏移大于等于 Y 方向(线更“横向”) */
|
||||
if (wDX >= wDY)
|
||||
{
|
||||
wCurx = wXstart; /* 当前点 X 从起点开始 */
|
||||
wCury = wYstart; /* 当前点 Y 从起点开始 */
|
||||
wNextx = wXend; /* 对称点 X 从终点开始(用于加粗两端) */
|
||||
wNexty = wYend; /* 对称点 Y 从终点开始 */
|
||||
wDecision = (wDX >> 1); /* 初始化判别值为一半的 wDX */
|
||||
for (; wCurx <= wNextx; ) /* 从两端向中间画,直到两个端点相遇或交叉 */
|
||||
{
|
||||
/* 累积的误差超过一条“格子宽度”时,说明需要在 Y 方向跨一格 */
|
||||
if (wDecision >= wDX)
|
||||
{
|
||||
wDecision -= wDX; /* 误差回退一个 wDX,保持在合理范围内 */
|
||||
wCury += wSignY; /* 当前端点 Y 沿着符号方向移动一格 */
|
||||
wNexty -= wSignY; /* 对称端点 Y 反向移动一格,保持对称 */
|
||||
}
|
||||
/* 以当前端点 (wCurx, wCury) 为中心,按线宽在 Y 方向“扩粗”填充像素 */
|
||||
for (wPY = wCury - wWidth / 2; wPY <= wCury + wWidth / 2; wPY++)
|
||||
{
|
||||
Lcd_SetPixel(wCurx, wPY, color);
|
||||
}
|
||||
|
||||
/* 以对称端点 (wNextx, wNexty) 为中心,同样按线宽在 Y 方向填充,实现两端对称绘制 */
|
||||
for (wPY = wNexty - wWidth / 2; wPY <= wNexty + wWidth / 2; wPY++)
|
||||
{
|
||||
Lcd_SetPixel(wNextx, wPY, color);
|
||||
}
|
||||
wCurx++; /* 当前端点 X 向右移动一格 */
|
||||
wNextx--; /* 对称端点 X 向左移动一格 */
|
||||
wDecision += wDY; /* 增加误差值,下一轮判断是否需要在 Y 方向跨格 */
|
||||
}
|
||||
}
|
||||
/* 情况二:Y 方向偏移大于 X 方向(线更“竖向”) */
|
||||
else
|
||||
{
|
||||
wCurx = wXstart; /* 当前点 X 从起点开始 */
|
||||
wCury = wYstart; /* 当前点 Y 从起点开始 */
|
||||
wNextx = wXend; /* 对称点 X 从终点开始 */
|
||||
wNexty = wYend; /* 对称点 Y 从终点开始 */
|
||||
wDecision = (wDY >> 1); /* 初始化判别值为一半的 wDY */
|
||||
for (; wCury <= wNexty; ) /* 从两端向中间画,直到在 Y 方向相遇 */
|
||||
{
|
||||
|
||||
/* 累积误差超过一条“格子高度”时,说明需要在 X 方向跨一格 */
|
||||
if (wDecision >= wDY)
|
||||
{
|
||||
wDecision -= wDY; /* 回退一个 wDY,避免误差过大 */
|
||||
wCurx += wSignX; /* 当前端点 X 沿符号方向移动一格 */
|
||||
wNextx -= wSignX; /* 对称端点 X 反向移动一格 */
|
||||
}
|
||||
/* 以当前端点 (wCurx, wCury) 为中心,在 X 方向按线宽“扩粗”填充像素 */
|
||||
for (wPX = wCurx - wWidth / 2; wPX <= wCurx + wWidth / 2; wPX++)
|
||||
{
|
||||
Lcd_SetPixel(wPX, wCury, color);
|
||||
}
|
||||
|
||||
/* 以对称端点 (wNextx, wNexty) 为中心,同样在 X 方向扩粗填充,实现两端对称绘制 */
|
||||
for (wPX = wNextx - wWidth / 2; wPX <= wNextx + wWidth / 2; wPX++)
|
||||
{
|
||||
Lcd_SetPixel(wPX, wNexty, color);
|
||||
}
|
||||
wCury++; /* 当前端点 Y 向下移动一格 */
|
||||
wNexty--; /* 对称端点 Y 向上移动一格 */
|
||||
wDecision += wDX; /* 增加误差值,下一轮判断是否需要在 X 方向跨格 */
|
||||
}
|
||||
}
|
||||
}
|
||||
/******************************************************************************
|
||||
* 函数名: Lcd_MeiTouPic
|
||||
* 功能: 在指定 Y 位置绘制一条带左右“眉头”装饰的水平线(中间直线 + 两端斜线)
|
||||
* 参数: wYStart - 中间水平线的 Y 坐标
|
||||
* wWidth - 线宽(像素),传递给 Lcd_LineH / Lcd_Line
|
||||
* 返回: 无
|
||||
* 说明: 1. 中间部分为 X=16..144 的水平粗线
|
||||
* 2. 左端在 (16, wYStart) 位置向左上方连一条斜线到 (8, wYStart-8)
|
||||
* 3. 右端在 (144, wYStart) 位置向右上方连一条斜线到 (152, wYStart-8)
|
||||
*****************************************************************************/
|
||||
void Lcd_MeiTouPic(uint16_t wYStart, uint16_t wWidth)
|
||||
{
|
||||
Lcd_LineH(16, 144, wYStart, wWidth, g_tCVsr.wFontColor); /* 中间水平粗线段 */
|
||||
Lcd_Line(8, wYStart - 8, 16, wYStart, wWidth, g_tCVsr.wFontColor);/* 左端向上的斜线“眉头” */
|
||||
Lcd_Line(144, wYStart, 152, wYStart - 8, wWidth, g_tCVsr.wFontColor);/* 右端向上的斜线“眉头” */
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Lcd_ButtonPush(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y)
|
||||
{
|
||||
Lcd_LineH(left_x, right_x,top_y, 1, g_tCVsr.wFontColor);
|
||||
Lcd_LineV(top_y, bottom_y,left_x, 1, g_tCVsr.wFontColor);
|
||||
Lcd_LineH(left_x, right_x,bottom_y, 1, g_tCVsr.wFontColor);
|
||||
Lcd_LineV(top_y, bottom_y,right_x, 1, g_tCVsr.wFontColor);
|
||||
}
|
||||
/******************************************************************************
|
||||
* 宏名: M_Max / M_Min
|
||||
* 功能: 返回两个数中的较大值 / 较小值(简单三目运算宏)
|
||||
*****************************************************************************/
|
||||
#define M_Max( a, b ) ( ((a) > (b)) ? (a) : (b) )
|
||||
#define M_Min( a, b ) ( ((a) < (b)) ? (a) : (b) )
|
||||
|
||||
/******************************************************************************
|
||||
* 函数名: Lcd_Invert
|
||||
* 功能: 对指定矩形区域内的像素进行“反相”操作(黑变白、白变黑)
|
||||
* 参数: wXstart - 矩形左上角 X 坐标
|
||||
* wYstart - 矩形左上角 Y 坐标
|
||||
* wXend - 矩形右下角 X 坐标
|
||||
* wYend - 矩形右下角 Y 坐标
|
||||
* 返回: 无
|
||||
* 说明: 1. 首先进行边界检查,防止坐标越界访问显存
|
||||
* 2. 使用 M_Min/M_Max 规范化矩形对角坐标为 (xMin,yMin)-(xMax,yMax)
|
||||
* 3. 遍历矩形区域,每个像素值按位取反写回,实现反白/反显效果
|
||||
*****************************************************************************/
|
||||
void Lcd_Invert(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend)
|
||||
{
|
||||
uint16_t xMin, xMax, yMin, yMax; /* 归一化后的矩形边界坐标 */
|
||||
uint8_t wColor; /* 当前像素原始色值(8bit 单色) */
|
||||
|
||||
/* 边界检查:若任意一端 X 超出屏幕宽度,则直接返回,不做处理 */
|
||||
if ((wXstart > LCD_SIZE_X) || (wXend > LCD_SIZE_X))
|
||||
return;
|
||||
/* 边界检查:若任意一端 Y 超出屏幕高度,则直接返回 */
|
||||
if ((wYstart > LCD_SIZE_Y) || (wYend > LCD_SIZE_Y))
|
||||
return;
|
||||
|
||||
/* 规范化矩形:xMin/xMax 为左右边界,yMin/yMax 为上下边界(不关心调用时 start/end 的大小关系) */
|
||||
xMin = M_Min(wXstart, wXend);
|
||||
yMin = M_Min(wYstart, wYend);
|
||||
xMax = M_Max(wXstart, wXend);
|
||||
yMax = M_Max(wYstart, wYend);
|
||||
|
||||
/* 双重循环遍历矩形区域内的每个像素点(不包含 xMax/yMax 边界本身) */
|
||||
for (uint16_t y = yMin; y < yMax; y++)
|
||||
{
|
||||
for (uint16_t x = xMin; x < xMax; x++)
|
||||
{
|
||||
/* 从显存中读取当前像素值,逐位取反后写回,实现黑白反转/反显效果 */
|
||||
wColor = g_tCVsr.pwbyLCDMemory[y * LCD_SIZE_X + x];
|
||||
g_tCVsr.pwbyLCDMemory[y * LCD_SIZE_X + x] = (uint8_t)~wColor;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
//==============================================================================
|
||||
// 功能说明 : 在指定屏幕坐标处显示一个 ASCII 字符
|
||||
// 设计说明 : 从 ASCII 字库中取出点阵, 按当前显示属性(正显/反显、旋转 90° 与否)
|
||||
|
||||
@@ -50,6 +50,13 @@ typedef struct { // 显示属性数据结构
|
||||
void Lcd_Init(void);
|
||||
void Lcd_SetPixel(uint16_t x, uint16_t y, uint16_t color);
|
||||
uint16_t Lcd_GetPixel(uint16_t x, uint16_t y);
|
||||
void Lcd_FillRect(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y, uint32_t color);
|
||||
void Lcd_MeiTouPic(uint16_t wYStart, uint16_t wWidth);
|
||||
void Lcd_ButtonPush(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y);
|
||||
void Lcd_Invert(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend);
|
||||
void Lcd_LineH(uint16_t wXStart, uint16_t wXEnd, uint16_t wYStart, uint16_t wWidth, uint16_t color);
|
||||
void Lcd_LineV(uint16_t wYStart, uint16_t wYEnd, uint16_t wXStart, uint16_t wWidth, uint16_t color);
|
||||
|
||||
int8_t Lcd_ShowStr(uint16_t x, uint16_t y, uint8_t *pcString);
|
||||
int8_t Lcd_ShowTest(uint16_t x, uint16_t y, uint8_t *pcString);
|
||||
#endif
|
||||
780
src/Drv/menu.c
780
src/Drv/menu.c
@@ -1,8 +1,9 @@
|
||||
#include "menu.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "lcd.h"
|
||||
#include "key.h"
|
||||
#include "display.h"
|
||||
|
||||
/* 简单的静态菜单树:
|
||||
*
|
||||
@@ -25,8 +26,8 @@
|
||||
uint16_t wPara; // 菜单执行函数参数;
|
||||
FUNCPTR pfnWinProc; // 界面执行函数指针;
|
||||
|
||||
uint16_t wPos; // 当前菜单位置,相对于本级菜单。
|
||||
uint16_t wNum; // 下级菜单项数
|
||||
uint16_t wPos; /* 当前菜单在本级中的位置 */
|
||||
uint16_t wNum; /* 下级菜单的总项数 */
|
||||
uint16_t wSPosX; // 下级菜单起始坐标
|
||||
uint16_t wSPosY; // 下级菜单起始坐标
|
||||
uint16_t wEPosX; // 下级菜单对角坐标
|
||||
@@ -70,41 +71,731 @@ typedef struct
|
||||
unsigned bRes:1;
|
||||
}tagDspCtrl,*tagPDspCtrl;
|
||||
|
||||
typedef struct
|
||||
{ // 菜单控制结构
|
||||
uint8_t by0LevelNum; // 0级菜单项数
|
||||
uint8_t byLeftMove; // 菜单左移长度
|
||||
uint8_t bySecPage; // 第二页菜单标志
|
||||
FUNCPTR fnExecute; // 执行函数指针
|
||||
|
||||
tagPMenuItem ptHead; // 菜单链表头指针
|
||||
tagPMenuItem ptCurrent; // 菜单链表当前指针
|
||||
tagPMenuItem ptRoute[4]; // 0-3级菜单的指针路径
|
||||
tagPMenuItem ptCurBak; // 菜单链表指针备份
|
||||
tagPMenuItem pt0Level; // 0级菜单链表指针备份
|
||||
|
||||
tagPMenuItem ptSeeSetSoft; // 查看定值菜单结点
|
||||
tagPMenuItem ptSetValSoft; // 整定定值菜单结点
|
||||
tagPMenuItem ptSeeSetFigure; // 查看定值菜单结点
|
||||
tagPMenuItem ptSetValFigure; // 整定定值菜单结点
|
||||
tagPMenuItem ptSeeSetPara; // 查看定值菜单结点
|
||||
tagPMenuItem ptSetValPara; // 整定定值菜单结点
|
||||
}tagMenuCtrl,*tagPMenuCtrl;
|
||||
|
||||
tagDspCtrl g_tDspCtrl; // 显示控制全局结构
|
||||
tagMenuCtrl g_tMenuCtrl; // 菜单全局控制结构
|
||||
|
||||
|
||||
static MenuItem g_menuRun;
|
||||
static MenuItem g_menuSet;
|
||||
static MenuItem g_menuSee;
|
||||
static MenuItem g_menuSet1;
|
||||
static MenuItem g_menuSet2;
|
||||
tagMenuItem g_tMenuItem[300]; // 菜单链表空间定义
|
||||
|
||||
void Menu_Init()
|
||||
void Menu_0LevelNumCal(void)
|
||||
{
|
||||
Lcd_Init();
|
||||
tagPMenuModel ptModelIndex; /* 当前遍历到的菜单表项指针 */
|
||||
uint16_t wLoop; /* 表项下标,0 .. MENU_MAX_ITEM-1 */
|
||||
uint8_t by0LevelNum; /* 0 级菜单项计数 */
|
||||
|
||||
/* 初始化显示控制结构 */
|
||||
memset(&g_tDspCtrl, 0, sizeof(g_tDspCtrl));
|
||||
by0LevelNum = 0;
|
||||
for (wLoop = 0; wLoop < MENU_MAX_ITEM; wLoop++) {
|
||||
ptModelIndex = (tagPMenuModel)&g_tMenuModelTab[wLoop];
|
||||
if (0 == ptModelIndex->byClass) {
|
||||
by0LevelNum++;
|
||||
}
|
||||
}
|
||||
g_tMenuCtrl.by0LevelNum = by0LevelNum;
|
||||
}
|
||||
/******************************************************************************
|
||||
* 函数名: Menu_Main_Creat
|
||||
* 功能: 根据静态菜单表 g_tMenuModelTab 在 g_tMenuItem 中建立可遍历的菜单树
|
||||
* (双向链表 + 父子关系,同级首尾成环)
|
||||
* 参数: 无
|
||||
* 返回: 无
|
||||
* 说明: 1. 表内按深度优先顺序排列:先 0 级,再其下 1 级,再 2 级… 同级按表顺序
|
||||
* 2. 通过 byClass(0/1/2/3)区分层级
|
||||
* 3. 用 ptFirst[级]、ptLast[级] 记录每级首尾,便于链接与最后首尾成环
|
||||
*****************************************************************************/
|
||||
void Menu_Main_Creat(void)
|
||||
{
|
||||
tagPMenuItem ptFirst[4]; /* 各级(0..3)当前“首”结点,用于成环与回退时链接 */
|
||||
tagPMenuItem ptLast[4]; /* 各级当前“尾”结点 */
|
||||
tagPMenuItem ptCurrent; /* 表顺序上的“当前”结点(刚处理完的) */
|
||||
tagPMenuItem ptNextNode; /* 本轮要接入的“下一”结点 */
|
||||
|
||||
tagPMenuModel ptModelIndex;
|
||||
uint16_t wLoop1, wLoop2;
|
||||
uint8_t byCurClass; /* 当前结点层级 */
|
||||
uint8_t byNextClass; /* 下一结点层级 */
|
||||
|
||||
/* ---------- 第 0 项:作为 0 级首项,初始化并记为 0 级首/尾 ---------- */
|
||||
ptFirst[0] = &g_tMenuItem[0];
|
||||
ptLast[0] = ptFirst[0];
|
||||
|
||||
ptCurrent = &g_tMenuItem[0];
|
||||
ptModelIndex = (tagPMenuModel)&g_tMenuModelTab[0];
|
||||
ptCurrent->wPos = 0;
|
||||
ptCurrent->wNum = 0;
|
||||
ptCurrent->wSPosX = 0;
|
||||
ptCurrent->wSPosY = 0;
|
||||
ptCurrent->wEPosX = 0;
|
||||
ptCurrent->wEPosY = 0;
|
||||
|
||||
ptCurrent->ptHigher = NULL;
|
||||
ptCurrent->ptLower = NULL;
|
||||
ptCurrent->ptBefore = NULL;
|
||||
ptCurrent->ptBehind = NULL;
|
||||
|
||||
ptCurrent->byClass = ptModelIndex->byClass;
|
||||
ptCurrent->byAttrib = ptModelIndex->byAttrib;
|
||||
ptCurrent->wPassword = ptModelIndex->wPassword;
|
||||
ptCurrent->wPara = ptModelIndex->wPara;
|
||||
ptCurrent->pfnWinProc = ptModelIndex->pfnWinProc;
|
||||
memcpy(ptCurrent->byName, ptModelIndex->byName, 50);
|
||||
memcpy(ptCurrent->byTip, ptModelIndex->byTip, 50);
|
||||
#ifdef DEBUG
|
||||
printf("Menu_Main_Creat:%d级菜单:%s\n", ptCurrent->byClass, ptCurrent->byName);
|
||||
#endif
|
||||
|
||||
/* ---------- 从第 1 项起,按表顺序逐个接入树 ---------- */
|
||||
for (wLoop1 = 1; wLoop1 < MENU_MAX_ITEM; wLoop1++)
|
||||
{
|
||||
/* 获取下一项菜单项 */
|
||||
ptNextNode = &g_tMenuItem[wLoop1];
|
||||
/* 获取下一项菜单项的模型索引 */
|
||||
ptModelIndex = (tagPMenuModel)&g_tMenuModelTab[wLoop1];
|
||||
|
||||
/* 初始化下一项菜单项的各项属性 */
|
||||
ptNextNode->wPos = 0;
|
||||
ptNextNode->wNum = 0;
|
||||
ptNextNode->wSPosX = 0;
|
||||
ptNextNode->wSPosY = 0;
|
||||
ptNextNode->wEPosX = 0;
|
||||
ptNextNode->wEPosY = 0;
|
||||
ptNextNode->ptHigher = NULL;
|
||||
ptNextNode->ptLower = NULL;
|
||||
ptNextNode->ptBefore = NULL;
|
||||
ptNextNode->ptBehind = NULL;
|
||||
|
||||
ptNextNode->byClass = ptModelIndex->byClass;
|
||||
ptNextNode->byAttrib = ptModelIndex->byAttrib;
|
||||
ptNextNode->wPassword = ptModelIndex->wPassword;
|
||||
ptNextNode->wPara = ptModelIndex->wPara;
|
||||
ptNextNode->pfnWinProc = ptModelIndex->pfnWinProc;
|
||||
memcpy(ptNextNode->byName, ptModelIndex->byName, 50);
|
||||
memcpy(ptNextNode->byTip, ptModelIndex->byTip, 50);
|
||||
#ifdef DEBUG
|
||||
printf("Menu_Main_Creat:%d级菜单:%s\n", ptNextNode->byClass, ptNextNode->byName);
|
||||
#endif
|
||||
|
||||
byCurClass = ptCurrent->byClass;
|
||||
byNextClass = ptNextNode->byClass;
|
||||
|
||||
/* 根据当前菜单项和下一菜单项的层级关系,更新菜单链表 */
|
||||
if (byCurClass < byNextClass)
|
||||
{
|
||||
ptFirst[byNextClass] = ptNextNode;
|
||||
ptLast[byNextClass] = ptFirst[byNextClass];
|
||||
|
||||
ptCurrent->ptLower = ptNextNode;
|
||||
ptNextNode->ptHigher = ptCurrent;
|
||||
}
|
||||
else if (byCurClass == byNextClass)
|
||||
{
|
||||
ptLast[byNextClass] = ptNextNode;
|
||||
ptCurrent->ptBehind = ptNextNode;
|
||||
|
||||
ptNextNode->ptBefore = ptCurrent;
|
||||
ptNextNode->ptHigher = ptCurrent->ptHigher;
|
||||
}
|
||||
else if (byCurClass > byNextClass) /* 下一项更浅 */
|
||||
{
|
||||
/* 下一项更浅:下一项接到 byNextClass 级当前尾的后面*/
|
||||
ptLast[byNextClass]->ptBehind = ptNextNode;
|
||||
ptNextNode->ptBefore = ptLast[byNextClass];
|
||||
ptNextNode->ptHigher = ptLast[byNextClass]->ptHigher;
|
||||
/* 更新 byNextClass 级当前尾 */
|
||||
ptLast[byNextClass] = ptNextNode;
|
||||
|
||||
/* 当层级回退时,更新各级首尾成环 */
|
||||
for (wLoop2 = byCurClass; wLoop2 > byNextClass; wLoop2--)
|
||||
{
|
||||
ptLast[wLoop2]->ptBehind = ptFirst[wLoop2]; /* 尾的下一个是首 */
|
||||
ptFirst[wLoop2]->ptBefore = ptLast[wLoop2]; /* 首的前一个是尾 */
|
||||
}
|
||||
}
|
||||
ptCurrent = ptNextNode;
|
||||
}
|
||||
|
||||
/* ---------- 从当前所在级到 0 级,各级首尾成环(循环链表) ---------- */
|
||||
byCurClass = ptCurrent->byClass;
|
||||
for (wLoop1 = 0; wLoop1 <= byCurClass; wLoop1++)
|
||||
{
|
||||
ptLast[wLoop1]->ptBehind = ptFirst[wLoop1];
|
||||
ptFirst[wLoop1]->ptBefore = ptLast[wLoop1];
|
||||
}
|
||||
}
|
||||
/******************************************************************************
|
||||
* 函数名: Menu_charLenCal
|
||||
* 功能: 1.计算指定级菜单下所有子项的最大显示长度
|
||||
* 2.为有下级的菜单名追加右箭头符号 '\x10'
|
||||
* 3.更新 wPos 字段,表示当前菜单在本级中的位置
|
||||
* 4. wNum 指定级菜单的下级级菜数量
|
||||
* 参数: bylevel - 当前层级(调用方一般为 0,表示从 0 级菜单的子级开始)
|
||||
* byMenuNum - 各级菜单项数量数组 [0..3],本函数会更新 bylevel+1 级计数
|
||||
* ptFirst - 各级首项指针数组,本函数会写入 ptFirst[bylevel+1]
|
||||
* ptIndex - 各级当前项指针数组,本函数会更新 ptIndex[bylevel] 及子级
|
||||
* 返回: 该级所有菜单项中的最大字符长度(含可能追加的右箭头 '\x10')
|
||||
* 说明: 1. 从 ptIndex[bylevel] 的子级(ptLower)开始,沿 ptBehind 遍历同级所有项
|
||||
* 2. 若有下级菜单且名称末尾无右箭头,则在 byName 末尾追加 '\x10' 并补 '\0'
|
||||
* 3. 同级菜单首尾成环,遍历回 ptFirst 时结束
|
||||
* 4. 供 Menu_PositionCal 用于计算下拉菜单矩形框宽度(byMaxLen * 字宽)
|
||||
*****************************************************************************/
|
||||
uint8_t Menu_charLenCal(uint8_t bylevel, uint8_t *byMenuNum, tagPMenuItem *ptFirst, tagPMenuItem *ptIndex)
|
||||
{
|
||||
uint16_t wLoop;
|
||||
uint8_t byStrLen; /* 当前菜单项名称长度(含可能追加的右箭头) */
|
||||
uint8_t byMaxLen; /* 同级菜单中的最大显示长度 */
|
||||
|
||||
/* 记录子级首项,供后续遍历与布局使用 */
|
||||
ptFirst[bylevel + 1] = ptIndex[bylevel]->ptLower;
|
||||
|
||||
bylevel = bylevel + 1;
|
||||
ptIndex[bylevel] = ptFirst[bylevel];
|
||||
|
||||
byMenuNum[bylevel] = 1;
|
||||
ptIndex[bylevel]->wPos = 1;
|
||||
|
||||
byMaxLen = 0;
|
||||
for (wLoop = 0; wLoop < MENU_MAX_ITEM; wLoop++)
|
||||
{
|
||||
/* 计算当前菜单项名称长度 */
|
||||
byStrLen = (uint8_t)strlen((const char *)ptIndex[bylevel]->byName);
|
||||
|
||||
/* 若有下级且名称末尾尚无右箭头,则追加 '\x10' 并更新长度 */
|
||||
if ((ptIndex[bylevel]->ptLower != NULL) && ('\x10' != ptIndex[bylevel]->byName[byStrLen - 1]))
|
||||
{
|
||||
ptIndex[bylevel]->byName[byStrLen] = '\x10';
|
||||
ptIndex[bylevel]->byName[byStrLen + 1] = '\0';
|
||||
byStrLen += 1;
|
||||
}
|
||||
|
||||
if (byMaxLen < byStrLen)
|
||||
{
|
||||
byMaxLen = byStrLen;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
printf("计算%d级菜单位置:%s,长度%d\n", bylevel, ptIndex[bylevel]->byName, byMaxLen);
|
||||
#endif
|
||||
|
||||
/* 移至同级下一项(ptBehind),同级首尾成环 */
|
||||
ptIndex[bylevel] = ptIndex[bylevel]->ptBehind;
|
||||
if (ptIndex[bylevel] == ptFirst[bylevel]) /* 同级首尾成环,遍历回 ptFirst 时说明遍历完了当前级的所有菜单,结束 */
|
||||
{
|
||||
break;
|
||||
}
|
||||
byMenuNum[bylevel]++; /* 同级菜单项数量+1 */
|
||||
ptIndex[bylevel]->wPos = byMenuNum[bylevel]; /* 同级菜单项序号 + 1 */
|
||||
}
|
||||
|
||||
/* 将子级菜单总数回填到父项 wNum */
|
||||
ptIndex[bylevel - 1]->wNum = byMenuNum[bylevel];
|
||||
return byMaxLen;
|
||||
}
|
||||
void Menu_Sub2PosCal(uint8_t bylevel, tagPMenuItem *ptFirst, tagPMenuItem *ptIndex)
|
||||
{
|
||||
uint16_t wLoop;
|
||||
uint16_t wSPosX;
|
||||
uint16_t wSPosY;
|
||||
uint16_t wEPosX;
|
||||
uint16_t wEPosY;
|
||||
uint16_t wTemp;
|
||||
uint8_t byMaxLen;
|
||||
uint8_t byMaxNum;
|
||||
uint8_t byMenuPos;
|
||||
uint8_t byItemNum;
|
||||
uint8_t byMenuNum[4];
|
||||
|
||||
byMaxNum = (MENU_YMAX - MENU_YMIN - 6) / LINE_HEIGHT; // 页内纵向可显示菜单数
|
||||
|
||||
ptIndex[bylevel] = ptFirst[bylevel];
|
||||
for (wLoop = 0; wLoop < MENU_MAX_ITEM; wLoop++)
|
||||
{
|
||||
if ((ptIndex[bylevel]->ptLower) != NULL) // 当前2级菜单有下级菜单
|
||||
{
|
||||
byMaxLen = Menu_charLenCal(bylevel, byMenuNum, ptFirst, ptIndex); // 计算3级菜单项数及字符数
|
||||
|
||||
wSPosX = ptIndex[bylevel - 1]->wEPosX; // 3级菜单矩形框横坐标起始
|
||||
ptIndex[bylevel]->wSPosX = wSPosX;
|
||||
|
||||
wEPosX =
|
||||
wSPosX + byMaxLen * MENU_WITDTH + MENU_XADD; // 3级菜单矩形框横坐标终止
|
||||
ptIndex[bylevel]->wEPosX = wEPosX;
|
||||
|
||||
byMenuPos = ptIndex[bylevel]->wPos; // 2级菜单在菜单列中的位置
|
||||
byItemNum = byMenuNum[bylevel]; // 2级菜单项数
|
||||
|
||||
if ((byItemNum > byMaxNum) && (byMenuPos >(byMaxNum - 1))) // 2级菜单项数大于一页且2级菜单当前位置不在第一页
|
||||
{
|
||||
byMenuPos = byMaxNum - (byItemNum - byMenuPos); // 第一页尾显示↓ 第二页头显示↑
|
||||
}
|
||||
|
||||
wSPosY = ptIndex[bylevel - 1]->wSPosY;
|
||||
wSPosY =
|
||||
wSPosY + (byMenuPos - 1) * LINE_HEIGHT; // 计算3级菜单框起始坐标
|
||||
|
||||
byItemNum = byMenuNum[bylevel + 1]; // 3级菜单项数
|
||||
wEPosY = wSPosY + byItemNum * LINE_HEIGHT + MENU_YADD; // 计算3级菜单框终止坐标
|
||||
|
||||
if (wEPosY < MENU_YMAX) // 3级菜单框与2级对应位置不越限
|
||||
{
|
||||
ptIndex[bylevel]->wSPosY = wSPosY; // 3级菜单矩形框纵起始坐标定位
|
||||
ptIndex[bylevel]->wEPosY = wEPosY; // 3级菜单矩形框纵终止坐标定位
|
||||
}
|
||||
else
|
||||
{
|
||||
wEPosY = wSPosY - (byItemNum - 1) * LINE_HEIGHT - MENU_YADD;
|
||||
if ((wEPosY > MENU_YMIN) && (wEPosY < MENU_YMAX)) // 菜单向上不越限
|
||||
{
|
||||
wTemp = wSPosY;
|
||||
wSPosY = wEPosY;
|
||||
wEPosY = wTemp + LINE_HEIGHT;
|
||||
}
|
||||
else // 菜单显示超过一页
|
||||
{
|
||||
wSPosY = ptIndex[0]->wSPosY; // 3级菜单框起始坐标同1级菜单
|
||||
wEPosY = wSPosY + byItemNum * LINE_HEIGHT + MENU_YADD; // 计算3级菜单框终止坐标
|
||||
|
||||
if (wEPosY > MENU_YMAX)
|
||||
{
|
||||
wEPosY = MENU_YMAX; // 菜单纵向一屏可显示
|
||||
}
|
||||
}
|
||||
ptIndex[bylevel]->wSPosY = wSPosY; // 3级菜单矩形框纵起始坐标定位
|
||||
ptIndex[bylevel]->wEPosY = wEPosY; // 3级菜单矩形框纵终止坐标定位
|
||||
}
|
||||
}
|
||||
/* 移至同级下一项 */
|
||||
ptIndex[bylevel] = ptIndex[bylevel]->ptBehind;
|
||||
/*如果移至同级下一项后,发现回到了同级菜单的第一个菜单,则说明遍历完了同级菜单的所有菜单,结束*/
|
||||
if (ptIndex[bylevel] == ptFirst[bylevel])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu_Sub1PosCal(uint8_t bylevel, tagPMenuItem *ptFirst, tagPMenuItem *ptIndex)
|
||||
{
|
||||
uint16_t wSPosX;
|
||||
uint16_t wSPosY;
|
||||
uint16_t wEPosX;
|
||||
uint16_t wEPosY;
|
||||
uint8_t byMenuNum[4];
|
||||
uint8_t byMaxLen;
|
||||
uint8_t byMaxNum;
|
||||
uint16_t byMenuPos;
|
||||
uint8_t byItemNum;
|
||||
uint16_t wTemp;
|
||||
|
||||
byMaxNum = (MENU_YMAX) / LINE_HEIGHT; // 页内纵向可显示菜单数
|
||||
|
||||
ptIndex[bylevel] = ptFirst[bylevel];
|
||||
for (uint16_t wLoop = 0; wLoop < MENU_MAX_ITEM; wLoop++)
|
||||
{
|
||||
if ((ptIndex[bylevel]->ptLower) != NULL) // 当前1级菜单有下级菜单
|
||||
{
|
||||
byMaxLen = Menu_charLenCal(bylevel, byMenuNum, ptFirst, ptIndex); // 计算2级菜单项数及字符数
|
||||
|
||||
/*X坐标计算*/
|
||||
ptIndex[bylevel]->wSPosX = ptIndex[bylevel - 1]->wEPosX; // 2级菜单矩形框横坐标起始
|
||||
ptIndex[bylevel]->wEPosX = ptIndex[bylevel]->wSPosX + byMaxLen * MENU_WITDTH + MENU_XADD; // 2级菜单矩形框横坐标终止
|
||||
|
||||
|
||||
byMenuPos = ptIndex[bylevel]->wPos; // 1级菜单在菜单列中的位置
|
||||
byItemNum = byMenuNum[bylevel]; // 1级菜单项数
|
||||
|
||||
if ((byItemNum > byMaxNum) && (byMenuPos >= byMaxNum)) // 1级菜单项数大于一页且1级菜单当前位置不在第一页
|
||||
{
|
||||
byMenuPos = byMaxNum - (byItemNum - byMenuPos); /* 第一页尾显示↓ 第二页头显示↑ */
|
||||
}
|
||||
|
||||
wSPosY = ptIndex[bylevel - 1]->wSPosY + (byMenuPos - 1) * LINE_HEIGHT; // 计算2级菜单框起始坐标
|
||||
|
||||
byItemNum = byMenuNum[bylevel + 1]; // 2级菜单项数
|
||||
wEPosY = wSPosY + byItemNum * LINE_HEIGHT + MENU_YADD; // 计算2级菜单框终止坐标
|
||||
#ifdef DEBUG
|
||||
printf("%d %d %d %d \n", bylevel, byItemNum, wSPosY, wEPosY);
|
||||
#endif
|
||||
if (wEPosY < MENU_YMAX) /* 菜单框没有超出边界 */
|
||||
{
|
||||
ptIndex[bylevel]->wSPosY = wSPosY; // 2级菜单矩形框纵起始坐标定位
|
||||
ptIndex[bylevel]->wEPosY = wEPosY; // 2级菜单矩形框纵终止坐标定位
|
||||
}
|
||||
else /* 菜单框超出边界 */
|
||||
{
|
||||
wEPosY = wSPosY - (byItemNum - 1) * LINE_HEIGHT - MENU_YADD;
|
||||
if ((wEPosY > LINE_HEIGHT) && (wEPosY < MENU_YMAX)) // 菜单向上不越限
|
||||
{
|
||||
wTemp = wSPosY;
|
||||
wSPosY = wEPosY;
|
||||
wEPosY = wTemp + LINE_HEIGHT;
|
||||
}
|
||||
else
|
||||
{
|
||||
wSPosY = ptIndex[0]->wSPosY; // 2级菜单框起始坐标同1级菜单
|
||||
wEPosY = wSPosY + byItemNum * LINE_HEIGHT + MENU_YADD; // 计算2级菜单框终止坐标
|
||||
for (uint16_t i = 1; i < (wSPosY / LINE_HEIGHT + 1); i++)
|
||||
{
|
||||
if (wEPosY > MENU_YMAX) // wEPosY = CN_YMAX; // 菜单纵向一屏可显示
|
||||
{
|
||||
wSPosY = ptIndex[0]->wSPosY - LINE_HEIGHT * i;
|
||||
if (wSPosY < LINE_HEIGHT)
|
||||
{
|
||||
wSPosY = 0;
|
||||
}
|
||||
wEPosY = wSPosY + byItemNum * LINE_HEIGHT + MENU_YADD; // 计算2级菜单框终止坐标
|
||||
#ifdef DEBUG
|
||||
printf("Menu_Sub1PosCal: %d %d %d %d\n", bylevel, byItemNum, wSPosY, wEPosY);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
break; /* 菜单向上不越限,结束 */
|
||||
}
|
||||
}
|
||||
}
|
||||
ptIndex[bylevel]->wSPosY = wSPosY; /* 2级菜单矩形框纵起始坐标定位 */
|
||||
ptIndex[bylevel]->wEPosY = wEPosY; /* 2级菜单矩形框纵终止坐标定位 */
|
||||
}
|
||||
Menu_Sub2PosCal(2, ptFirst, ptIndex); // 3级菜单框坐标计算
|
||||
}
|
||||
/* 移至同级下一项 */
|
||||
ptIndex[bylevel] = ptIndex[bylevel]->ptBehind;
|
||||
/*如果移至同级下一项后,发现回到了同级菜单的第一个菜单,则说明遍历完了同级菜单的所有菜单,结束*/
|
||||
if (ptIndex[bylevel] == ptFirst[bylevel])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/******************************************************************************
|
||||
* 函数名: Menu_PositionCal
|
||||
* 功能: 为菜单树中所有有下级的 0 级菜单项计算并填充其下拉框的显示坐标
|
||||
* (wSPosX/Y、wEPosX/Y),供后续绘制矩形框与文字使用
|
||||
* 参数: ptMenuHead - 0 级菜单链表头指针(通常为 g_tMenuCtrl.ptHead)
|
||||
* 返回: 无
|
||||
* 说明: 1. 0 级菜单在屏幕顶部横向均分,有下级的项在其下方弹出 1 级下拉框
|
||||
* 2. 下拉框左上角 (wSPosX, wSPosY) 从屏幕底部向上推算,避免超出屏幕
|
||||
* 3. 下拉框宽度由 Menu_charLenCal 返回的 byMaxLen 与字宽 CN_WITDTH 决定
|
||||
* 4. 若下拉框超出屏幕顶部(wEPosY < CN_YMAX),则 wEPosY 取屏高,需分页显示
|
||||
* 5. 2 级及以下子菜单坐标由 Menu_Sub1PosCal 递归计算
|
||||
*****************************************************************************/
|
||||
void Menu_PositionCal(tagPMenuItem ptMenuHead)
|
||||
{
|
||||
tagPMenuItem ptFirst[4]; /* 各级首项指针 [0..3] */
|
||||
tagPMenuItem ptIndex[4]; /* 各级当前遍历项指针 */
|
||||
|
||||
uint8_t byMenuNum[4]; /* 各级菜单项数量 */
|
||||
uint8_t byMaxLen; /* 1 级菜单项最大字符长度(含右箭头) */
|
||||
uint8_t byInterval; /* 0 级菜单项在 X 方向的间隔像素 */
|
||||
uint8_t by0LevelNum; /* 0 级菜单项总数 */
|
||||
|
||||
ptFirst[0] = ptMenuHead;
|
||||
ptIndex[0] = ptFirst[0];
|
||||
byMenuNum[0] = 1;
|
||||
ptIndex[0]->wPos = 1;
|
||||
|
||||
byInterval = LCD_SIZE_X / g_tMenuCtrl.by0LevelNum; /* 平均分布 */
|
||||
by0LevelNum = g_tMenuCtrl.by0LevelNum;
|
||||
|
||||
for (uint16_t wLoop = 0; wLoop < by0LevelNum; wLoop++) /* 遍历 0 级菜单项 */
|
||||
{
|
||||
if (ptIndex[0]->ptLower != NULL) /* 有下级菜单 */
|
||||
{
|
||||
/* 计算 1 级子项最大长度,并为其名称追加右箭头;更新 ptFirst/ptIndex/byMenuNum */
|
||||
byMaxLen = Menu_charLenCal(0, byMenuNum, ptFirst, ptIndex);
|
||||
|
||||
/* 左上角坐标计算 */
|
||||
/* 1 级下拉框左上角:按 0 级序号横向定位,纵向从屏底向上预留多行高度 */
|
||||
ptIndex[0]->wSPosX = (byMenuNum[0] - 1) * byInterval;
|
||||
ptIndex[0]->wSPosY = LCD_SIZE_Y - LINE_HEIGHT - byMenuNum[1] * LINE_HEIGHT - MENU_YADD;
|
||||
|
||||
/* 右下角坐标计算 */
|
||||
/* 下拉框右下角 X:起始 + 最大字符数×字宽 + 边距 */
|
||||
ptIndex[0]->wEPosX = ptIndex[0]->wSPosX + byMaxLen * MENU_WITDTH + MENU_XADD;
|
||||
/* 下拉框右下角 Y:与 0 级菜单底边齐平,若超出屏顶则取屏高(需分页) */
|
||||
ptIndex[0]->wEPosY = LCD_SIZE_Y - LINE_HEIGHT;
|
||||
|
||||
/* 递归计算 2 级及以下子菜单的显示坐标 */
|
||||
Menu_Sub1PosCal(1, ptFirst, ptIndex);
|
||||
}
|
||||
/* 移至 0 级下一项*/
|
||||
ptIndex[0] = ptIndex[0]->ptBehind;
|
||||
/*如果移至 0 级下一项后,发现回到了0级菜单的第一个菜单,则说明遍历完了0级菜单的所有菜单,结束*/
|
||||
if (ptIndex[0] == ptFirst[0])
|
||||
{
|
||||
break;
|
||||
}
|
||||
byMenuNum[0]++; /* 0级菜单项数量+1 */
|
||||
ptIndex[0]->wPos = byMenuNum[0]; /* 0级菜单项序号 + 1 */
|
||||
}
|
||||
}
|
||||
/******************************************************************************
|
||||
* 函数名: Lcd_Black_Button
|
||||
* 功能: 在指定矩形区域绘制一个带边框的实心“按钮”效果
|
||||
* 参数: left_x - 按钮左上角 X 坐标
|
||||
* top_y - 按钮左上角 Y 坐标
|
||||
* right_x - 按钮右下角 X 坐标
|
||||
* bottom_y - 按钮右下角 Y 坐标
|
||||
* 返回: 无
|
||||
* 说明: 1. 先用当前前景色填充内部区域(边框内减 1 像素)
|
||||
* 2. 再用 1 像素宽的水平/垂直线绘制上、下、左、右边框
|
||||
* 3. 颜色均使用 g_tCVsr.wFontColor(当前字体/前景颜色)
|
||||
*****************************************************************************/
|
||||
void Menu_BoundaryBox(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bottom_y)
|
||||
{
|
||||
/* 填充按钮内部区域:Y 从 top_y+1 到 bottom_y-1,X 从 left_x+1 到 right_x-1 */
|
||||
for (uint16_t y = top_y + 1; y < bottom_y; y++)
|
||||
{
|
||||
for (uint16_t x = left_x + 1; x < right_x; x++)
|
||||
{
|
||||
Lcd_SetPixel(x, y, g_tCVsr.wBackColor); /* 设置按钮内部像素为前景色,实现实心效果 */
|
||||
}
|
||||
}
|
||||
/* 绘制上边框:从 (left_x, top_y) 到 (right_x, top_y),线宽 1 像素 */
|
||||
Lcd_LineH(left_x, right_x, top_y, 1, g_tCVsr.wFontColor);
|
||||
/* 绘制左边框:从 (left_x, top_y) 到 (left_x, bottom_y),线宽 1 像素 */
|
||||
Lcd_LineV(top_y, bottom_y, left_x, 1, g_tCVsr.wFontColor);
|
||||
/* 绘制下边框:从 (left_x, bottom_y) 到 (right_x+1, bottom_y),稍微向右多画 1 像素防止边界漏点 */
|
||||
Lcd_LineH(left_x, right_x + 1, bottom_y, 1, g_tCVsr.wFontColor);
|
||||
/* 绘制右边框:从 (right_x, top_y) 到 (right_x, bottom_y),线宽 1 像素 */
|
||||
Lcd_LineV(top_y, bottom_y, right_x, 1, g_tCVsr.wFontColor);
|
||||
}
|
||||
/*
|
||||
这是分页的逻辑,需要保存,不能删除
|
||||
if (byMenuNum <= 10)
|
||||
{
|
||||
byPage = 1;
|
||||
}
|
||||
else if (byMenuNum <= 20)
|
||||
{
|
||||
byPage = 2;
|
||||
}
|
||||
else if (byMenuNum > 20)
|
||||
{
|
||||
byPage = 3;
|
||||
}
|
||||
if (byMenuNum > byMaxNum) // 菜单项数大于一页
|
||||
{
|
||||
wPosX = (wSPosX + wEPosX) / 2 - 5;
|
||||
wLoopMax = byMaxNum - 1;
|
||||
|
||||
if (byMenuPos < byMenuNum - (byMaxNum - 1) + 1) // 由第二页返回第一页
|
||||
{
|
||||
bySecPage = 0;
|
||||
g_tMenuCtrl.bySecPage = bySecPage;
|
||||
}
|
||||
|
||||
if ((byMenuPos < byMaxNum) && (0 == bySecPage)) // 当前位置在第一页
|
||||
{
|
||||
byFirstPos = 1;
|
||||
wPosY = wEPosY - LINE_HEIGHT;
|
||||
|
||||
Lcd_ButtonPush(wSPosX + 3, wPosY - 2, wEPosX - 4, wPosY + 14);
|
||||
Lcd_ShowStr(wPosX, wPosY, (uint8_t*)"↓"); // 第一页尾显示↓
|
||||
}
|
||||
else if ((byMenuPos < (byMaxNum * 2 - 2)) && (byPage > 2)) // 当前位置在第二页
|
||||
{
|
||||
bySecPage = 0xCC;
|
||||
g_tMenuCtrl.bySecPage = bySecPage;
|
||||
|
||||
byFirstPos = byMaxNum;
|
||||
wPosY = wSPosY + 2;
|
||||
|
||||
Lcd_ButtonPush(wSPosX + 3, wPosY - 1, wEPosX - 4, wPosY + 13);
|
||||
Lcd_ShowStr(wPosX, wPosY, (uint8_t*)"↑"); // 第二页头显示↑
|
||||
|
||||
wPosY = wEPosY - LINE_HEIGHT;
|
||||
|
||||
Lcd_ButtonPush(wSPosX + 3, wPosY - 2, wEPosX - 4, wPosY + 14);
|
||||
Lcd_ShowStr(wPosX, wPosY, (uint8_t*)"↓"); // 第一页尾显示↓
|
||||
}
|
||||
else
|
||||
{
|
||||
bySecPage = 0x77;
|
||||
g_tMenuCtrl.bySecPage = bySecPage;
|
||||
|
||||
if (byPage > 2)
|
||||
{
|
||||
byFirstPos = byMaxNum * 2 - 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
byFirstPos = byMenuNum - (byMaxNum - 1) + 1;
|
||||
}
|
||||
wPosY = wSPosY + 2;
|
||||
|
||||
Lcd_ButtonPush(wSPosX + 3, wPosY - 1, wEPosX - 4, wPosY + 13);
|
||||
Lcd_ShowStr(wPosX, wPosY, (uint8_t*)"↑"); // 第二页头显示↑
|
||||
}
|
||||
|
||||
for (wLoop = 0; wLoop < byMenuNum; wLoop++)
|
||||
{
|
||||
if (byFirstPos == ptIndex->wPos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
ptIndex = ptIndex->ptBehind;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bySecPage = 0;
|
||||
g_tMenuCtrl.bySecPage = bySecPage;
|
||||
}
|
||||
if (0xCC == bySecPage)
|
||||
{
|
||||
for (wLoop = 0; wLoop < (wLoopMax - 1); wLoop++)
|
||||
{
|
||||
byMenuPos = ptIndex->wPos - (byFirstPos - 1);
|
||||
wPosY = wSPosY + byMenuPos * LINE_HEIGHT + 3; // 计算显示字符位置
|
||||
|
||||
memcpy(byName, ptIndex->byName, 50);
|
||||
Lcd_ShowStr(wPosX, wPosY, byName); // 显示菜单字符串
|
||||
|
||||
if (ptRoute == ptIndex) // 当前菜单设置反显
|
||||
{
|
||||
Lcd_Invert(wSPosX + 2, wPosY - 1, wEPosX - 2, wPosY + 14);
|
||||
}
|
||||
ptIndex = ptIndex->ptBehind; // 取下一菜单结点
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wPosX = wSPosX + 4;
|
||||
for (wLoop = 0; wLoop < wLoopMax; wLoop++)
|
||||
{
|
||||
if (0x77 == bySecPage) // 显示第二页菜单
|
||||
{
|
||||
byMenuPos = ptIndex->wPos - (byFirstPos - 1);
|
||||
}
|
||||
else // 显示第一页菜单
|
||||
{
|
||||
byMenuPos = ptIndex->wPos - 1;
|
||||
}
|
||||
wPosY = wSPosY + byMenuPos * LINE_HEIGHT + 3; // 计算显示字符位置
|
||||
memcpy(byName, ptIndex->byName, 50);
|
||||
Lcd_ShowStr(wPosX, wPosY, byName); // 显示菜单字符串
|
||||
|
||||
if (ptRoute == ptIndex) // 当前菜单设置反显
|
||||
{
|
||||
Lcd_Invert(wSPosX + 2, wPosY - 1, wEPosX - 2, wPosY + 14);
|
||||
}
|
||||
ptIndex = ptIndex->ptBehind; // 取下一菜单结点
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/******************************************************************************
|
||||
* 函数名: Menu_Show_Other
|
||||
* 功能: 显示除 0 级以外的其它级别菜单(1 级 / 2 级 / 3 级)的下拉列表
|
||||
* 参数: bylevel - 要显示的菜单层级(1 表示 1 级菜单,2 表示 2 级菜单等)
|
||||
* byLeftMove - 菜单整体在 X 方向的左移像素数(用于横向滚动或对齐)
|
||||
* 返回: 无
|
||||
* 说明: 1. 使用 g_tMenuCtrl.ptRoute[bylevel] 获取本级菜单框坐标,并应用水平偏移
|
||||
* 2. 调用 Menu_BoundaryBox 绘制当前级菜单的背景边框矩形
|
||||
* 3. 以 g_tMenuCtrl.ptRoute[bylevel+1] 为当前“选中项”,逐项绘制所有本级菜单文字
|
||||
* 4. 对选中项对应的那一行调用 Lcd_Invert 进行反显高亮
|
||||
* 注意: 分页逻辑还没有完成,如果有分页的情况会显示错误
|
||||
*****************************************************************************/
|
||||
void Menu_Show_Other(uint8_t bylevel)
|
||||
{
|
||||
tagPMenuItem ptIndex; /* 本级菜单遍历指针 */
|
||||
tagPMenuItem ptRoute; /* 下一级“选中项”指针(用于确定当前高亮项) */
|
||||
|
||||
uint16_t wPosX; /* 菜单文本显示的起始 X 坐标 */
|
||||
uint16_t wPosY; /* 菜单文本显示的起始 Y 坐标 */
|
||||
|
||||
uint16_t byMenuNum; /* 本级菜单项总数,用于控制循环次数 */
|
||||
uint16_t byMenuPos; /* 当前菜单项在本级中的序号(1..byMenuNum) */
|
||||
uint8_t byName[50]; /* 临时缓冲区,用于拷贝菜单名称 */
|
||||
|
||||
/* 1. 基于当前层级路径结点,取得本级菜单框坐标并根据 byLeftMove 做水平偏移 */
|
||||
ptIndex = g_tMenuCtrl.ptRoute[bylevel];
|
||||
/* 绘制本级菜单的背景边框矩形 */
|
||||
Menu_BoundaryBox(ptIndex->wSPosX, ptIndex->wSPosY, ptIndex->wEPosX, ptIndex->wEPosY);
|
||||
|
||||
/* 2. 取得下一级路径中的当前结点,用于确定哪一项需要高亮 */
|
||||
ptRoute = g_tMenuCtrl.ptRoute[bylevel + 1]; /* 进入下一级菜单的选中项 */
|
||||
|
||||
ptIndex = ptRoute; /* 从选中项开始向后遍历同级菜单 */
|
||||
|
||||
/* 3. 逐项绘制菜单文字,并对选中项所在行做反显处理 */
|
||||
wPosX = ptIndex->wSPosX + 4; /* 文本相对左边框右移 4 像素,预留内边距 */
|
||||
for (uint16_t index = 0; index < g_tMenuCtrl.ptRoute[bylevel]->wNum; index++)
|
||||
{
|
||||
wPosY = g_tMenuCtrl.ptRoute[bylevel]->wSPosY + (ptIndex->wPos - 1) * LINE_HEIGHT + 3; /* 行顶坐标 + 行高 * 行号 + 3 像素微调 */
|
||||
memcpy(byName, ptIndex->byName, 50); /* 将菜单名称拷贝到临时缓存 */
|
||||
Lcd_ShowStr(wPosX, wPosY, byName); /* 在计算出的坐标位置显示菜单字符串 */
|
||||
|
||||
if (ptRoute == ptIndex) /* 若当前绘制项为“选中项” */
|
||||
{
|
||||
/* 对该行对应的矩形区域执行反显,用于高亮当前选择 */
|
||||
/* x轴:起始位置+2,结束位置-2,产生内嵌的感觉 */
|
||||
Lcd_Invert(g_tMenuCtrl.ptRoute[bylevel]->wSPosX + 2, wPosY - 1, g_tMenuCtrl.ptRoute[bylevel]->wEPosX - 2, wPosY + 14);
|
||||
}
|
||||
ptIndex = ptIndex->ptBehind; /* 沿同级链表向后移动,处理下一项 */
|
||||
}
|
||||
}
|
||||
/******************************************************************************
|
||||
* 函数名: Menu_Show_0Level
|
||||
* 功能: 绘制 0 级主菜单栏(顶部标题区域),按当前菜单指针依次显示各个 0 级菜单项
|
||||
* 参数: 无(依赖全局菜单控制结构 g_tMenuCtrl 与 LCD 显存控制 g_tCVsr)
|
||||
* 返回: 无
|
||||
* 说明: 1. 所有 0 级菜单在 X 方向上按等间距分布
|
||||
* 2. 每次循环仅在菜单项仍在屏幕可见区域时才绘制对应标题文本
|
||||
* 3. 顶部 0~32 像素区域作为 0 级菜单标题栏背景,会被统一清屏并重绘
|
||||
* 4. 调用 Lcd_MeiTouPic 绘制“眉头”装饰线条,增强标题栏的视觉效果
|
||||
* 5. 示例中固定显示 “当前模式: 无模式”,后续可替换为实际运行模式文本
|
||||
*****************************************************************************/
|
||||
void Menu_Show_0Level()
|
||||
{
|
||||
tagPMenuItem ptIndex; /* 遍历 0 级菜单链表用的指针(从表头开始依次后移) */
|
||||
uint16_t wSPosX; /* 当前要显示的 0 级菜单项左上角 X 坐标(屏幕坐标) */
|
||||
uint16_t wSPosY; /* 0 级菜单“下陷框”左上角 Y 坐标 */
|
||||
uint16_t wEPosX; /* 0 级菜单“下陷框”右下角 X 坐标 */
|
||||
uint16_t wEPosY; /* 0 级菜单“下陷框”右下角 Y 坐标 */
|
||||
uint16_t wLoop; /* 循环计数:第几个 0 级菜单项(从 0 开始) */
|
||||
uint8_t byName[50]; /* 临时缓冲区:存放当前 0 级菜单项名称 */
|
||||
uint8_t byInterval; /* 每个 0 级菜单在 X 方向上的间隔宽度(用于平均分布) */
|
||||
uint8_t byLeftMove; /* 当前 0 级菜单整体向左平移的像素数(超出一屏时产生“滚动”效果) */
|
||||
uint8_t by0LevelNum; /* 实际参与显示的 0 级菜单个数(是否显示“隐藏/显示”项会影响个数) */
|
||||
|
||||
|
||||
Lcd_ShowStr(16, 20, (uint8_t*)"你好!\nabcsadfasdfasdfsdfasdf"); /* 你好! */
|
||||
/* 先清除顶部 0~32 像素高度区域,作为 0 级菜单标题栏背景 */
|
||||
Lcd_FillRect(0, 0, LCD_SIZE_X, 32, g_tCVsr.wBackColor);
|
||||
/* 绘制“眉头”装饰,使 0 级菜单栏更加立体 */
|
||||
Lcd_MeiTouPic(16, 2 );
|
||||
Lcd_ShowStr(16, 20, (uint8_t*)"当前模式: 无模式" );
|
||||
}
|
||||
|
||||
void Menu_Init(void)
|
||||
{
|
||||
Lcd_Init();
|
||||
/* 初始化显示控制结构 */
|
||||
memset(&g_tDspCtrl, 0, sizeof(g_tDspCtrl));
|
||||
|
||||
Menu_0LevelNumCal(); /* 统计 0 级菜单项个数 */
|
||||
Menu_Main_Creat(); /* 创建菜单树 */
|
||||
g_tMenuCtrl.ptHead = &g_tMenuItem[0]; // 取菜单头结点
|
||||
Menu_PositionCal(g_tMenuCtrl.ptHead); /* 菜单框位置计算 */
|
||||
|
||||
g_tMenuCtrl.ptRoute[0] = &g_tMenuItem[0]; // 0级路径初始化
|
||||
g_tMenuCtrl.ptCurrent = g_tMenuCtrl.ptHead->ptLower; // 当前指针初始化
|
||||
g_tMenuCtrl.ptCurBak = g_tMenuCtrl.ptCurrent;
|
||||
g_tMenuCtrl.ptRoute[1] = g_tMenuCtrl.ptCurrent; // 1级路径初始化
|
||||
g_tMenuCtrl.byLeftMove = 0;
|
||||
g_tMenuCtrl.bySecPage = 0;
|
||||
|
||||
Menu_Show_0Level(); /* 显示 0 级菜单 */
|
||||
Menu_Show_Other(0);
|
||||
}
|
||||
|
||||
|
||||
void MenuProc_See_AppInfo()
|
||||
@@ -165,14 +856,33 @@ void MenuProc_YX_SetTwin()
|
||||
printf("MenuProc_YX_SetTwin\n");
|
||||
}
|
||||
|
||||
int Menu_MapKey(int ch)
|
||||
{
|
||||
if (ch == 'w' || ch == 'W') return KEY_U;
|
||||
if (ch == 's' || ch == 'S') return KEY_D;
|
||||
if (ch == 'a' || ch == 'A') return KEY_L;
|
||||
if (ch == 'd' || ch == 'D') return KEY_R;
|
||||
if (ch == '\r' || ch == '\n') return KEY_ENT;
|
||||
if (ch == 27) return KEY_ESC; /* ESC */
|
||||
return KEY_NONE;
|
||||
}
|
||||
/* ---------- 以下为 display.c 菜单表引用、暂无具体实现的桩函数,避免链接未定义符号 ---------- */
|
||||
void Menu_NonPfunc(void) { printf("Menu_NonPfunc\n"); }
|
||||
void MenuProc_AllInf_Default(void) { printf("MenuProc_AllInf_Default\n"); }
|
||||
void MenuProc_Para_Default(void) { printf("MenuProc_Para_Default\n"); }
|
||||
void MenuProc_Set_Default(void) { printf("MenuProc_Set_Default\n"); }
|
||||
void MenuProc_Resume_Soft(void) { printf("MenuProc_Resume_Soft\n"); }
|
||||
void MenuProc_Cfg_CellDef(void) { printf("MenuProc_Cfg_CellDef\n"); }
|
||||
void MenuProc_Cfg_ShowAnaType(void) { printf("MenuProc_Cfg_ShowAnaType\n"); }
|
||||
void MenuProc_Dbg_XuYX(void) { printf("MenuProc_Dbg_XuYX\n"); }
|
||||
void MenuProc_Dbg_XuYC(void) { printf("MenuProc_Dbg_XuYC\n"); }
|
||||
void MenuProc_Dbg_XuEvent(void) { printf("MenuProc_Dbg_XuEvent\n"); }
|
||||
void MenuProc_Dbg_Relay(void) { printf("MenuProc_Dbg_Relay\n"); }
|
||||
void MenuProc_See_VersionBoard(void){ printf("MenuProc_See_VersionBoard\n"); }
|
||||
void MenuProc_Cfg_ClrRec(void) { printf("MenuProc_Cfg_ClrRec\n"); }
|
||||
void MenuProc_Cfg_ComPara(void) { printf("MenuProc_Cfg_ComPara\n"); }
|
||||
void MenuProc_Cfg_EditIP(void) { printf("MenuProc_Cfg_EditIP\n"); }
|
||||
void MenuProc_Cfg_EditSntp(void) { printf("MenuProc_Cfg_EditSntp\n"); }
|
||||
void MenuProc_Cfg_CellConf(void) { printf("MenuProc_Cfg_CellConf\n"); }
|
||||
void MenuProc_See_RecSOE(void) { printf("MenuProc_See_RecSOE\n"); }
|
||||
void MenuProc_See_RecAct(void) { printf("MenuProc_See_RecAct\n"); }
|
||||
void MenuProc_See_RecOpt(void) { printf("MenuProc_See_RecOpt\n"); }
|
||||
void MenuProc_See_RecAlm(void) { printf("MenuProc_See_RecAlm\n"); }
|
||||
void MenuProc_See_RecStart(void) { printf("MenuProc_See_RecStart\n"); }
|
||||
void MenuProc_See_RecYK(void) { printf("MenuProc_See_RecYK\n"); }
|
||||
void MenuProc_See_RecChk(void) { printf("MenuProc_See_RecChk\n"); }
|
||||
void MenuProc_See_RecRun(void) { printf("MenuProc_See_RecRun\n"); }
|
||||
void MenuProc_See_RecFault(void) { printf("MenuProc_See_RecFault\n"); }
|
||||
void MenuProc_YC_SetSqValue(void) { printf("MenuProc_YC_SetSqValue\n"); }
|
||||
void MenuProc_YC_SetAdjCoe(void) { printf("MenuProc_YC_SetAdjCoe\n"); }
|
||||
|
||||
|
||||
@@ -13,6 +13,19 @@
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
|
||||
#define CN_HEIGHT 12 // 菜单汉字高
|
||||
#define CN_ROWSPACE 2 // 菜单字符行间隔
|
||||
#define LINE_HEIGHT (CN_HEIGHT + CN_ROWSPACE) // 字符行间隔
|
||||
#define MENU_XADD 7 // 菜单框横坐标增加
|
||||
#define MENU_YADD 4 // 菜单框纵坐标增加
|
||||
#define MENU_WITDTH 7 // 菜单汉字13×12点阵
|
||||
|
||||
|
||||
|
||||
#define MENU_YMIN 0 // 菜单Y坐标的最小值
|
||||
#define MENU_YMAX (LCD_SIZE_Y - 16) // 菜单Y坐标的最大值
|
||||
|
||||
typedef struct MenuItem MenuItem;
|
||||
|
||||
struct MenuItem {
|
||||
|
||||
@@ -93,7 +93,6 @@ int main(void)
|
||||
|
||||
printf("开始初始化菜单树...\n");
|
||||
Menu_Init(); /* 初始化菜单树(运行界面/定值设置/查看数据等) */
|
||||
Menu_Show_0Level();
|
||||
printf("PC 端 HMI 菜单模拟启动(TCP 服务在单独线程,端口 7003)。\n");
|
||||
/* 7003 为 RemoDispBus 默认端口,与 remo_disp_server.py 一致 */
|
||||
if (StartTcpServerThread(7003, &server_sock, &server_quit, &server_th) != 0)
|
||||
|
||||
Reference in New Issue
Block a user