修改了菜单按键的逻辑,使菜单可以根据按键动态跳转,但是还没有实现对应的操作逻辑
This commit is contained in:
3662
src/Drv/Ascii.c
3662
src/Drv/Ascii.c
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ extern uint8_t g_abyASCII168[][16];
|
||||
|
||||
|
||||
#define UTF8_HZ12_BYTES_PER_CHAR 24
|
||||
#define UTF8_HZ12_NUM_CHARS 4030
|
||||
#define UTF8_HZ12_NUM_CHARS 7038
|
||||
|
||||
/* 按 Unicode 码点查找点阵,返回 24 字节指针,未找到返回 NULL */
|
||||
const uint8_t* utf8_hz12_get(uint32_t unicode);
|
||||
|
||||
@@ -1,34 +1,46 @@
|
||||
/* 远程按键驱动模块:
|
||||
* 按键数据由外部(如远程显示、串口等)写入 g_tRemoteKey,
|
||||
* 本模块通过 Key_Read() 供菜单等上层逻辑读取。
|
||||
*/
|
||||
#include "key.h"
|
||||
|
||||
tagRKeyCtrl g_tRemoteKey; /* 远程按键控制结构 */
|
||||
|
||||
|
||||
typedef struct{ // 按键控制结构
|
||||
uint8_t byUsrFlg; // 用户标志,0x55标志 有显示已刷新,供远程显示用
|
||||
uint8_t byKeyValid; // 有效标志 <见 _KEY_VALID_FLAG_ 定义>
|
||||
uint8_t byKeyValue; // 键值
|
||||
uint8_t bUseRkey; //是否启用远程按键
|
||||
}tagRKeyCtrl;
|
||||
|
||||
tagRKeyCtrl g_tRemoteKey; //远程按键
|
||||
|
||||
|
||||
uint8_t Key_Read()
|
||||
/******************************************************************************
|
||||
* 函数名: Key_Init
|
||||
* 功能: 按键模块初始化,将远程按键控制结构体复位为默认状态
|
||||
* 参数: 无
|
||||
* 返回: 无
|
||||
* 说明: 清除有效标志、键值置 KEY_NONE、关闭远程按键使能
|
||||
*****************************************************************************/
|
||||
void Key_Init(void)
|
||||
{
|
||||
uint8_t byKeyTmp;
|
||||
g_tRemoteKey.byKeyValid = EN_KEY_FLAG_NULL; /* 无新按键 */
|
||||
g_tRemoteKey.byKeyValue = KEY_NONE; /* 键值清零 */
|
||||
g_tRemoteKey.bUseRkey = 1; /* 不启用远程按键 */
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* 函数名: Key_Read
|
||||
* 功能: 读取当前按键值(消费式读取)
|
||||
* 参数: 无
|
||||
* 返回: 按键值(KEY_U/KEY_D/KEY_L/KEY_R/KEY_ENT/KEY_ESC/KEY_F1/KEY_F2 等),
|
||||
* 无按键时返回 KEY_NONE
|
||||
* 说明: 若存在新按键则返回键值并清除有效标志;每次调用最多消费一个按键事件
|
||||
*****************************************************************************/
|
||||
uint8_t Key_Read(void)
|
||||
{
|
||||
uint8_t byKeyTmp; /* 本次读取到的键值 */
|
||||
|
||||
byKeyTmp = KEY_NONE;
|
||||
|
||||
if(EN_KEY_FLAG_NEW == g_tRemoteKey.byKeyValid )
|
||||
if (EN_KEY_FLAG_NEW == g_tRemoteKey.byKeyValid)
|
||||
{
|
||||
byKeyTmp = g_tRemoteKey.byKeyValue;
|
||||
g_tRemoteKey.byKeyValid = EN_KEY_FLAG_NULL;
|
||||
byKeyTmp = g_tRemoteKey.byKeyValue; /* 取出键值 */
|
||||
g_tRemoteKey.byKeyValid = EN_KEY_FLAG_NULL; /* 消费后清除标志 */
|
||||
}
|
||||
|
||||
return byKeyTmp;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,15 @@ enum _KEY_VALID_FLAG_ { // 按键是否有效标志
|
||||
EN_KEY_FLAG_SAM, // 采样过程中(未确定)
|
||||
};
|
||||
|
||||
typedef struct{ // 按键控制结构
|
||||
uint8_t byUsrFlg; // 用户标志,0x55标志 有显示已刷新,供远程显示用
|
||||
uint8_t byKeyValid; // 有效标志 <见 _KEY_VALID_FLAG_ 定义>
|
||||
uint8_t byKeyValue; // 键值
|
||||
uint8_t bUseRkey; //是否启用远程按键
|
||||
}tagRKeyCtrl;
|
||||
|
||||
uint8_t Key_Read();
|
||||
extern tagRKeyCtrl g_tRemoteKey; //远程按键
|
||||
|
||||
uint8_t Key_Read(void);
|
||||
void Key_Init(void);
|
||||
#endif
|
||||
@@ -1,7 +1,12 @@
|
||||
|
||||
#include "lcd.h"
|
||||
#include <string.h>
|
||||
#include "ascii.h"
|
||||
#include <math.h>
|
||||
#ifdef DEBUG
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
tagScreenControl g_tCVsr; // 当前界面结构指针
|
||||
tagDspAttrib g_tDspAttrib; // 显示属性
|
||||
|
||||
@@ -14,16 +19,17 @@ void Lcd_Init(void)
|
||||
g_tCVsr.wBackColor = LCD_COLOR_BLACK;
|
||||
/* 设置默认字体 */
|
||||
//g_tCVsr.pwLibHZ = (uint16_t*)HZK12;
|
||||
g_tCVsr.wGBFontWidth = 14;
|
||||
/* 字体的大小需要调试 */
|
||||
g_tCVsr.wGBFontWidth = 13;
|
||||
g_tCVsr.wGBFontHeight = 12;
|
||||
|
||||
/* 设置默认ASCII字体 */
|
||||
g_tCVsr.pbyLibAscii = g_abyASCII126[0];
|
||||
g_tCVsr.wASCIIFontWidth = 8;
|
||||
g_tCVsr.wASCIIFontWidth = 7;
|
||||
g_tCVsr.wASCIIFontHeight = 12;
|
||||
g_tDspAttrib.rowSpace = 2;
|
||||
}
|
||||
void Lcd_SetPixel(uint16_t x, uint16_t y, uint16_t color)
|
||||
void Lcd_SetPixel(uint16_t x, uint16_t y, uint8_t color)
|
||||
{
|
||||
if (x >= LCD_SIZE_X || y >= LCD_SIZE_Y)
|
||||
{
|
||||
@@ -86,7 +92,7 @@ void Lcd_FillRect(uint16_t left_x, uint16_t top_y, uint16_t right_x, uint16_t bo
|
||||
* 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)
|
||||
void Lcd_LineH(uint16_t wXStart, uint16_t wXEnd, uint16_t wYStart, uint16_t wWidth, uint8_t color)
|
||||
{
|
||||
uint16_t wYEnd = wYStart + wWidth; /* 计算水平线在 Y 方向的结束位置 = 起始 Y + 线宽 */
|
||||
|
||||
@@ -114,7 +120,7 @@ void Lcd_LineH(uint16_t wXStart, uint16_t wXEnd, uint16_t wYStart, uint16_t wWid
|
||||
* 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)
|
||||
void Lcd_LineV(uint16_t wYStart, uint16_t wYEnd, uint16_t wXStart, uint16_t wWidth, uint8_t color)
|
||||
{
|
||||
uint16_t wXEnd = wXStart + wWidth;
|
||||
|
||||
@@ -146,7 +152,7 @@ void Lcd_LineV(uint16_t wYStart, uint16_t wYEnd, uint16_t wXStart, uint16_t wWid
|
||||
* 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)
|
||||
void Lcd_Line(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend, uint16_t wWidth, uint8_t color)
|
||||
{
|
||||
uint16_t wDX; /* X 方向差值(水平偏移量的绝对值) */
|
||||
uint16_t wDY; /* Y 方向差值(垂直偏移量的绝对值) */
|
||||
@@ -226,7 +232,6 @@ void Lcd_Line(uint16_t wXstart, uint16_t wYstart, uint16_t wXend, uint16_t wYend
|
||||
wDecision = (wDY >> 1); /* 初始化判别值为一半的 wDY */
|
||||
for (; wCury <= wNexty; ) /* 从两端向中间画,直到在 Y 方向相遇 */
|
||||
{
|
||||
|
||||
/* 累积误差超过一条“格子高度”时,说明需要在 X 方向跨一格 */
|
||||
if (wDecision >= wDY)
|
||||
{
|
||||
@@ -343,7 +348,7 @@ inline uint16_t Lcd_Pub_Ascii(uint16_t x, uint16_t y, uint8_t byAscii)
|
||||
{
|
||||
uint8_t i, j;
|
||||
uint8_t byLine, *pbyFontLib;
|
||||
uint16_t on_color, off_color;
|
||||
uint8_t on_color, off_color;
|
||||
|
||||
/* 从 ASCII 字库中取得当前字符的点阵数据首地址
|
||||
每个字符占用 wASCIIFontHeight 个字节, 按行存储 */
|
||||
@@ -379,7 +384,7 @@ inline uint16_t Lcd_Pub_Ascii(uint16_t x, uint16_t y, uint8_t byAscii)
|
||||
for (i = 0; i < g_tCVsr.wASCIIFontWidth; i++)
|
||||
{
|
||||
uint8_t bit_on = ((byLine << i) & 0x80) != 0;
|
||||
uint16_t color;
|
||||
uint8_t color;
|
||||
uint16_t px, py;
|
||||
|
||||
if (bit_on)
|
||||
@@ -423,7 +428,7 @@ inline uint16_t Lcd_Pub_Ascii(uint16_t x, uint16_t y, uint8_t byAscii)
|
||||
* @param out_unicode 输出该字符的 Unicode 码点(U+0000..U+FFFF)
|
||||
* @return 该字符占用的字节数 1/2/3;0 表示结束、无效或无法解析
|
||||
*/
|
||||
static int utf8_next(const unsigned char *utf8, uint32_t *out_unicode)
|
||||
uint8_t utf8_next(const unsigned char *utf8, uint32_t *out_unicode)
|
||||
{
|
||||
unsigned char c = utf8[0];
|
||||
|
||||
@@ -477,8 +482,12 @@ void Lcd_Pub_UTF8(uint16_t x, uint16_t y, uint32_t unicode )
|
||||
const uint8_t *bitmap = utf8_hz12_get(unicode);
|
||||
uint16_t word = 0;
|
||||
if (bitmap == NULL)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("Lcd_Pub_UTF8: unicode = %d, bitmap = NULL\n", unicode);
|
||||
#endif
|
||||
return;
|
||||
|
||||
}
|
||||
for (uint8_t j = 0; j < g_tCVsr.wGBFontHeight; j++)
|
||||
{
|
||||
word = (uint16_t)((bitmap[j*2] << 8) | bitmap[j*2+1]);
|
||||
@@ -509,8 +518,7 @@ int8_t Lcd_ShowStr(uint16_t x, uint16_t y, uint8_t *pcString)
|
||||
uint16_t bakx, baky; /* 当前行行首坐标,换行时 x 回到 bakx */
|
||||
uint32_t unicode; /* utf8_next 解析出的当前字符码点 */
|
||||
uint16_t index = 0; /* 当前字符在 pcString 中的字节下标 */
|
||||
int8_t err;
|
||||
|
||||
uint8_t n = 0;
|
||||
bakx = x;
|
||||
baky = y;
|
||||
|
||||
@@ -519,11 +527,10 @@ int8_t Lcd_ShowStr(uint16_t x, uint16_t y, uint8_t *pcString)
|
||||
return -1;
|
||||
if (y >= LCD_SIZE_Y - g_tCVsr.wGBFontHeight)
|
||||
return -2;
|
||||
|
||||
while (pcString[index] != 0x0)
|
||||
{
|
||||
/* 解析当前字符:n = 占用字节数(1=ASCII,2/3=多字节),unicode = 码点 */
|
||||
int n = utf8_next(pcString + index, &unicode);
|
||||
n = utf8_next(pcString + index, &unicode);
|
||||
if (n <= 0)
|
||||
break;
|
||||
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
typedef struct
|
||||
{
|
||||
uint8_t pwbyLCDMemory[LCD_DISPLAYMEMORYSIZE]; //定义显存
|
||||
uint16_t wFontColor; // 字体颜色
|
||||
uint16_t wBackColor; // 字符显示背景颜色
|
||||
uint16_t wGBFontWidth; // 汉字字体宽度
|
||||
uint16_t wGBFontHeight; // 汉字字体高度
|
||||
uint16_t wASCIIFontWidth; // 字符字体宽度
|
||||
uint16_t wASCIIFontHeight; // 字符字体高度
|
||||
uint8_t wFontColor; // 字体颜色
|
||||
uint8_t wBackColor; // 字符显示背景颜色
|
||||
uint8_t wGBFontWidth; // 汉字字体宽度
|
||||
uint8_t wGBFontHeight; // 汉字字体高度
|
||||
uint8_t wASCIIFontWidth; // 字符字体宽度
|
||||
uint8_t wASCIIFontHeight; // 字符字体高度
|
||||
uint16_t *pwLibHZ; // 汉字库地址
|
||||
uint8_t *pbyLibAscii; // ASCII库地址
|
||||
} tagScreenControl;
|
||||
@@ -48,15 +48,16 @@ typedef struct { // 显示属性数据结构
|
||||
|
||||
|
||||
void Lcd_Init(void);
|
||||
void Lcd_SetPixel(uint16_t x, uint16_t y, uint16_t color);
|
||||
void Lcd_SetPixel(uint16_t x, uint16_t y, uint8_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);
|
||||
void Lcd_LineH(uint16_t wXStart, uint16_t wXEnd, uint16_t wYStart, uint16_t wWidth, uint8_t color);
|
||||
void Lcd_LineV(uint16_t wYStart, uint16_t wYEnd, uint16_t wXStart, uint16_t wWidth, uint8_t color);
|
||||
|
||||
uint8_t utf8_next(const unsigned char *utf8, uint32_t *out_unicode);
|
||||
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
|
||||
370
src/Drv/menu.c
370
src/Drv/menu.c
@@ -4,6 +4,7 @@
|
||||
#include <string.h>
|
||||
#include "lcd.h"
|
||||
#include "display.h"
|
||||
#include "key.h"
|
||||
|
||||
/* 简单的静态菜单树:
|
||||
*
|
||||
@@ -28,13 +29,13 @@
|
||||
|
||||
uint16_t wPos; /* 当前菜单在本级中的位置 */
|
||||
uint16_t wNum; /* 下级菜单的总项数 */
|
||||
uint16_t wSPosX; // 下级菜单起始坐标
|
||||
uint16_t wSPosY; // 下级菜单起始坐标
|
||||
uint16_t wEPosX; // 下级菜单对角坐标
|
||||
uint16_t wEPosY; // 下级菜单对角坐标
|
||||
uint16_t wSPosX; /*下级菜单框 x 轴的起始坐标*/
|
||||
uint16_t wSPosY; /*下级菜单框 y 轴的起始坐标*/
|
||||
uint16_t wEPosX; /*下级菜单框 x 轴的结束坐标*/
|
||||
uint16_t wEPosY; /*下级菜单框 y 轴的结束坐标*/
|
||||
}tagMenuItem,*tagPMenuItem;
|
||||
|
||||
// 显示控制结构
|
||||
// 显示控制结构
|
||||
typedef struct
|
||||
{
|
||||
tagPMenuItem ptMenuCur; // 菜单当前指针
|
||||
@@ -73,15 +74,15 @@ typedef struct
|
||||
|
||||
typedef struct
|
||||
{ // 菜单控制结构
|
||||
uint8_t by0LevelNum; // 0级菜单项数
|
||||
uint8_t byLeftMove; // 菜单左移长度
|
||||
uint8_t bySecPage; // 第二页菜单标志
|
||||
uint8_t by0LevelNum; // 0级菜单项数
|
||||
uint8_t byLeftMove; // 菜单左移长度
|
||||
uint8_t bySecPage; // 第二页菜单标志
|
||||
FUNCPTR fnExecute; // 执行函数指针
|
||||
|
||||
tagPMenuItem ptHead; // 菜单链表头指针
|
||||
tagPMenuItem ptCurrent; // 菜单链表当前指针
|
||||
tagPMenuItem ptCurrent; /* 菜单链表当前指针 */
|
||||
tagPMenuItem ptRoute[4]; // 0-3级菜单的指针路径
|
||||
tagPMenuItem ptCurBak; // 菜单链表指针备份
|
||||
tagPMenuItem ptCurBak; /* ptCurrent 的备份,通过比较ptCurBak和ptCurrent是否相等来判断是否发生移动 */
|
||||
tagPMenuItem pt0Level; // 0级菜单链表指针备份
|
||||
|
||||
tagPMenuItem ptSeeSetSoft; // 查看定值菜单结点
|
||||
@@ -92,8 +93,8 @@ typedef struct
|
||||
tagPMenuItem ptSetValPara; // 整定定值菜单结点
|
||||
}tagMenuCtrl,*tagPMenuCtrl;
|
||||
|
||||
tagDspCtrl g_tDspCtrl; // 显示控制全局结构
|
||||
tagMenuCtrl g_tMenuCtrl; // 菜单全局控制结构
|
||||
tagDspCtrl g_tDspCtrl; /* 显示控制全局结构 */
|
||||
tagMenuCtrl g_tMenuCtrl; /* 菜单全局控制结构 */
|
||||
|
||||
|
||||
tagMenuItem g_tMenuItem[300]; // 菜单链表空间定义
|
||||
@@ -242,6 +243,28 @@ void Menu_Main_Creat(void)
|
||||
ptFirst[wLoop1]->ptBefore = ptLast[wLoop1];
|
||||
}
|
||||
}
|
||||
uint8_t utf8_len_cal(uint8_t *str)
|
||||
{
|
||||
uint8_t strLen = 0;
|
||||
uint32_t unicode;
|
||||
uint8_t index = 0;
|
||||
uint8_t n = 0;
|
||||
|
||||
while (str[index] != '\0')
|
||||
{
|
||||
n = utf8_next(str + index, &unicode);
|
||||
if (n > 1)
|
||||
{
|
||||
strLen += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
strLen++;
|
||||
}
|
||||
index += n;
|
||||
}
|
||||
return strLen;
|
||||
}
|
||||
/******************************************************************************
|
||||
* 函数名: Menu_charLenCal
|
||||
* 功能: 1.计算指定级菜单下所有子项的最大显示长度
|
||||
@@ -262,6 +285,7 @@ uint8_t Menu_charLenCal(uint8_t bylevel, uint8_t *byMenuNum, tagPMenuItem *ptFir
|
||||
{
|
||||
uint16_t wLoop;
|
||||
uint8_t byStrLen; /* 当前菜单项名称长度(含可能追加的右箭头) */
|
||||
uint8_t utf8Len;
|
||||
uint8_t byMaxLen; /* 同级菜单中的最大显示长度 */
|
||||
|
||||
/* 记录子级首项,供后续遍历与布局使用 */
|
||||
@@ -277,19 +301,19 @@ uint8_t Menu_charLenCal(uint8_t bylevel, uint8_t *byMenuNum, tagPMenuItem *ptFir
|
||||
for (wLoop = 0; wLoop < MENU_MAX_ITEM; wLoop++)
|
||||
{
|
||||
/* 计算当前菜单项名称长度 */
|
||||
utf8Len = utf8_len_cal(ptIndex[bylevel]->byName);
|
||||
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;
|
||||
utf8Len += 1;
|
||||
}
|
||||
|
||||
if (byMaxLen < byStrLen)
|
||||
if (byMaxLen < utf8Len)
|
||||
{
|
||||
byMaxLen = byStrLen;
|
||||
byMaxLen = utf8Len;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
printf("计算%d级菜单位置:%s,长度%d\n", bylevel, ptIndex[bylevel]->byName, byMaxLen);
|
||||
@@ -319,7 +343,7 @@ void Menu_Sub2PosCal(uint8_t bylevel, tagPMenuItem *ptFirst, tagPMenuItem *ptInd
|
||||
uint16_t wTemp;
|
||||
uint8_t byMaxLen;
|
||||
uint8_t byMaxNum;
|
||||
uint8_t byMenuPos;
|
||||
uint16_t byMenuPos;
|
||||
uint8_t byItemNum;
|
||||
uint8_t byMenuNum[4];
|
||||
|
||||
@@ -392,55 +416,74 @@ void Menu_Sub2PosCal(uint8_t bylevel, tagPMenuItem *ptFirst, tagPMenuItem *ptInd
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 功能:根据当前菜单层级 bylevel,计算该层级所有子菜单(如二级菜单)的矩形框位置。
|
||||
* 主要负责:
|
||||
* - 统计下一级菜单的最大字符长度,用于确定菜单框的宽度(X 方向)
|
||||
* - 根据上一级菜单条目的位置以及当前条目在本列中的位置,计算菜单框的起始/结束 Y 坐标
|
||||
* - 处理菜单项超过一屏时的翻页显示逻辑,保证菜单框不超出屏幕边界
|
||||
*
|
||||
* 参数说明:
|
||||
* bylevel :当前要计算的菜单层级(例如 1 表示一级菜单,2 表示二级菜单)
|
||||
* ptFirst :保存各层级“同级菜单链表首节点”的指针数组
|
||||
* ptIndex :在遍历过程中使用的“当前菜单指针数组”,ptIndex[bylevel] 表示当前层级正在处理的菜单项
|
||||
*/
|
||||
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;
|
||||
uint16_t wSPosY; // 当前层级菜单框的纵向起始坐标(Start Y)
|
||||
uint16_t wEPosY; // 当前层级菜单框的纵向结束坐标(End Y)
|
||||
uint8_t byMenuNum[4]; // 各层级菜单项数量的临时缓存数组,供长度/行数计算使用
|
||||
uint8_t byMaxLen; // 下一级菜单条目中,最长标题的字符数,用来确定菜单框宽度
|
||||
uint8_t byMaxNum; // 在一屏竖直方向上最多能显示多少条菜单项
|
||||
uint16_t byMenuPos; // 当前菜单在本层菜单链表中的序号位置(用于计算其在屏幕中的纵向位置)
|
||||
uint8_t byItemNum; // 当前参与计算的“菜单项数量”(根据语境,既可能是当前层,也可能是子菜单层)
|
||||
uint16_t wTemp; // 用于中间计算时的临时变量(例如交换起止坐标等)
|
||||
|
||||
byMaxNum = (MENU_YMAX) / LINE_HEIGHT; // 页内纵向可显示菜单数
|
||||
byMaxNum = (MENU_YMAX) / LINE_HEIGHT; // 在当前屏幕高度内,竖直方向上一页最多能显示的菜单行数
|
||||
|
||||
ptIndex[bylevel] = ptFirst[bylevel];
|
||||
for (uint16_t wLoop = 0; wLoop < MENU_MAX_ITEM; wLoop++)
|
||||
ptIndex[bylevel] = ptFirst[bylevel]; // 从本层级菜单链表的首节点开始遍历
|
||||
for (uint16_t wLoop = 0; wLoop < MENU_MAX_ITEM; wLoop++) // 遍历当前层级的所有菜单项(以最大项数为上限防止死循环)
|
||||
{
|
||||
if ((ptIndex[bylevel]->ptLower) != NULL) // 当前1级菜单有下级菜单
|
||||
if ((ptIndex[bylevel]->ptLower) != NULL) // 如果当前菜单项存在下一级菜单(即有子菜单需要显示)
|
||||
{
|
||||
// 统计下一级菜单的条目数和每个条目的字符长度,返回该下一级中最长标题的字符数
|
||||
// 同时,把各层级菜单数量写入 byMenuNum[] 中,用于后面计算 Y 坐标和分页
|
||||
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级菜单矩形框横坐标终止
|
||||
/* X 坐标计算:二级(或更深层级)菜单的矩形框紧挨着上一级菜单的右侧展开 */
|
||||
ptIndex[bylevel]->wSPosX = ptIndex[bylevel - 1]->wEPosX; // 2级菜单矩形框横坐标起始 = 上一级菜单矩形框的结束 X
|
||||
ptIndex[bylevel]->wEPosX = ptIndex[bylevel]->wSPosX + byMaxLen * MENU_WITDTH + MENU_XADD; // 2级菜单矩形框横坐标终止 = 起始 X + 最大字符宽度 + 额外边框间距
|
||||
|
||||
|
||||
byMenuPos = ptIndex[bylevel]->wPos; // 1级菜单在菜单列中的位置
|
||||
byItemNum = byMenuNum[bylevel]; // 1级菜单项数
|
||||
byMenuPos = ptIndex[bylevel]->wPos; // 当前菜单在本层菜单链表中的逻辑位置(第几项)
|
||||
byItemNum = byMenuNum[bylevel]; // 本层菜单总项数
|
||||
|
||||
// 当本层菜单总数超过一屏,且当前高亮菜单已经滚动到非第一页时,
|
||||
// 需要对菜单在当前页中的显示位置进行折算,实现“↑/↓”翻页效果。
|
||||
if ((byItemNum > byMaxNum) && (byMenuPos >= byMaxNum)) // 1级菜单项数大于一页且1级菜单当前位置不在第一页
|
||||
{
|
||||
byMenuPos = byMaxNum - (byItemNum - byMenuPos); /* 第一页尾显示↓ 第二页头显示↑ */
|
||||
}
|
||||
|
||||
// 根据“上一级菜单框的起始 Y 坐标”和“当前菜单位于本层中的序号”,
|
||||
// 计算出当前子菜单框在 Y 方向上的起始位置,使子菜单在对应父菜单项的右侧对齐。
|
||||
wSPosY = ptIndex[bylevel - 1]->wSPosY + (byMenuPos - 1) * LINE_HEIGHT; // 计算2级菜单框起始坐标
|
||||
|
||||
byItemNum = byMenuNum[bylevel + 1]; // 2级菜单项数
|
||||
wEPosY = wSPosY + byItemNum * LINE_HEIGHT + MENU_YADD; // 计算2级菜单框终止坐标
|
||||
byItemNum = byMenuNum[bylevel + 1]; // 下一级(子菜单)项数
|
||||
wEPosY = wSPosY + byItemNum * LINE_HEIGHT + MENU_YADD; // 计算2级菜单框终止坐标 = 起始 Y + 所有子项高度 + 边框间距
|
||||
#ifdef DEBUG
|
||||
printf("%d %d %d %d \n", bylevel, byItemNum, wSPosY, wEPosY);
|
||||
#endif
|
||||
// 情况 1:子菜单框整体没有超过屏幕的最大 Y 边界,直接采用计算结果
|
||||
if (wEPosY < MENU_YMAX) /* 菜单框没有超出边界 */
|
||||
{
|
||||
ptIndex[bylevel]->wSPosY = wSPosY; // 2级菜单矩形框纵起始坐标定位
|
||||
ptIndex[bylevel]->wEPosY = wEPosY; // 2级菜单矩形框纵终止坐标定位
|
||||
}
|
||||
else /* 菜单框超出边界 */
|
||||
else /* 情况 2:子菜单框在向下展开时超出了屏幕底部,需要整体向上“提”一段 */
|
||||
{
|
||||
// 反向计算:尝试让子菜单从当前高亮项“往上展开”,
|
||||
// 使最后一项与当前父菜单项对齐,从而避免超出底部。
|
||||
wEPosY = wSPosY - (byItemNum - 1) * LINE_HEIGHT - MENU_YADD;
|
||||
if ((wEPosY > LINE_HEIGHT) && (wEPosY < MENU_YMAX)) // 菜单向上不越限
|
||||
{
|
||||
@@ -497,6 +540,7 @@ void Menu_Sub1PosCal(uint8_t bylevel, tagPMenuItem *ptFirst, tagPMenuItem *ptInd
|
||||
* 3. 下拉框宽度由 Menu_charLenCal 返回的 byMaxLen 与字宽 CN_WITDTH 决定
|
||||
* 4. 若下拉框超出屏幕顶部(wEPosY < CN_YMAX),则 wEPosY 取屏高,需分页显示
|
||||
* 5. 2 级及以下子菜单坐标由 Menu_Sub1PosCal 递归计算
|
||||
* 注意:当前还有一个 bug,没有检查坐标的上下界,导致按照上下界清屏会清不干净
|
||||
*****************************************************************************/
|
||||
void Menu_PositionCal(tagPMenuItem ptMenuHead)
|
||||
{
|
||||
@@ -521,18 +565,21 @@ void Menu_PositionCal(tagPMenuItem ptMenuHead)
|
||||
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;
|
||||
#ifdef DEBUG
|
||||
printf("Menu_PositionCal: wLoop = %d, byMaxLen = %d, ptIndex[0]->wEPosX = %d, ptIndex[0]->wEPosY = %d\n", wLoop, byMaxLen, ptIndex[0]->wEPosX, ptIndex[0]->wEPosY);
|
||||
#endif
|
||||
|
||||
/* 递归计算 2 级及以下子菜单的显示坐标 */
|
||||
Menu_Sub1PosCal(1, ptFirst, ptIndex);
|
||||
@@ -563,9 +610,9 @@ void Menu_PositionCal(tagPMenuItem ptMenuHead)
|
||||
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 y = top_y; y < bottom_y; y++)
|
||||
{
|
||||
for (uint16_t x = left_x + 1; x < right_x; x++)
|
||||
for (uint16_t x = left_x; x < right_x; x++)
|
||||
{
|
||||
Lcd_SetPixel(x, y, g_tCVsr.wBackColor); /* 设置按钮内部像素为前景色,实现实心效果 */
|
||||
}
|
||||
@@ -708,7 +755,6 @@ else if (byMenuNum > 20)
|
||||
* 函数名: Menu_Show_Other
|
||||
* 功能: 显示除 0 级以外的其它级别菜单(1 级 / 2 级 / 3 级)的下拉列表
|
||||
* 参数: bylevel - 要显示的菜单层级(1 表示 1 级菜单,2 表示 2 级菜单等)
|
||||
* byLeftMove - 菜单整体在 X 方向的左移像素数(用于横向滚动或对齐)
|
||||
* 返回: 无
|
||||
* 说明: 1. 使用 g_tMenuCtrl.ptRoute[bylevel] 获取本级菜单框坐标,并应用水平偏移
|
||||
* 2. 调用 Menu_BoundaryBox 绘制当前级菜单的背景边框矩形
|
||||
@@ -724,11 +770,9 @@ void Menu_Show_Other(uint8_t bylevel)
|
||||
uint16_t wPosX; /* 菜单文本显示的起始 X 坐标 */
|
||||
uint16_t wPosY; /* 菜单文本显示的起始 Y 坐标 */
|
||||
|
||||
uint16_t byMenuNum; /* 本级菜单项总数,用于控制循环次数 */
|
||||
uint16_t byMenuPos; /* 当前菜单项在本级中的序号(1..byMenuNum) */
|
||||
uint8_t byName[50]; /* 临时缓冲区,用于拷贝菜单名称 */
|
||||
|
||||
/* 1. 基于当前层级路径结点,取得本级菜单框坐标并根据 byLeftMove 做水平偏移 */
|
||||
/* 1. 获取当前层级路径结点,用于绘制本级菜单的背景边框矩形 */
|
||||
ptIndex = g_tMenuCtrl.ptRoute[bylevel];
|
||||
/* 绘制本级菜单的背景边框矩形 */
|
||||
Menu_BoundaryBox(ptIndex->wSPosX, ptIndex->wSPosY, ptIndex->wEPosX, ptIndex->wEPosY);
|
||||
@@ -739,14 +783,16 @@ void Menu_Show_Other(uint8_t bylevel)
|
||||
ptIndex = ptRoute; /* 从选中项开始向后遍历同级菜单 */
|
||||
|
||||
/* 3. 逐项绘制菜单文字,并对选中项所在行做反显处理 */
|
||||
wPosX = ptIndex->wSPosX + 4; /* 文本相对左边框右移 4 像素,预留内边距 */
|
||||
wPosX = g_tMenuCtrl.ptRoute[bylevel]->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) /* 若当前绘制项为“选中项” */
|
||||
#ifdef DEBUG
|
||||
printf("Menu_Show_Other: wPosX = %d, wPosY = %d, byName = %s\n", wPosX, wPosY, byName);
|
||||
#endif
|
||||
if(ptRoute == ptIndex) /* 若当前绘制项为“选中项” */
|
||||
{
|
||||
/* 对该行对应的矩形区域执行反显,用于高亮当前选择 */
|
||||
/* x轴:起始位置+2,结束位置-2,产生内嵌的感觉 */
|
||||
@@ -774,12 +820,222 @@ void Menu_Show_0Level()
|
||||
Lcd_MeiTouPic(16, 2 );
|
||||
Lcd_ShowStr(16, 20, (uint8_t*)"当前模式: 无模式" );
|
||||
}
|
||||
/******************************************************************************
|
||||
* 函数名: Menu_Show_Proc
|
||||
* 功能: 根据当前菜单状态刷新 0~3 级菜单显示(增量刷新或整体刷新)
|
||||
* 情况一:0 级路径发生变化(如 ESC 退回或跨列切换):需要整体刷新
|
||||
* 情况二:0 级路径未变,但当前结点发生变化
|
||||
1. 同级内上下移动: 仅重画本级下拉菜单(current->byClass-1 对应的菜单框索引)
|
||||
2. 进层: 仅重画本级下拉菜单(current->byClass-1 对应的菜单框索引)
|
||||
3. 退层:清屏并重画 0~3 级菜单(这是最简单的操作,但是比较耗费资源)
|
||||
所以1. 2. 的逻辑可以合并为:仅重画本级下拉菜单(current->byClass-1 对应的菜单框索引)
|
||||
* 参数: 无(依赖全局 g_tMenuCtrl、g_tDspCtrl、g_tCVsr)
|
||||
* 返回: 无
|
||||
* 说明: 该函数后续可以改进退层的逻辑,减少计算量
|
||||
* 注意: 超出屏幕范围的逻辑还没有处理
|
||||
*****************************************************************************/
|
||||
void Menu_Show_Proc(void)
|
||||
{
|
||||
uint8_t bNeedFresh; /* 是否需要整体刷新 0~3 级菜单(1=是,0=否) */
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("Menu_Show_proc进入\n");
|
||||
#endif
|
||||
|
||||
bNeedFresh = 0; /* 默认不整体刷新,仅增量更新 */
|
||||
|
||||
if (g_tDspCtrl.bFirst) /* 第一次进入菜单层标志 */
|
||||
{
|
||||
g_tDspCtrl.bFirst = 0; /* 清除首次标志 */
|
||||
bNeedFresh = 1; /* 强制整体刷新 */
|
||||
}
|
||||
/* 情况一:0 级路径发生变化(如 ESC 退回或跨列切换) */
|
||||
if (g_tMenuCtrl.pt0Level != g_tMenuCtrl.ptRoute[0])
|
||||
{
|
||||
bNeedFresh = 1; /* 需要整体刷新 */
|
||||
Lcd_FillRect(0, MENU_YMIN, LCD_SIZE_X, MENU_YMAX, g_tCVsr.wBackColor); /* 清除 1~3 级菜单区域 */
|
||||
g_tMenuCtrl.pt0Level = g_tMenuCtrl.ptRoute[0]; /* 更新 0 级路径起点 */
|
||||
g_tMenuCtrl.ptCurBak = g_tMenuCtrl.ptCurrent; /* 更新备份指针 */
|
||||
}
|
||||
else if (g_tMenuCtrl.ptCurBak != g_tMenuCtrl.ptCurrent) /* 情况二:0 级路径未变,但当前结点发生变化 */
|
||||
{
|
||||
if (g_tMenuCtrl.ptCurrent->byClass >= g_tMenuCtrl.ptCurBak->byClass) /* 2.1 同级内上下移动 或者 进层*/
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("同级内上下移动 或 进层:从 %d 级到 %d 级\n", g_tMenuCtrl.ptCurBak->byClass, g_tMenuCtrl.ptCurrent->byClass);
|
||||
#endif
|
||||
Menu_Show_Other(g_tMenuCtrl.ptCurrent->byClass - 1); /* 仅重画本级下拉菜单(byClass-1 对应 1~3 级菜单框索引) */
|
||||
/*由于刚开是已经判断了 0 层没有发生变化,因此 ptCurrent->byClass 一定是大于 1 的,所以不需要判断*/
|
||||
}
|
||||
else /* 2.2 级别减小(退层,如从 2 级退回 1 级) */
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("退层:从 %d 级退回 %d 级\n", g_tMenuCtrl.ptCurBak->byClass, g_tMenuCtrl.ptCurrent->byClass);
|
||||
#endif
|
||||
Lcd_FillRect(0, MENU_YMIN, LCD_SIZE_X, MENU_YMAX, g_tCVsr.wBackColor); /* 擦除 1~3 级区域(保留 0~32 像素标题栏) */
|
||||
bNeedFresh = 1;
|
||||
}
|
||||
g_tMenuCtrl.ptCurBak = g_tMenuCtrl.ptCurrent; /* 无论何种情况,更新备份指针为最新当前结点 */
|
||||
}
|
||||
|
||||
/* 情况三:需要整体刷新 0~3 级菜单 */
|
||||
if (bNeedFresh > 0)
|
||||
{
|
||||
Menu_Show_0Level(); /* 显示 0 级菜单 */
|
||||
for (uint8_t index = 0; index < g_tMenuCtrl.ptCurrent->byClass; index++) /* 依次显示 1 级、2 级、3 级(直到当前层级) */
|
||||
{
|
||||
Menu_Show_Other(index);
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
printf("Menu_Show_proc结束\n");
|
||||
#endif
|
||||
}
|
||||
/******************************************************************************
|
||||
* 函数名: Menu_Route
|
||||
* 功能: 菜单层按键调度:读键并更新当前菜单结点与路径,必要时刷新显示
|
||||
* 参数: 无
|
||||
* 返回: 无
|
||||
* 说明: 1. 首次进入时只调用 Menu_Show_Proc 显示当前路径
|
||||
* 2. 上/下键同级移动;左键退上一级或上一列 0 级;右键/确认进入下级或执行叶子结点
|
||||
* 3. ESC 在 1 级退回“主界面”并重算布局;在 2~3 级仅退上一级
|
||||
* 4. 键值使用 key.h 中的 KEY_U/KEY_D/KEY_L/KEY_R/KEY_ENT/KEY_ESC/KEY_F1/KEY_F2
|
||||
*****************************************************************************/
|
||||
void Menu_Route(void)
|
||||
{
|
||||
tagPMenuItem ptCurrent; /* 当前光标所在菜单结点(可能被按键修改) */
|
||||
tagPMenuItem ptHead; /* 0 级菜单头结点,用于 ESC 退回与路径重置 */
|
||||
tagPMenuItem ptIndex; /* 路径回溯时的遍历指针 */
|
||||
tagPMenuItem *ptRoute; /* 指向 g_tMenuCtrl.ptRoute,便于按层级写入路径 */
|
||||
|
||||
uint8_t byKeyVal; /* 本次读到的键值(KEY_U/KEY_D/KEY_L/KEY_R/KEY_ENT/KEY_ESC 等) */
|
||||
uint8_t bNeedFresh; /* 本次按键是否导致需要重绘菜单(1=是,0=否) */
|
||||
|
||||
bNeedFresh = 0; /* 默认不刷新,仅当按键改变 ptCurrent 时置 1 */
|
||||
|
||||
ptCurrent = g_tMenuCtrl.ptCurrent; /* 取出当前选中的菜单结点 */
|
||||
ptHead = g_tMenuCtrl.ptHead; /* 取出 0 级头结点 */
|
||||
ptRoute = g_tMenuCtrl.ptRoute; /* 取出路径数组指针,用于更新 ptRoute[0..3] */
|
||||
|
||||
if (g_tDspCtrl.bFirst) /* 第一次进入菜单层:不读键,直接按当前路径刷新显示 */
|
||||
{
|
||||
Menu_Show_Proc(); /* 内部会清 bFirst 并绘制 0~当前级菜单 */
|
||||
}
|
||||
else /* 非首次:读键并根据键值更新 ptCurrent */
|
||||
{
|
||||
byKeyVal = Key_Read(); /* 从 Key_Read 获取键值(无键时为 KEY_NONE) */
|
||||
switch (byKeyVal)
|
||||
{
|
||||
case KEY_F1: /* F1 当前项目未使用,忽略 */
|
||||
break;
|
||||
case KEY_F2: /* F2 同上 */
|
||||
break;
|
||||
|
||||
case KEY_U: /* 上键:切换到同级上一项(ptBefore) */
|
||||
ptCurrent = ptCurrent->ptBefore;
|
||||
bNeedFresh = 1;
|
||||
break;
|
||||
|
||||
case KEY_D: /* 下键:切换到同级下一项(ptBehind) */
|
||||
ptCurrent = ptCurrent->ptBehind;
|
||||
bNeedFresh = 1;
|
||||
break;
|
||||
|
||||
case KEY_L: /* 左键:退到上一级路径结点,若已在 0 级则退到上一列 0 级再进其下级 */
|
||||
ptCurrent = ptRoute[ptCurrent->byClass - 1]; /* 先退到上一级路径结点 */
|
||||
if (0 == ptCurrent->byClass) /* 若退完后是 0 级,则再退一列或两列并进下级 */
|
||||
{
|
||||
if (1 == ptCurrent->wPos) /* 当前在第 1 列:退两列再进下级 */
|
||||
{
|
||||
ptCurrent = ptCurrent->ptBefore->ptBefore;
|
||||
ptCurrent = ptCurrent->ptLower;
|
||||
}
|
||||
else /* 其它列:退一列再进下级 */
|
||||
{
|
||||
ptCurrent = ptCurrent->ptBefore;
|
||||
ptCurrent = ptCurrent->ptLower;
|
||||
}
|
||||
}
|
||||
bNeedFresh = 1;
|
||||
break;
|
||||
|
||||
case KEY_R: /* 右键与确认键统一处理 */
|
||||
case KEY_ENT:
|
||||
if (ptCurrent->ptLower != NULL) /* 有下级:进入下一层,光标移到下级首项 */
|
||||
{
|
||||
ptCurrent = ptCurrent->ptLower;
|
||||
bNeedFresh = 1;
|
||||
}
|
||||
else /* 无下级(叶子结点):执行该菜单项绑定的处理函数 */
|
||||
{
|
||||
if (ptCurrent->pfnWinProc != NULL)
|
||||
{
|
||||
ptCurrent->pfnWinProc(); /* 如 MenuProc_See_AppInfo、MenuProc_See_YC 等 */
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY_ESC: /* 退出键 */
|
||||
if (1 == ptCurrent->byClass) /* 当前在 1 级:整体退出到“主界面”,重置 0 级路径与布局 */
|
||||
{
|
||||
g_tMenuCtrl.pt0Level = ptHead; /* 0 级路径起点设为头结点 */
|
||||
g_tMenuCtrl.ptRoute[0] = ptHead; /* 路径[0] 指向 0 级头 */
|
||||
g_tMenuCtrl.ptCurrent = ptHead->ptLower; /* 当前结点设为 0 级第一个子项 */
|
||||
ptCurrent = g_tMenuCtrl.ptCurrent;
|
||||
g_tMenuCtrl.ptCurBak = ptCurrent; /* 备份当前结点 */
|
||||
g_tMenuCtrl.ptRoute[1] = ptCurrent; /* 路径[1] 指向该 1 级项 */
|
||||
g_tMenuCtrl.byLeftMove = 0; /* 左移偏移清零 */
|
||||
return; /* 直接返回,本次不再调用 Menu_Show_Proc(由下次 Menu_Route 或外部刷新) */
|
||||
}
|
||||
else
|
||||
{ /* 当前在 2 或 3 级:仅退到上一级路径结点 */
|
||||
ptCurrent = ptRoute[ptCurrent->byClass - 1];
|
||||
bNeedFresh = 1;
|
||||
}
|
||||
break;
|
||||
default: /* 其它键值(含 KEY_NONE)忽略 */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bNeedFresh) /* 若按键导致菜单位置变化,则更新全局并刷新显示 */
|
||||
{
|
||||
g_tMenuCtrl.ptCurrent = ptCurrent; /* 更新当前选中的菜单项 */
|
||||
if (g_tMenuCtrl.ptCurBak != ptCurrent) /* 当前菜单项相对上次备份有变化,需更新路径数组 */
|
||||
{
|
||||
ptIndex = g_tMenuCtrl.ptCurrent;
|
||||
for (uint16_t index = 0; index < MENU_MAX_ITEM; index++) /* 从当前结点沿 ptHigher 回溯到 0 级 */
|
||||
{
|
||||
if (NULL == ptIndex->ptHigher) /* 无上级(不应出现,保险起见退到 ptBefore) */
|
||||
{
|
||||
ptIndex = ptIndex->ptBefore;
|
||||
}
|
||||
else /* 有上级:先回到上级,再将该级路径记录到 ptRoute[byClass] */
|
||||
{
|
||||
ptIndex = ptIndex->ptHigher;
|
||||
g_tMenuCtrl.ptRoute[ptIndex->byClass] = ptIndex;
|
||||
}
|
||||
if (0 == ptIndex->byClass) /* 已回溯到 0 级,结束 */
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_tMenuCtrl.ptRoute[g_tMenuCtrl.ptCurrent->byClass] = ptCurrent; /* 当前所在层路径指向新结点 */
|
||||
}
|
||||
#ifdef DEBUG
|
||||
printf("Menu_Route: g_tMenuCtrl.ptCurrent.byClass = %d\n", g_tMenuCtrl.ptCurrent->byClass);
|
||||
printf("Menu_Route: g_tMenuCtrl.ptCurBak.byClass = %d\n", g_tMenuCtrl.ptCurBak->byClass);
|
||||
for (uint16_t index = 0; index < 4; index++)
|
||||
{
|
||||
printf("Menu_Route: g_tMenuCtrl.ptRoute[%d].byName = %s\n", index, g_tMenuCtrl.ptRoute[index]->byName);
|
||||
}
|
||||
#endif
|
||||
Menu_Show_Proc(); /* 按新路径刷新 0~当前级菜单显示 */
|
||||
}
|
||||
}
|
||||
void Menu_Init(void)
|
||||
{
|
||||
Lcd_Init();
|
||||
/* 初始化显示控制结构 */
|
||||
memset(&g_tDspCtrl, 0, sizeof(g_tDspCtrl));
|
||||
g_tDspCtrl.bFirst = 1;
|
||||
|
||||
Menu_0LevelNumCal(); /* 统计 0 级菜单项个数 */
|
||||
Menu_Main_Creat(); /* 创建菜单树 */
|
||||
@@ -787,14 +1043,14 @@ void Menu_Init(void)
|
||||
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.pt0Level = g_tMenuCtrl.ptRoute[0]; /* 供 Menu_Show_Proc 判断 0 级路径是否变化 */
|
||||
g_tMenuCtrl.ptCurrent = g_tMenuCtrl.ptHead->ptLower; /* 菜单当前指针初始化为0级第一个子项 */
|
||||
g_tMenuCtrl.ptCurBak = g_tMenuCtrl.ptCurrent; /* 菜单备份指针初始化 */
|
||||
g_tMenuCtrl.ptRoute[1] = g_tMenuCtrl.ptCurrent; // 1级路径初始化
|
||||
g_tMenuCtrl.ptRoute[2] = g_tMenuCtrl.ptCurrent; // 1级路径初始化
|
||||
g_tMenuCtrl.ptRoute[3] = g_tMenuCtrl.ptCurrent; // 1级路径初始化
|
||||
g_tMenuCtrl.byLeftMove = 0;
|
||||
g_tMenuCtrl.bySecPage = 0;
|
||||
|
||||
Menu_Show_0Level(); /* 显示 0 级菜单 */
|
||||
Menu_Show_Other(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,43 +12,25 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lcd.h" /* MENU_YMAX 依赖 LCD_SIZE_Y */
|
||||
|
||||
|
||||
#define CN_HEIGHT 12 // 菜单汉字高
|
||||
#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_XADD 4 // 菜单框横坐标增加
|
||||
#define MENU_YADD 4 // 菜单框纵坐标增加
|
||||
#define MENU_WITDTH 7 /*ASCII字体宽度 7,因此单个字节显示的宽度是 7 个像素点*/
|
||||
|
||||
|
||||
|
||||
#define MENU_YMIN 0 // 菜单Y坐标的最小值
|
||||
#define MENU_YMAX (LCD_SIZE_Y - 16) // 菜单Y坐标的最大值
|
||||
#define MENU_YMIN 0 // 菜单 Y 坐标的最小值 顶部边界 0
|
||||
#define MENU_YMAX (LCD_SIZE_Y - 12) // 菜单 Y 坐标的最大值 底部边界 LCD_SIZE_Y - 16
|
||||
|
||||
typedef struct MenuItem MenuItem;
|
||||
|
||||
struct MenuItem {
|
||||
const char *name; /* 菜单名称 */
|
||||
const char *tip; /* 底部提示信息 */
|
||||
|
||||
MenuItem *higher; /* 上级菜单 */
|
||||
MenuItem *lower; /* 下级第一个菜单 */
|
||||
MenuItem *before; /* 同级上一个 */
|
||||
MenuItem *behind; /* 同级下一个 */
|
||||
|
||||
uint8_t level; /* 菜单级别:0/1/2/... */
|
||||
uint8_t pos; /* 在同级菜单中的序号(从 0 开始) */
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
MenuItem *head0; /* 0 级菜单头结点 */
|
||||
MenuItem *current; /* 当前选中的菜单结点 */
|
||||
} MenuCtrl;
|
||||
|
||||
/* 初始化一棵简单的测试菜单树 */
|
||||
void Menu_Init();
|
||||
void Menu_Show_0Level();
|
||||
void Menu_Route(void);
|
||||
void Menu_Show_Proc(void);
|
||||
|
||||
/* 非功能键处理 */
|
||||
void Menu_NonPfunc();
|
||||
|
||||
68
src/main.c
68
src/main.c
@@ -30,56 +30,18 @@ static int getch(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 菜单:MenuItem、MenuCtrl、Menu_InitPC、Menu_LoopStep、Menu_MapKey、KEY_* */
|
||||
#include "Drv/menu.h"
|
||||
/* TCP:Tcp_Init/Cleanup、TcpServer_Listen/Accept/Close、TcpClient_Send/Recv/Close */
|
||||
#include "TCP/tcp.h"
|
||||
/* 线程:Thread_Create、Thread_Join、thread_handle_t */
|
||||
#include "thread_utils.h"
|
||||
/* 远程显示 / TCP 服务器线程:server_thread_arg_t、tcp_server_thread_fn */
|
||||
#include "remoteDisplay.h"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* 启动 TCP 服务与服务器线程(Tcp_Init + 创建线程 + 短暂等待就绪)
|
||||
* port: 监听端口(如 7070)
|
||||
* out_server_sock: 输出监听 socket,供主线程退出时 TcpServer_Close
|
||||
* out_server_quit: 输出退出标志,主线程置 1 通知服务器线程退出
|
||||
* out_server_th: 输出线程句柄,供主线程 Thread_Join
|
||||
* 返回:0 成功,1 失败(已调用 Tcp_Cleanup)
|
||||
* ---------------------------------------------------------------------------- */
|
||||
static int StartTcpServerThread(uint16_t port,
|
||||
int *out_server_sock,
|
||||
volatile int *out_server_quit,
|
||||
thread_handle_t *out_server_th)
|
||||
{
|
||||
server_thread_arg_t server_arg;
|
||||
|
||||
if (Tcp_Init() != 0) {
|
||||
fprintf(stderr, "Tcp_Init failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
*out_server_sock = TCP_INVALID_SOCKET;
|
||||
*out_server_quit = 0;
|
||||
server_arg.port = port;
|
||||
server_arg.p_server_sock = out_server_sock;
|
||||
server_arg.p_quit = out_server_quit;
|
||||
|
||||
if (Thread_Create(tcp_server_thread_fn, &server_arg, out_server_th) != 0) {
|
||||
fprintf(stderr, "Thread_Create(server) failed\n");
|
||||
Tcp_Cleanup();
|
||||
return 1;
|
||||
}
|
||||
Sleep(200);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#include "Drv/key.h"
|
||||
#include "thread_utils.h"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* 程序入口
|
||||
* ---------------------------------------------------------------------------- */
|
||||
int main(void)
|
||||
{
|
||||
uint8_t count = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
/* Windows:将控制台代码页设为 UTF-8,避免中文乱码(如“监听”“退出”等) */
|
||||
@@ -89,18 +51,36 @@ int main(void)
|
||||
int server_sock;
|
||||
volatile int server_quit;
|
||||
thread_handle_t server_th;
|
||||
server_thread_arg_t server_arg;
|
||||
|
||||
|
||||
server_sock = TCP_INVALID_SOCKET;
|
||||
server_quit = 0;
|
||||
|
||||
server_arg.port = 7003;
|
||||
server_arg.p_server_sock = &server_sock;
|
||||
server_arg.p_quit = &server_quit;
|
||||
|
||||
printf("开始初始化菜单树...\n");
|
||||
|
||||
Menu_Init(); /* 初始化菜单树(运行界面/定值设置/查看数据等) */
|
||||
Key_Init(); /* 初始化按键 */
|
||||
printf("PC 端 HMI 菜单模拟启动(TCP 服务在单独线程,端口 7003)。\n");
|
||||
/* 7003 为 RemoDispBus 默认端口,与 remo_disp_server.py 一致 */
|
||||
if (StartTcpServerThread(7003, &server_sock, &server_quit, &server_th) != 0)
|
||||
if (StartTcpServerThread(&server_th, &server_arg) != 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
while(1) {
|
||||
|
||||
while(1)
|
||||
{
|
||||
Menu_Route();
|
||||
Sleep(20);
|
||||
count++;
|
||||
if(count > 50)
|
||||
{
|
||||
count = 0;
|
||||
Menu_Show_Proc();
|
||||
}
|
||||
}
|
||||
|
||||
server_quit = 1; /* 通知服务器线程退出:下一轮 while 或 Accept 返回后会结束循环 */
|
||||
|
||||
@@ -8,11 +8,10 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
#include "remoteDisplay.h"
|
||||
#include "Drv/lcd.h"
|
||||
#include "TCP/tcp.h"
|
||||
|
||||
#include "Drv/key.h"
|
||||
#include "remoteDisplay.h"
|
||||
|
||||
/* RemoDispBus 协议常量(与 remo_disp_server.py 一致) */
|
||||
#define TAG_CLIENT 0xAAu
|
||||
@@ -81,7 +80,6 @@ static void handle_cmd_lcdmem(int client, const uint8_t* req_data, unsigned int
|
||||
{
|
||||
unsigned int start_addr = 0;
|
||||
|
||||
printf("handle_cmd_lcdmem\n");
|
||||
if (req_len >= 4)
|
||||
start_addr = ((unsigned int)req_data[0] << 24) | ((unsigned int)req_data[1] << 16)
|
||||
| ((unsigned int)req_data[2] << 8) | req_data[3];
|
||||
@@ -94,11 +92,14 @@ static void handle_cmd_lcdmem(int client, const uint8_t* req_data, unsigned int
|
||||
payload[3] = start_addr & 0xFF;
|
||||
|
||||
unsigned int copy_len = LCD_DISPLAYMEMORYSIZE;
|
||||
if (start_addr < LCD_DISPLAYMEMORYSIZE) {
|
||||
if (start_addr < LCD_DISPLAYMEMORYSIZE)
|
||||
{
|
||||
unsigned int offset = start_addr;
|
||||
copy_len = LCD_DISPLAYMEMORYSIZE - offset;
|
||||
memcpy(payload + 4, g_tCVsr.pwbyLCDMemory + offset, copy_len);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
copy_len = 0;
|
||||
}
|
||||
send_reply(client, CMD_LCDMEM, payload, 4 + copy_len);
|
||||
@@ -122,9 +123,11 @@ static void handle_cmd_init(int client)
|
||||
/* 处理 CMD_KEY:可选转交菜单,此处仅回 ACK */
|
||||
static void handle_cmd_key(int client, const uint8_t* data, unsigned int len)
|
||||
{
|
||||
(void)data;
|
||||
(void)len;
|
||||
send_reply(client, CMD_KEY, (const uint8_t*)NULL, 0);
|
||||
#ifdef DEBUG
|
||||
printf("handle_cmd_key: 0x%02X\n", data[0]);
|
||||
#endif
|
||||
g_tRemoteKey.byKeyValid = EN_KEY_FLAG_NEW;
|
||||
g_tRemoteKey.byKeyValue = data[0];
|
||||
}
|
||||
|
||||
/* 处理 CMD_KEEPLIVE:保活,回空 */
|
||||
@@ -145,14 +148,15 @@ void tcp_server_thread_fn(void* arg)
|
||||
printf("[TCP Server] RemoDispBus 监听端口 %d,等待客户端连接...\n", ctx->port);
|
||||
|
||||
#define REMO_BUF_SIZE 4096
|
||||
uint8_t recv_buf[REMO_BUF_SIZE];
|
||||
uint8_t recv_buf[4096];
|
||||
unsigned int recv_len = 0;
|
||||
|
||||
/* ========== 外层循环:主线程未请求退出时,持续等待并接受新客户端 ========== */
|
||||
while (!*ctx->p_quit) {
|
||||
/* 阻塞等待一个客户端连接;主线程关闭 server_sock 时 Accept 会失败并返回 INVALID */
|
||||
int client = TcpServer_Accept(server_sock);
|
||||
if (client == TCP_INVALID_SOCKET) {
|
||||
if (client == TCP_INVALID_SOCKET)
|
||||
{
|
||||
if (*ctx->p_quit)
|
||||
break;
|
||||
continue;
|
||||
@@ -161,13 +165,16 @@ void tcp_server_thread_fn(void* arg)
|
||||
recv_len = 0; /* 新连接对应新的接收缓冲区,避免混用上一连接的残留数据 */
|
||||
|
||||
/* ========== 内层循环:处理当前连接上的 RemoDispBus 请求,直到断开或退出 ========== */
|
||||
while (!*ctx->p_quit) {
|
||||
while (!*ctx->p_quit)
|
||||
{
|
||||
/* ----- 1. 若缓冲区中已有一条完整且校验通过的帧,则解析并分发处理 ----- */
|
||||
uint8_t cmd;
|
||||
const uint8_t* pdata;
|
||||
unsigned int data_len, consume;
|
||||
if (parse_frame(recv_buf, recv_len, &cmd, &pdata, &data_len, &consume)) {
|
||||
switch (cmd) {
|
||||
if (parse_frame(recv_buf, recv_len, &cmd, &pdata, &data_len, &consume))
|
||||
{
|
||||
switch (cmd)
|
||||
{
|
||||
case CMD_KEEPLIVE:
|
||||
handle_cmd_keeplive(client);
|
||||
break;
|
||||
@@ -191,14 +198,17 @@ void tcp_server_thread_fn(void* arg)
|
||||
}
|
||||
|
||||
/* ----- 2. 缓冲区中尚无完整帧:先防止溢出,再收一批数据 ----- */
|
||||
if (recv_len >= REMO_BUF_SIZE - 256) {
|
||||
if (recv_len >= REMO_BUF_SIZE - 256)
|
||||
{
|
||||
recv_len = 0; /* 异常情况:数据过多且始终不成帧,清空缓冲区防止越界 */
|
||||
}
|
||||
int n = TcpClient_Recv(client, (char*)(recv_buf + recv_len), REMO_BUF_SIZE - recv_len - 1);
|
||||
if (n > 0) {
|
||||
if (n > 0)
|
||||
{
|
||||
recv_len += (unsigned int)n;
|
||||
printf("recv_len = %d\n", recv_len);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* n==0 表示对方正常关闭连接,n<0 表示读取出错;均退出本连接循环 */
|
||||
printf("[TCP Server] 客户端关闭连接\n");
|
||||
break;
|
||||
@@ -212,3 +222,27 @@ void tcp_server_thread_fn(void* arg)
|
||||
*ctx->p_server_sock = TCP_INVALID_SOCKET;
|
||||
printf("[TCP Server] 已退出\n");
|
||||
}
|
||||
/* ----------------------------------------------------------------------------
|
||||
* 启动 TCP 服务与服务器线程(Tcp_Init + 创建线程 + 短暂等待就绪)
|
||||
* port: 监听端口(如 7070)
|
||||
* out_server_sock: 输出监听 socket,供主线程退出时 TcpServer_Close
|
||||
* out_server_quit: 输出退出标志,主线程置 1 通知服务器线程退出
|
||||
* out_server_th: 输出线程句柄,供主线程 Thread_Join
|
||||
* 返回:0 成功,1 失败(已调用 Tcp_Cleanup)
|
||||
* ---------------------------------------------------------------------------- */
|
||||
int StartTcpServerThread(thread_handle_t *out_server_th, server_thread_arg_t *io_server_arg)
|
||||
{
|
||||
if (Tcp_Init() != 0) {
|
||||
fprintf(stderr, "Tcp_Init failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (Thread_Create(tcp_server_thread_fn, io_server_arg, out_server_th) != 0) {
|
||||
fprintf(stderr, "Thread_Create(server) failed\n");
|
||||
Tcp_Cleanup();
|
||||
return 1;
|
||||
}
|
||||
Sleep(200);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#ifndef REMOTE_DISPLAY_H
|
||||
#define REMOTE_DISPLAY_H
|
||||
#ifndef __REMOTEDISPLAY_H
|
||||
#define __REMOTEDISPLAY_H
|
||||
|
||||
|
||||
|
||||
#include "thread_utils.h"
|
||||
|
||||
/*
|
||||
* 远程显示 / TCP 服务器相关接口
|
||||
@@ -14,8 +18,7 @@ typedef struct {
|
||||
volatile int* p_quit; /* 主线程置 1 通知线程退出 */
|
||||
} server_thread_arg_t;
|
||||
|
||||
/* TCP 服务器线程函数:在独立线程中监听端口并处理客户端收/发 */
|
||||
void tcp_server_thread_fn(void* arg);
|
||||
int StartTcpServerThread(thread_handle_t *out_server_th, server_thread_arg_t *io_server_arg);
|
||||
|
||||
#endif /* REMOTE_DISPLAY_H */
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user