将菜单的架构改成 MVP,并且进一步优化视图层和模型层的逻辑
This commit is contained in:
85
src/Drv/pages/global/global_state.c
Normal file
85
src/Drv/pages/global/global_state.c
Normal file
@@ -0,0 +1,85 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "global_state.h"
|
||||
|
||||
static GlobalModel s_globalModel;
|
||||
|
||||
static void GlobalModel_CopyText(uint8_t *dst, uint16_t dstSize, const uint8_t *src)
|
||||
{
|
||||
uint16_t i;
|
||||
if ((dst == NULL) || (dstSize == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (src == NULL)
|
||||
{
|
||||
dst[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < (uint16_t)(dstSize - 1); i++)
|
||||
{
|
||||
dst[i] = src[i];
|
||||
if (src[i] == '\0')
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
dst[dstSize - 1] = '\0';
|
||||
}
|
||||
|
||||
void GlobalModel_Init(void)
|
||||
{
|
||||
memset(&s_globalModel, 0, sizeof(s_globalModel));
|
||||
}
|
||||
|
||||
GlobalModel *GlobalModel_Instance(void)
|
||||
{
|
||||
return &s_globalModel;
|
||||
}
|
||||
|
||||
void GlobalModel_SetActiveYcType(uint16_t ycType)
|
||||
{
|
||||
s_globalModel.activeYcType = ycType;
|
||||
}
|
||||
|
||||
uint16_t GlobalModel_GetActiveYcType(void)
|
||||
{
|
||||
return s_globalModel.activeYcType;
|
||||
}
|
||||
|
||||
void GlobalModel_SetMenuAction(uint16_t actionId,
|
||||
const uint8_t *menuName,
|
||||
const uint8_t *menuTip,
|
||||
uint16_t menuPara)
|
||||
{
|
||||
s_globalModel.activeActionId = actionId;
|
||||
s_globalModel.activeMenuPara = menuPara;
|
||||
GlobalModel_CopyText(s_globalModel.activeMenuName,
|
||||
(uint16_t)sizeof(s_globalModel.activeMenuName),
|
||||
menuName);
|
||||
GlobalModel_CopyText(s_globalModel.activeMenuTip,
|
||||
(uint16_t)sizeof(s_globalModel.activeMenuTip),
|
||||
menuTip);
|
||||
}
|
||||
|
||||
uint16_t GlobalModel_GetMenuActionId(void)
|
||||
{
|
||||
return s_globalModel.activeActionId;
|
||||
}
|
||||
|
||||
uint16_t GlobalModel_GetMenuActionPara(void)
|
||||
{
|
||||
return s_globalModel.activeMenuPara;
|
||||
}
|
||||
|
||||
const uint8_t *GlobalModel_GetMenuActionName(void)
|
||||
{
|
||||
return s_globalModel.activeMenuName;
|
||||
}
|
||||
|
||||
const uint8_t *GlobalModel_GetMenuActionTip(void)
|
||||
{
|
||||
return s_globalModel.activeMenuTip;
|
||||
}
|
||||
71
src/Drv/pages/global/global_state.h
Normal file
71
src/Drv/pages/global/global_state.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#ifndef PAGE_GLOBAL_STATE_H
|
||||
#define PAGE_GLOBAL_STATE_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
MENU_ACTION_NONE = 0,
|
||||
MENU_ACTION_SET_VALUE,
|
||||
MENU_ACTION_SET_SOFT,
|
||||
MENU_ACTION_CFG_TIME,
|
||||
MENU_ACTION_CFG_REV_EVENT,
|
||||
MENU_ACTION_CFG_MANUAL_WAVE,
|
||||
MENU_ACTION_SEE_INPUT,
|
||||
MENU_ACTION_SEE_SET,
|
||||
MENU_ACTION_SEE_SOFT,
|
||||
MENU_ACTION_YX_SET_COMM_TYPE,
|
||||
MENU_ACTION_YX_SET_WIDTH,
|
||||
MENU_ACTION_YX_SET_TWIN,
|
||||
MENU_ACTION_ALLINF_DEFAULT,
|
||||
MENU_ACTION_PARA_DEFAULT,
|
||||
MENU_ACTION_SET_DEFAULT,
|
||||
MENU_ACTION_RESUME_SOFT,
|
||||
MENU_ACTION_CFG_CELL_DEF,
|
||||
MENU_ACTION_CFG_SHOW_ANA_TYPE,
|
||||
MENU_ACTION_DBG_XU_YX,
|
||||
MENU_ACTION_DBG_XU_YC,
|
||||
MENU_ACTION_DBG_XU_EVENT,
|
||||
MENU_ACTION_DBG_RELAY,
|
||||
MENU_ACTION_SEE_VERSION_BOARD,
|
||||
MENU_ACTION_CFG_CLR_REC,
|
||||
MENU_ACTION_CFG_COM_PARA,
|
||||
MENU_ACTION_CFG_EDIT_IP,
|
||||
MENU_ACTION_CFG_EDIT_SNTP,
|
||||
MENU_ACTION_CFG_CELL_CONF,
|
||||
MENU_ACTION_SEE_REC_SOE,
|
||||
MENU_ACTION_SEE_REC_ACT,
|
||||
MENU_ACTION_SEE_REC_OPT,
|
||||
MENU_ACTION_SEE_REC_ALM,
|
||||
MENU_ACTION_SEE_REC_START,
|
||||
MENU_ACTION_SEE_REC_YK,
|
||||
MENU_ACTION_SEE_REC_CHK,
|
||||
MENU_ACTION_SEE_REC_RUN,
|
||||
MENU_ACTION_SEE_REC_FAULT,
|
||||
MENU_ACTION_YC_SET_SQ_VALUE,
|
||||
MENU_ACTION_YC_SET_ADJ_COE
|
||||
} menu_action_id_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t activeYcType;
|
||||
uint16_t activeMenuPara;
|
||||
uint16_t activeActionId;
|
||||
uint8_t activeMenuName[50];
|
||||
uint8_t activeMenuTip[50];
|
||||
} GlobalModel;
|
||||
|
||||
void GlobalModel_Init(void);
|
||||
GlobalModel *GlobalModel_Instance(void);
|
||||
void GlobalModel_SetActiveYcType(uint16_t ycType);
|
||||
uint16_t GlobalModel_GetActiveYcType(void);
|
||||
void GlobalModel_SetMenuAction(uint16_t actionId,
|
||||
const uint8_t *menuName,
|
||||
const uint8_t *menuTip,
|
||||
uint16_t menuPara);
|
||||
uint16_t GlobalModel_GetMenuActionId(void);
|
||||
uint16_t GlobalModel_GetMenuActionPara(void);
|
||||
const uint8_t *GlobalModel_GetMenuActionName(void);
|
||||
const uint8_t *GlobalModel_GetMenuActionTip(void);
|
||||
|
||||
#endif
|
||||
44
src/Drv/pages/global/renderer_lcd.c
Normal file
44
src/Drv/pages/global/renderer_lcd.c
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "renderer_lcd.h"
|
||||
|
||||
#include "lcd.h"
|
||||
#include "lcd_draw.h"
|
||||
#include "lcd_text.h"
|
||||
|
||||
static unsigned short PageRenderer_LcdSizeX(void)
|
||||
{
|
||||
return LCD_SIZE_X;
|
||||
}
|
||||
|
||||
static unsigned short PageRenderer_LcdSizeY(void)
|
||||
{
|
||||
return LCD_SIZE_Y;
|
||||
}
|
||||
|
||||
static unsigned char PageRenderer_LcdColorFont(void)
|
||||
{
|
||||
return LCD_FONT;
|
||||
}
|
||||
|
||||
static unsigned char PageRenderer_LcdColorBack(void)
|
||||
{
|
||||
return LCD_BACK;
|
||||
}
|
||||
|
||||
static const PageRenderPort g_lcd_port = {
|
||||
.get_size_x = PageRenderer_LcdSizeX,
|
||||
.get_size_y = PageRenderer_LcdSizeY,
|
||||
.get_color_font = PageRenderer_LcdColorFont,
|
||||
.get_color_back = PageRenderer_LcdColorBack,
|
||||
.fill_rect = Lcd_FillRect,
|
||||
.line_h = Lcd_LineH,
|
||||
.line_v = Lcd_LineV,
|
||||
.line = Lcd_Line,
|
||||
.set_pixel = Lcd_SetPixel,
|
||||
.invert = Lcd_Invert,
|
||||
.show_str = Lcd_ShowStr,
|
||||
};
|
||||
|
||||
const PageRenderPort *PageRenderer_Lcd(void)
|
||||
{
|
||||
return &g_lcd_port;
|
||||
}
|
||||
21
src/Drv/pages/global/renderer_lcd.h
Normal file
21
src/Drv/pages/global/renderer_lcd.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef PAGE_RENDERER_LCD_H
|
||||
#define PAGE_RENDERER_LCD_H
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned short (*get_size_x)(void);
|
||||
unsigned short (*get_size_y)(void);
|
||||
unsigned char (*get_color_font)(void);
|
||||
unsigned char (*get_color_back)(void);
|
||||
signed char (*fill_rect)(unsigned short left_x, unsigned short top_y, unsigned short right_x, unsigned short bottom_y, unsigned int color);
|
||||
signed char (*line_h)(unsigned short x_start, unsigned short x_end, unsigned short y, unsigned short width, unsigned char color);
|
||||
signed char (*line_v)(unsigned short y_start, unsigned short y_end, unsigned short x, unsigned short width, unsigned char color);
|
||||
signed char (*line)(unsigned short x_start, unsigned short y_start, unsigned short x_end, unsigned short y_end, unsigned short width, unsigned char color);
|
||||
signed char (*set_pixel)(unsigned short x, unsigned short y, unsigned char color);
|
||||
signed char (*invert)(unsigned short left_x, unsigned short top_y, unsigned short right_x, unsigned short bottom_y);
|
||||
signed char (*show_str)(unsigned short x, unsigned short y, unsigned char *text);
|
||||
} PageRenderPort;
|
||||
|
||||
const PageRenderPort *PageRenderer_Lcd(void);
|
||||
|
||||
#endif
|
||||
145
src/Drv/pages/menu/def.h
Normal file
145
src/Drv/pages/menu/def.h
Normal file
@@ -0,0 +1,145 @@
|
||||
#ifndef MENU_def_H
|
||||
#define MENU_def_H
|
||||
|
||||
/* layout constants are unified into menu_view */
|
||||
#define MENU_SIZE_Y (160)
|
||||
#define MENU_SIZE_X (160)
|
||||
#define CN_HEIGHT 12
|
||||
#define CN_ROWSPACE 2
|
||||
#define LINE_HEIGHT (CN_HEIGHT + CN_ROWSPACE)
|
||||
#define MENU_XADD 4
|
||||
#define MENU_YADD 4
|
||||
#define MENU_WITDTH 7
|
||||
#define MENU_YMIN 0
|
||||
#define MENU_YMAX (MENU_SIZE_Y - CN_HEIGHT)
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 枚举名: MenuMode
|
||||
* 功能:
|
||||
* 定义菜单页顶部状态栏显示所使用的运行模式。
|
||||
*
|
||||
* 枚举值说明:
|
||||
* MODE_NONE - 无模式/默认模式
|
||||
* MODE_OVERFLOW_PROTECTION - 过流保护模式
|
||||
* MODE_LOCAL_FEEDER_SEGMENT - 就地馈线分段模式
|
||||
* MODE_LOCAL_FEEDER_CONTACT - 就地馈线联络模式
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef enum
|
||||
{
|
||||
MODE_NONE = 0,
|
||||
MODE_OVERFLOW_PROTECTION = 1,
|
||||
MODE_LOCAL_FEEDER_SEGMENT = 2,
|
||||
MODE_LOCAL_FEEDER_CONTACT = 3
|
||||
} MenuMode;
|
||||
|
||||
typedef struct MenuItem MenuItem;
|
||||
typedef MenuItem *tagPMenuItem;
|
||||
typedef MenuItem tagMenuItem;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: tagMenuModel
|
||||
* 功能:
|
||||
* 菜单项的静态业务定义数据(名称、属性、参数与动作入口)。
|
||||
*
|
||||
* 字段说明:
|
||||
* byClass - 菜单层级(0 表示根层,其它值表示子层级深度)
|
||||
* byName - 菜单显示名称(UTF-8 字节数组)
|
||||
* byTip - 菜单提示信息(UTF-8 字节数组)
|
||||
* byAttrib - 菜单属性标志(读写权限/类型等)
|
||||
* wPassword - 菜单访问密码或权限码
|
||||
* wPara - 菜单动作参数(业务扩展字段)
|
||||
* pfnWinProc - 菜单动作回调函数(叶子节点触发)
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef struct tagMenuModel
|
||||
{
|
||||
uint8_t byClass;
|
||||
uint8_t byName[50];
|
||||
uint8_t byTip[50];
|
||||
uint8_t byAttrib;
|
||||
uint16_t wPassword;
|
||||
uint16_t wPara;
|
||||
FUNCPTR pfnWinProc;
|
||||
} tagMenuModel;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: MenuLinks
|
||||
* 功能:
|
||||
* 菜单运行时节点的四向链路关系,描述父子与同层前后关系。
|
||||
*
|
||||
* 字段说明:
|
||||
* higher - 指向父节点
|
||||
* lower - 指向第一个子节点
|
||||
* before - 指向同层前一个节点
|
||||
* behind - 指向同层后一个节点
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef struct
|
||||
{
|
||||
MenuItem *higher;
|
||||
MenuItem *lower;
|
||||
MenuItem *before;
|
||||
MenuItem *behind;
|
||||
} MenuLinks;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: MenuRect
|
||||
* 功能:
|
||||
* 菜单项/菜单框的布局计算结果,供 View 层绘制与反显定位使用。
|
||||
*
|
||||
* 字段说明:
|
||||
* wPos - 当前项在本层中的行号(从 0 开始)
|
||||
* wNum - 当前层可见项数量
|
||||
* wSPosX - 绘制区域左上角 X
|
||||
* wSPosY - 绘制区域左上角 Y
|
||||
* wEPosX - 绘制区域右下角 X
|
||||
* wEPosY - 绘制区域右下角 Y
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef struct
|
||||
{
|
||||
uint16_t wPos; /* 当前项在本层中的行号(从 0 开始) */
|
||||
uint16_t wNum;
|
||||
uint16_t wSPosX;
|
||||
uint16_t wSPosY;
|
||||
uint16_t wEPosX;
|
||||
uint16_t wEPosY;
|
||||
} MenuRect;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: MenuItem
|
||||
* 功能:
|
||||
* 菜单运行时节点,聚合链路关系、业务定义与布局结果。
|
||||
*
|
||||
* 字段说明:
|
||||
* links - 节点拓扑关系(父子/同层)
|
||||
* menuDef - 节点业务定义(名称、属性、回调等)
|
||||
* rect - 节点绘制位置与层级布局信息
|
||||
* ------------------------------------------------------------------------- */
|
||||
struct MenuItem
|
||||
{
|
||||
MenuLinks links;
|
||||
tagMenuModel menuDef;
|
||||
MenuRect rect;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: tagMenuCtrl
|
||||
* 功能:
|
||||
* 菜单导航与渲染过程中的运行时控制上下文(Presenter 持有)。
|
||||
*
|
||||
* 字段说明:
|
||||
* ptHead - 菜单根节点
|
||||
* ptCurrent - 当前选中节点
|
||||
* ptRoute - 当前路径路由(按层级保存各级节点,固定深度 4)
|
||||
* ptCurBak - 上一次选中节点(用于差分刷新)
|
||||
* pt0Level - 当前顶层节点缓存(用于判断是否需要整页刷新)
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef struct
|
||||
{
|
||||
tagPMenuItem ptHead;
|
||||
tagPMenuItem ptCurrent;
|
||||
tagPMenuItem ptRoute[4];
|
||||
tagPMenuItem ptCurBak;
|
||||
tagPMenuItem pt0Level;
|
||||
} tagMenuCtrl;
|
||||
|
||||
#endif
|
||||
138
src/Drv/pages/menu/display.h
Normal file
138
src/Drv/pages/menu/display.h
Normal file
@@ -0,0 +1,138 @@
|
||||
#ifndef __DISPLAY__H__
|
||||
#define __DISPLAY__H__
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#define CN_COP_PWD (321)
|
||||
#define CN_USER_PWD (700)
|
||||
#define CN_SUPER_PWD (620)
|
||||
|
||||
enum _REV_TYPE_
|
||||
{
|
||||
EN_REV_FREE,
|
||||
EN_REV_FORCE
|
||||
};
|
||||
|
||||
enum _SET_SIDE_TYPE_
|
||||
{
|
||||
EN_SIDE_START = 0,
|
||||
EN_SIDE_BASIC,
|
||||
EN_SIDE_DEVINF,
|
||||
EN_SIDE_COP,
|
||||
EN_SIDE_MATRIX,
|
||||
EN_SIDE_ALL,
|
||||
EN_SIDE_HIGH,
|
||||
EN_SIDE_MED1,
|
||||
EN_SIDE_MED2,
|
||||
EN_SIDE_LOW1,
|
||||
EN_SIDE_LOW2,
|
||||
EN_SIDE_LK,
|
||||
EN_SIDE_Z,
|
||||
EN_SIDE_DEF,
|
||||
EN_SIDE_NONE = 0xFF
|
||||
};
|
||||
|
||||
enum _SOFT_TYPE_NUMBER
|
||||
{
|
||||
EN_SOFT_PRO = 0,
|
||||
EN_SOFT_GOOSE,
|
||||
EN_SOFT_MU,
|
||||
EN_SOFT_SWITCH,
|
||||
EN_SOFT_BAK,
|
||||
EN_SOFT_TYPE_END
|
||||
};
|
||||
|
||||
enum _MEA_TYPE_
|
||||
{
|
||||
EN_MEA_RLY,
|
||||
EN_MEA_RLY2,
|
||||
EN_MEA_RLY3,
|
||||
EN_MEA_ANA,
|
||||
EN_MEA_ANA2,
|
||||
EN_MEA_ANA3,
|
||||
EN_MEA_AC,
|
||||
EN_MEA_DC,
|
||||
EN_MEA_SYN,
|
||||
EN_MEA_POWER,
|
||||
EN_MEA_DD,
|
||||
EN_MEA_JLYC,
|
||||
EN_MEA_GEAR,
|
||||
EN_MEA_TQ,
|
||||
EN_MEA_INPUT1,
|
||||
EN_MEA_INPUT2,
|
||||
EN_MEA_INPUT3,
|
||||
EN_INPUT_RLY_ALL,
|
||||
EN_INPUT_RLY_FAULT,
|
||||
EN_INPUT_RLY_OTHER,
|
||||
EN_INPUT_BS_ALL,
|
||||
EN_INPUT_BS_FAULT,
|
||||
EN_INPUT_BS_OTHER,
|
||||
EN_MEA_ADJ,
|
||||
EN_MEA_YX,
|
||||
EN_OUTPUT_TRIP,
|
||||
EN_OUTPUT_SIGN,
|
||||
EN_MEA_LS,
|
||||
EN_MEA_SCRLY
|
||||
};
|
||||
|
||||
enum _INDEX_VALUE_TYPE_
|
||||
{
|
||||
EN_SOFT_SET,
|
||||
EN_FIGURE_SET
|
||||
};
|
||||
|
||||
enum _ANA_PARA_
|
||||
{
|
||||
EN_ANA_0 = 1,
|
||||
EN_ANA_1
|
||||
};
|
||||
|
||||
enum _INPUT_PARA_
|
||||
{
|
||||
EN_INPUT_0 = 1,
|
||||
EN_INPUT_1
|
||||
};
|
||||
|
||||
enum _ANA_TYPE_
|
||||
{
|
||||
EN_TYPE_DIF_CURRENT = 0,
|
||||
EN_TYPE_UNIT_CURRENT,
|
||||
EN_TYPE_UNIT_VOLTAGE
|
||||
};
|
||||
|
||||
enum _NO_USER_PASSWORD_
|
||||
{
|
||||
EN_NO_USER_PWD = 0x55
|
||||
};
|
||||
enum _FACTORY_PASSWORD_
|
||||
{
|
||||
EN_FACTORY_PASSWORD = 0x55
|
||||
};
|
||||
|
||||
enum _REC_TYPE_
|
||||
{
|
||||
EN_ACT_REC = 0,
|
||||
EN_ALM_REC,
|
||||
EN_CHK_REC,
|
||||
EN_SOE_REC,
|
||||
EN_COS_REC,
|
||||
EN_LOCK_REC,
|
||||
EN_OVER_REC,
|
||||
EN_START_REC,
|
||||
EN_RUN_REC,
|
||||
EN_INPUT_REC,
|
||||
EN_ONOFF_REC,
|
||||
EN_OPT_REC,
|
||||
EN_YK_REC,
|
||||
EN_SC_REC,
|
||||
EN_SCSTEPINFO_REC,
|
||||
EN_FAULT_REC,
|
||||
EN_ACTWAVE_REC,
|
||||
EN_STARTWAVE_REC,
|
||||
EN_HANDWAVE_REC,
|
||||
EN_FAULT_NO,
|
||||
EN_ALL_REC = 0xFF,
|
||||
EN_NO_REC = 0xFFFF
|
||||
};
|
||||
|
||||
#endif
|
||||
69
src/Drv/pages/menu/menu_model.h
Normal file
69
src/Drv/pages/menu/menu_model.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef MENU_MODEL_H
|
||||
#define MENU_MODEL_H
|
||||
|
||||
#include "display.h"
|
||||
|
||||
typedef struct tagMenuModel
|
||||
{
|
||||
uint8_t byClass;
|
||||
uint8_t byName[50];
|
||||
uint8_t byTip[50];
|
||||
uint8_t byAttrib;
|
||||
uint16_t wPassword;
|
||||
uint16_t wPara;
|
||||
FUNCPTR pfnWinProc;
|
||||
} tagMenuModel;
|
||||
|
||||
typedef struct MenuItem MenuItem;
|
||||
typedef MenuItem *tagPMenuItem;
|
||||
typedef MenuItem tagMenuItem;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
MenuItem *higher;
|
||||
MenuItem *lower;
|
||||
MenuItem *before;
|
||||
MenuItem *behind;
|
||||
} MenuLinks;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t wPos;
|
||||
uint16_t wNum;
|
||||
uint16_t wSPosX;
|
||||
uint16_t wSPosY;
|
||||
uint16_t wEPosX;
|
||||
uint16_t wEPosY;
|
||||
} MenuRect;
|
||||
|
||||
struct MenuItem
|
||||
{
|
||||
MenuLinks links;
|
||||
tagMenuModel menuDef;
|
||||
MenuRect rect;
|
||||
};
|
||||
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t by0LevelNum;
|
||||
tagPMenuItem ptHead;
|
||||
tagPMenuItem ptCurrent;
|
||||
tagPMenuItem ptRoute[4];
|
||||
tagPMenuItem ptCurBak;
|
||||
tagPMenuItem pt0Level;
|
||||
} tagMenuCtrl;
|
||||
|
||||
typedef struct menu_model_t menu_model_t;
|
||||
|
||||
struct menu_model_t
|
||||
{
|
||||
uint16_t maxItem;
|
||||
tagMenuItem menuItems[300];
|
||||
uint16_t by0LevelNum;
|
||||
};
|
||||
|
||||
void MenuModel_Init(menu_model_t *model);
|
||||
|
||||
#endif
|
||||
891
src/Drv/pages/menu/model.c
Normal file
891
src/Drv/pages/menu/model.c
Normal file
@@ -0,0 +1,891 @@
|
||||
#include "model.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "../../menu/app/menu.h"
|
||||
#include "utf8.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuModel_Utf8LenCal
|
||||
* 功能:
|
||||
* 计算 UTF-8 字符串在当前菜单显示规则下的“显示宽度”。
|
||||
*
|
||||
* 参数:
|
||||
* str - 待计算的 UTF-8 字符串
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针校验,调用方需保证 `str` 非空且为 `\0` 结尾。
|
||||
* - 依赖 `utf8_next()` 逐字符解析 UTF-8 编码;若输入非法,结果由底层解析行为决定。
|
||||
*
|
||||
* 说明:
|
||||
* - 当前显示规则约定:
|
||||
* 1) ASCII/单字节字符宽度记为 1
|
||||
* 2) 多字节 UTF-8 字符(如中文)宽度记为 2
|
||||
* - 本函数返回的是“显示占位宽度”,不是原始字节长度。
|
||||
* - 该结果会被菜单布局函数用于计算菜单框宽度与字符串显示长度。
|
||||
*
|
||||
* 返回值:
|
||||
* - UTF-8 字符串的显示宽度
|
||||
* ------------------------------------------------------------------------- */
|
||||
uint8_t MenuModel_Utf8LenCal(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);
|
||||
strLen += (n > 1) ? 2 : 1;
|
||||
index += n;
|
||||
}
|
||||
return strLen;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuLayout_ItemDisplayLen
|
||||
* 功能:
|
||||
* 补充右箭头并计算单个菜单项最终用于布局的显示宽度。
|
||||
*
|
||||
* 参数:
|
||||
* item - 菜单项节点指针
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针校验,调用方需保证 `item` 有效。
|
||||
* - 菜单名称最大扫描长度固定为 50,与 `byName` 缓冲区容量保持一致。
|
||||
*
|
||||
* 说明:
|
||||
* - 先通过 `MenuModel_Utf8LenCal()` 计算菜单名称本身的显示宽度。
|
||||
* - 若该菜单项存在子节点(`links.lower != NULL`),则该项在界面上通常会显示
|
||||
* 一个“可下钻”装饰符(例如 `\x10` 右箭头)。
|
||||
* - 如果当前名称末尾尚未包含该装饰符,则额外为布局宽度加 1,
|
||||
* 以保证菜单框宽度计算时为装饰符预留空间。
|
||||
* - 本函数只做宽度估算,不修改菜单项内容。
|
||||
*
|
||||
* 返回值:
|
||||
* - 该菜单项的显示宽度
|
||||
* ------------------------------------------------------------------------- */
|
||||
static uint8_t MenuLayout_ItemDisplayLen(const MenuItem *item)
|
||||
{
|
||||
uint8_t displayLen;
|
||||
displayLen = MenuModel_Utf8LenCal((uint8_t *)item->menuDef.byName);
|
||||
if (item->links.lower != NULL)
|
||||
{
|
||||
uint8_t byteLen = 0;
|
||||
while ((byteLen < 50) && (item->menuDef.byName[byteLen] != '\0'))
|
||||
{
|
||||
byteLen++;
|
||||
}
|
||||
if ((byteLen == 0) || (item->menuDef.byName[byteLen - 1] != '\x10'))
|
||||
{
|
||||
displayLen += 1;
|
||||
}
|
||||
}
|
||||
return displayLen;
|
||||
}
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuModel_GetMenuMaxDisplayLen
|
||||
* 功能:
|
||||
* 统计当前层级菜单项的最大显示宽度,并返回当前层级菜单项中的最大显示宽度。
|
||||
*
|
||||
* 参数:
|
||||
* ptFirst - 当前层的首节点指针
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针校验,调用方需保证 `ptFirst` 有效。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数只负责遍历下一层菜单项,并调用 `MenuLayout_ItemDisplayLen()`
|
||||
* 计算每个节点的显示宽度。
|
||||
*
|
||||
* 返回值:
|
||||
* - 当前层级菜单项中的最大显示宽度
|
||||
* ------------------------------------------------------------------------- */
|
||||
uint8_t MenuModel_GetMenuMaxDisplayLen(tagPMenuItem ptFirst)
|
||||
{
|
||||
uint8_t displayLen;
|
||||
uint8_t byMaxLen = 0;
|
||||
tagPMenuItem ptIndex = ptFirst;
|
||||
|
||||
for (uint16_t index = 0; index < MENU_MODEL_MAX_ITEM; index++)
|
||||
{
|
||||
displayLen = MenuLayout_ItemDisplayLen(ptIndex);
|
||||
|
||||
if (byMaxLen < displayLen)
|
||||
{
|
||||
byMaxLen = displayLen;
|
||||
}
|
||||
|
||||
ptIndex = ptIndex->links.behind;
|
||||
if (ptIndex == ptFirst)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return byMaxLen;
|
||||
}
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuLayout_MapMenuPos
|
||||
* 功能:
|
||||
* 根据当前菜单项在本层中的原始序号,以及当前层可显示的最大行数,
|
||||
* 计算该菜单项在菜单框中的实际显示行号。
|
||||
*
|
||||
* 参数:
|
||||
* menuPos - 当前菜单项在本层中的原始位置序号(从 0 开始)
|
||||
* itemNum - 当前层菜单项总数
|
||||
* maxNum - 当前菜单框在可视区域内最多可容纳的显示行数
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做参数合法性校验,调用方需保证 `menuPos / itemNum / maxNum` 有效。
|
||||
* - 若 `itemNum <= maxNum`,说明当前层菜单可完整显示,直接返回原始序号。
|
||||
* - 若 `itemNum > maxNum` 且 `menuPos < maxNum`,说明当前项仍落在可直接显示区域,
|
||||
* 直接返回原始序号。
|
||||
*
|
||||
* 说明:
|
||||
* - 当当前层菜单项总数未超过窗口容量时,菜单项在框内的显示行号与其原始序号一致。
|
||||
* - 当菜单项总数超过窗口容量时,需要把靠后的菜单项映射到有限的可视行范围内。
|
||||
* - 当前实现策略为:
|
||||
* 1) 若当前项仍位于前 `maxNum - 1` 个可显示位置内,则保持不变
|
||||
* 2) 若当前项已落到窗口容量之外,则将其映射到靠近窗口底部的可视区域
|
||||
* - 该函数的作用是为后续 Y 坐标计算提供“菜单框内部显示行号”,
|
||||
* 避免菜单项过多时直接按原始序号计算导致超出可显示范围。
|
||||
*
|
||||
* 返回值:
|
||||
* - 当前菜单项在菜单框中的实际显示行号
|
||||
* ------------------------------------------------------------------------- */
|
||||
static uint16_t MenuLayout_MapMenuPos(uint16_t menuPos, uint16_t itemNum, uint8_t maxNum)
|
||||
{
|
||||
if ((itemNum > maxNum) && (menuPos >= maxNum))
|
||||
{
|
||||
return (uint16_t)(maxNum - (itemNum - menuPos));
|
||||
}
|
||||
return menuPos;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuLayout_TryFlipUp
|
||||
* 功能:
|
||||
* 当子菜单默认向下展开可能越界时,尝试将菜单框改为“向上展开”。
|
||||
*
|
||||
* 参数:
|
||||
* startY - 菜单框起始 Y 坐标(输入/输出)
|
||||
* endY - 菜单框结束 Y 坐标(输入/输出)
|
||||
* itemNum - 当前子菜单项数量
|
||||
* flipMinY - 允许上翻后的最小 Y 边界
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针校验,调用方需保证 `startY / endY` 有效。
|
||||
* - 若根据 `itemNum` 推导出的上翻后起点小于等于 `flipMinY`,
|
||||
* 则认为顶部空间不足,上翻失败。
|
||||
* - 若候选起点超出 `MENU_YMAX`,则视为异常坐标,上翻失败。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数的目标是在“尽量保持父子菜单行对齐”的前提下,
|
||||
* 把原本会向下越界的子菜单框整体上移。
|
||||
* - 计算步骤:
|
||||
* 1) 以当前 `startY` 为锚点,反推出上翻后的候选顶部 `candidateEndY`
|
||||
* 2) 判断该候选顶部是否落在允许范围 `(flipMinY, MENU_YMAX)` 内
|
||||
* 3) 若可上翻,则将 `startY` 更新为候选顶部
|
||||
* 4) 同时使用旧 `startY` 反推出新的 `endY`
|
||||
* - 返回成功后,调用方可直接使用更新后的 `startY / endY`;
|
||||
* 若失败,则由调用方继续执行 clamp/probe 等后备布局策略。
|
||||
*
|
||||
* 返回值:
|
||||
* - 1 : 上翻成功
|
||||
* - 0 : 上翻失败
|
||||
* ------------------------------------------------------------------------- */
|
||||
static uint8_t MenuLayout_TryFlipUp(uint16_t *startY, uint16_t *endY, uint16_t itemNum, uint16_t flipMinY)
|
||||
{
|
||||
uint16_t candidateEndY;
|
||||
uint16_t temp;
|
||||
|
||||
candidateEndY = (uint16_t)(*startY - (itemNum - 1) * LINE_HEIGHT - MENU_YADD);
|
||||
if ((candidateEndY > flipMinY) && (candidateEndY < MENU_YMAX))
|
||||
{
|
||||
temp = *startY;
|
||||
*startY = candidateEndY;
|
||||
*endY = (uint16_t)(temp + LINE_HEIGHT);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuLayout_FallbackProbeUp
|
||||
* 功能:
|
||||
* 当子菜单无法直接放入可视区域时,按“逐行向上试探”的方式寻找可容纳位置。
|
||||
*
|
||||
* 参数:
|
||||
* ptIndex - 当前菜单路径节点数组(输入)
|
||||
* itemNum - 当前子菜单项数量
|
||||
* startY - 菜单框起始 Y 坐标(输出)
|
||||
* endY - 菜单框结束 Y 坐标(输出)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针校验,调用方需保证 `ptIndex / startY / endY` 有效。
|
||||
* - 默认要求 `ptIndex[0]` 已指向 0 级菜单节点,且其 `rect.wSPosY` 已完成布局计算。
|
||||
* - 当试探上移后 `startY` 小于 1 行高度时,直接钳制为 0,避免出现负坐标。
|
||||
* - 若多次上移后仍无法完全放入 `MENU_YMAX` 范围,本函数保留最后一次试探结果,
|
||||
* 由调用方或后续绘制逻辑继续处理。
|
||||
*
|
||||
* 说明:
|
||||
* - 该函数是介于“直接上翻失败”和“强制贴顶”之间的一种更平滑兜底策略。
|
||||
* - 处理步骤:
|
||||
* 1) 先以 0 级菜单顶部 `ptIndex[0]->rect.wSPosY` 作为初始起点
|
||||
* 2) 按 `itemNum * LINE_HEIGHT + MENU_YADD` 计算当前理论底部
|
||||
* 3) 若底部仍超出 `MENU_YMAX`,则每次向上移动 1 个 `LINE_HEIGHT`
|
||||
* 4) 每移动一次都重新计算 `endY`,直到菜单框进入可显示区域或试探结束
|
||||
* - 这种策略比直接贴顶更保守,尽量保留与原始父菜单位置的相对关系,
|
||||
* 同时逐步寻找一个更自然的显示位置。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuLayout_FallbackProbeUp(uint16_t w0LevelSPosY, uint16_t itemNum, uint16_t *startY, uint16_t *endY)
|
||||
{
|
||||
*startY = w0LevelSPosY;
|
||||
*endY = (uint16_t)(*startY + itemNum * LINE_HEIGHT + MENU_YADD);
|
||||
for (uint16_t i = 1; i < (uint16_t)(w0LevelSPosY / LINE_HEIGHT + 1); i++)
|
||||
{
|
||||
if (*endY > MENU_YMAX)
|
||||
{
|
||||
*startY = (uint16_t)(w0LevelSPosY - LINE_HEIGHT * i);
|
||||
if (*startY < LINE_HEIGHT)
|
||||
{
|
||||
*startY = 0;
|
||||
}
|
||||
*endY = (uint16_t)(*startY + itemNum * LINE_HEIGHT + MENU_YADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_SubPosCal
|
||||
* 功能:
|
||||
* 以递归方式计算从当前层首节点开始的所有子菜单框位置,
|
||||
* 完成当前层以及更深层级菜单的矩形布局。
|
||||
*
|
||||
* 参数:
|
||||
* ptFirst - 当前层首节点指针
|
||||
* ptParent - 当前层所属父菜单框节点
|
||||
* w0LevelSPosY - 0 级菜单框顶部 Y 坐标,用于越界时的回退布局基准
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 `ptFirst == NULL`,说明当前层不存在有效菜单链,直接返回。
|
||||
* - 本函数不对 `ptParent` 做空指针校验,调用方需保证其有效,
|
||||
* 且其 `rect` 已完成布局计算。
|
||||
* - 当前层遍历仍使用 `MENU_MODEL_MAX_ITEM` 作为保护上限,
|
||||
* 防止异常链表导致死循环。
|
||||
* - 当子菜单框底部超出 `MENU_YMAX` 时,优先尝试上翻;
|
||||
* 若上翻失败,则退化到 `MenuLayout_FallbackProbeUp()` 继续寻找可显示位置。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数是子菜单布局的递归核心,处理流程如下:
|
||||
* 1) 以 `ptFirst` 为起点遍历当前层所有节点
|
||||
* 2) 对每个存在子菜单的节点,先取其下一层首节点 `ptNextFirst`
|
||||
* 3) 通过 `MenuModel_GetMenuMaxDisplayLen()` 计算该下一层菜单的最大显示宽度
|
||||
* 4) 根据父菜单框右边界,计算当前子菜单框的 X 坐标
|
||||
* 5) 根据当前节点在本层中的 0 基序号 `wPos`、父层项数 `wNum`,
|
||||
* 计算当前子菜单框的理论 Y 坐标
|
||||
* 6) 若理论底部越界,则尝试上翻或执行回退探测
|
||||
* 7) 当前节点对应子菜单框位置确定后,
|
||||
* 再递归处理其更深一层的子菜单布局
|
||||
* - `ptParent->rect.wNum` 表示父节点直接子节点总数,
|
||||
* 用于把当前节点的原始层内 0 基序号映射到菜单框可显示行号。
|
||||
* - `w0LevelSPosY` 作为递归过程中统一的顶部参考,
|
||||
* 保证深层菜单在空间不足时仍能回退到稳定的显示区域。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuView_SubPosCal(tagPMenuItem ptFirst, tagPMenuItem ptParent, uint16_t w0LevelSPosY)
|
||||
{
|
||||
uint16_t wSPosY;
|
||||
uint16_t wEPosY;
|
||||
uint8_t byMaxLen;
|
||||
uint8_t byMaxNum;
|
||||
uint16_t byMenuPos;
|
||||
uint16_t byItemNum;
|
||||
|
||||
tagPMenuItem ptNextFirst;
|
||||
tagPMenuItem ptIndex;
|
||||
|
||||
|
||||
if (ptFirst == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
byMaxNum = (MENU_YMAX - MENU_YMIN) / LINE_HEIGHT;
|
||||
ptIndex = ptFirst;
|
||||
for (uint16_t wLoop = 0; wLoop < MENU_MODEL_MAX_ITEM; wLoop++)
|
||||
{
|
||||
if (ptIndex->links.lower != NULL)
|
||||
{
|
||||
ptNextFirst = ptIndex->links.lower; /* 保存下一层的第一个节点 */
|
||||
|
||||
byMaxLen = MenuModel_GetMenuMaxDisplayLen(ptNextFirst); /* 获取下一层菜单项中的最大显示宽度 */
|
||||
|
||||
/* 计算子菜单框的 X 坐标 */
|
||||
ptIndex->rect.wSPosX = ptParent->rect.wEPosX; /* 子菜单框左边界紧贴父菜单框右边界 */
|
||||
/* 子菜单框右边界由最大显示宽度 * 单字符宽度 + 额外边距推导 */
|
||||
ptIndex->rect.wEPosX = (uint16_t)(ptIndex->rect.wSPosX + byMaxLen * MENU_WITDTH + MENU_XADD);
|
||||
|
||||
|
||||
/* 计算子菜单框的 Y 坐标 */
|
||||
byMenuPos = ptIndex->rect.wPos; /* 当前菜单项的 0 基序号 */
|
||||
byItemNum = ptParent->rect.wNum; /* 父菜单项的项数 */
|
||||
byMenuPos = MenuLayout_MapMenuPos(byMenuPos, byItemNum, byMaxNum);
|
||||
|
||||
wSPosY = ptParent->rect.wSPosY + byMenuPos * LINE_HEIGHT;
|
||||
byItemNum = ptIndex->rect.wNum;
|
||||
wEPosY = wSPosY + byItemNum * LINE_HEIGHT + MENU_YADD;
|
||||
|
||||
if (wEPosY < MENU_YMAX)
|
||||
{
|
||||
ptIndex->rect.wSPosY = wSPosY;
|
||||
ptIndex->rect.wEPosY = wEPosY;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!MenuLayout_TryFlipUp(&wSPosY, &wEPosY, byItemNum, MENU_YMIN))
|
||||
{
|
||||
MenuLayout_FallbackProbeUp(w0LevelSPosY, byItemNum, &wSPosY, &wEPosY);
|
||||
}
|
||||
ptIndex->rect.wSPosY = wSPosY;
|
||||
ptIndex->rect.wEPosY = wEPosY;
|
||||
}
|
||||
MenuView_SubPosCal(ptNextFirst, ptIndex, w0LevelSPosY);
|
||||
}
|
||||
ptIndex = ptIndex->links.behind;
|
||||
if (ptIndex == ptFirst)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuModel_PositionCal
|
||||
* 功能:
|
||||
* 计算整棵菜单树的初始显示位置,完成 0 级菜单以及后续子菜单层级的矩形布局。
|
||||
*
|
||||
* 参数:
|
||||
* ptMenuHead - 菜单头节点(0 级起点)
|
||||
* by0LevelNum - 0 级菜单数量
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 ptMenuHead 为 NULL、config 为 NULL 或 by0LevelNum 为 0,则直接返回。
|
||||
* - 若 by0LevelNum 超过本函数内部固定工作数组容量 4,则直接返回。
|
||||
* - 当 0 级菜单数量大于 LCD 宽度时,byInterval 可能被截断为 0;
|
||||
* 当前通过断言暴露异常输入,并保持返回保护。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数是菜单布局计算入口:
|
||||
* 1) 先建立 0 级菜单的遍历起点、当前位置与 0 基序号
|
||||
* 2) 根据 lcdSizeX / by0LevelNum 计算 0 级菜单的水平分布间隔
|
||||
* 3) 逐个遍历 0 级菜单项
|
||||
* 4) 对存在子菜单的节点,计算其子菜单框体矩形
|
||||
* 5) 递归调用 MenuView_Sub1PosCal() 继续完成更深层布局
|
||||
* - 本函数只计算 rect,不负责实际绘制。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuModel_PositionCal(tagPMenuItem ptMenuHead, uint16_t by0LevelNum)
|
||||
{
|
||||
tagPMenuItem ptFirst = ptMenuHead;
|
||||
tagPMenuItem ptIndex = ptMenuHead;
|
||||
uint8_t byMaxLen;
|
||||
uint8_t byInterval;
|
||||
tagPMenuItem ptNextFirst;
|
||||
|
||||
ASSERT((ptMenuHead == NULL) || (by0LevelNum == 0));
|
||||
if ((ptMenuHead == NULL) || (by0LevelNum == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
byInterval = MENU_SIZE_X / by0LevelNum;
|
||||
|
||||
for (uint16_t wLoop = 0; wLoop < by0LevelNum; wLoop++)
|
||||
{
|
||||
if (ptIndex->links.lower != NULL)
|
||||
{
|
||||
ptNextFirst = ptIndex->links.lower; /* 保存下一层的第一个节点 */
|
||||
byMaxLen = MenuModel_GetMenuMaxDisplayLen(ptNextFirst);
|
||||
ptIndex->rect.wSPosX = ptIndex->rect.wPos * byInterval;
|
||||
ptIndex->rect.wSPosY = MENU_SIZE_Y - LINE_HEIGHT - ptIndex->rect.wNum * LINE_HEIGHT - MENU_YADD;
|
||||
ptIndex->rect.wEPosX = ptIndex->rect.wPos * byInterval + byMaxLen * MENU_WITDTH + MENU_XADD;
|
||||
ptIndex->rect.wEPosY = MENU_SIZE_Y - LINE_HEIGHT;
|
||||
|
||||
MenuView_SubPosCal(ptNextFirst, ptIndex, ptIndex->rect.wSPosY);
|
||||
}
|
||||
ptIndex = ptIndex->links.behind;
|
||||
if (ptIndex == ptFirst)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum _STR_FLAG_ {
|
||||
EN_STR_FLAG_FUNSET = 0,
|
||||
EN_STR_FLAG_OKCANCEL,
|
||||
EN_VALUE_FLAG_SLOTTYPE,
|
||||
EN_VALUE_FLAG_YESNO,
|
||||
EN_VALUE_FLAG_CHN_NUM,
|
||||
EN_VALUE_FLAG_YXSET,
|
||||
EN_VALUE_FLAG_YKSTEP,
|
||||
EN_VALUE_FLAG_YKACTION,
|
||||
EN_VALUE_FLAG_CHK,
|
||||
EN_VALUE_FLAG_BEUSED,
|
||||
EN_VALUE_FLAG_PORTTYPE,
|
||||
EN_VALUE_FLAG_MAPNAME,
|
||||
EN_VALUE_FLAG_RECTITLE,
|
||||
EN_VALUE_FLAG_BHINFO,
|
||||
EN_VALUE_FLAG_BHSET,
|
||||
EN_VALUE_FLAG_OKCANCEL,
|
||||
EN_VALUE_FLAG_FILETYPE,
|
||||
EN_VALUE_FLAG_ROLE,
|
||||
EN_VALUE_FLAG_COMMSTATE,
|
||||
EN_VALUE_FLAG_ALARM,
|
||||
EN_VALUE_FLAG_YXTYPE,
|
||||
EN_STR_FLAG_MENUALAUTO,
|
||||
EN_STR_FLAG_LINKBREAK,
|
||||
EN_STR_FLAG_DONEUNDONE,
|
||||
EN_STR_FLAG_ACTBACK,
|
||||
EN_STR_FLAG_RESUMEBREAK,
|
||||
EN_STR_FLAG_USESTOP,
|
||||
EN_STR_FLAG_BAUDRATE,
|
||||
EN_STR_FLAG_VERIFY,
|
||||
EN_STR_FLAG_PROTOL,
|
||||
EN_STR_FLAG_RECORD,
|
||||
EN_STR_FLAG_MESSAGE,
|
||||
EN_STR_FLAG_COM,
|
||||
EN_STR_FLAG_MODE,
|
||||
EN_STR_FLAG_COMBAUD,
|
||||
EN_STR_FLAG_DATABIT,
|
||||
EN_STR_FLAG_STOPBIT,
|
||||
EN_STR_FLAG_COMTYPE,
|
||||
EN_STR_FLAG_SAMEV,
|
||||
EN_STR_FLAG_PT,
|
||||
EN_STR_FLAG_CT,
|
||||
EN_STR_FLAG_TRANSTYPE,
|
||||
EN_STR_FLAG_LINEPHS,
|
||||
EN_STR_FLAG_RETOUT,
|
||||
EN_STR_FLAG_YXDI,
|
||||
EN_STR_FLAG_YKPUL,
|
||||
EN_STR_FLAG_WAVE,
|
||||
EN_STR_FLAG_MEMTYPE,
|
||||
EN_STR_FLAG_CHANNEL,
|
||||
EN_STR_FLAG_POWERTYPE,
|
||||
EN_STR_FLAG_ZEROTYPE,
|
||||
EN_STR_FLAG_YCCOMMTYPE,
|
||||
EN_STR_FLAG_YXCOMMTYPE,
|
||||
EN_STR_FLAG_CTRLWORD,
|
||||
EN_STR_FLAG_BUSNAME,
|
||||
EN_STR_FLAG_UNITSHOWMODE,
|
||||
EN_STR_FLAG_BAYMODE,
|
||||
EN_STR_FLAG_BASEANA,
|
||||
EN_STR_FLAG_BASEPHASE,
|
||||
EN_STR_FLAG_BAYCODE,
|
||||
EN_STR_FLAG_SET_NUM,
|
||||
EN_STR_FLAG_DCTYPE,
|
||||
EN_STR_FLAG_DCINTYPE,
|
||||
EN_STR_FLAG_DWINTYPE,
|
||||
EN_STR_FLAG_PTCT,
|
||||
EN_STR_FLAG_HARMOTYPE,
|
||||
EN_STR_FLAG_YXSIN_DI,
|
||||
EN_STR_FLAG_SHOWANATYPE,
|
||||
EN_STR_FLAG_ANAPOLARTYPE,
|
||||
EN_STR_FLAG_WIRDIAGRAMTYPE
|
||||
} enumStrType;
|
||||
|
||||
const tagMenuModel menuTab[] =
|
||||
{
|
||||
{ 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 },
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuModel_CountTopLevel
|
||||
* 功能:
|
||||
* 统计静态菜单定义表中 0 级菜单项的数量,用于后续顶部菜单布局计算。
|
||||
*
|
||||
* 参数:
|
||||
* maxItem - 需要参与统计的菜单项总数
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数默认 `maxItem` 不超过 `menuTab` 实际长度,调用方需保证该前提成立。
|
||||
* - 当 `maxItem == 0` 时,for 循环不进入,函数返回 0。
|
||||
*
|
||||
* 说明:
|
||||
* - 在当前菜单模型中,`byClass == 0` 表示顶层菜单节点。
|
||||
* - 本函数通过顺序扫描 `menuTab[0 ~ maxItem-1]`,
|
||||
* 统计所有 0 级节点数量,并将结果返回给布局初始化流程。
|
||||
* - 返回值通常会被用于计算顶层菜单在 LCD 上的水平分布间隔。
|
||||
*
|
||||
* 返回值:
|
||||
* - 静态菜单表中的 0 级菜单数量
|
||||
* ------------------------------------------------------------------------- */
|
||||
uint16_t MenuModel_CountTopLevel(uint16_t maxItem)
|
||||
{
|
||||
uint8_t by0LevelNum = 0;
|
||||
uint32_t wLoop;
|
||||
|
||||
for (wLoop = 0; wLoop < maxItem; wLoop++)
|
||||
{
|
||||
if (menuTab[wLoop].byClass == 0)
|
||||
{
|
||||
by0LevelNum++;
|
||||
}
|
||||
}
|
||||
return by0LevelNum;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuModel_BuildTree
|
||||
* 功能:
|
||||
* 根据按层级顺序排列的静态菜单定义表,构建菜单运行时树,并建立
|
||||
* `higher / lower / before / behind` 四类链路关系。
|
||||
*
|
||||
* 参数:
|
||||
* menuItems - 运行时菜单节点数组(输出)
|
||||
* modelTab - 静态菜单定义表(输入)
|
||||
* modelCount - 菜单项总数量
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做 menuItems/modelTab 空指针校验,调用方需保证输入有效。
|
||||
* - 默认要求 modelCount > 0,且 `modelTab` 已按菜单显示顺序正确排列:
|
||||
* 同层节点连续,子节点紧跟在父节点之后。
|
||||
* - 当前函数内部使用固定深度数组 `ptFirst[4] / ptLast[4]`,
|
||||
* 因此默认支持的最大层级索引为 3。
|
||||
*
|
||||
* 说明:
|
||||
* - 构建流程:
|
||||
* 1) 先初始化 `menuItems[0]`,作为整棵树的起点
|
||||
* 2) 使用 `ptFirst[level] / ptLast[level]` 记录每一层当前的首节点和尾节点
|
||||
* 3) 依次读取后续静态定义项,复制 `menuDef` 到运行时节点
|
||||
* 4) 比较“当前节点层级”和“下一个节点层级”,决定链路挂接方式:
|
||||
* a) byCurClass < byNextClass
|
||||
* 表示进入更深一层,`ptNextNode` 是 `ptCurrent` 的第一个子节点:
|
||||
* - `ptCurrent->links.lower = ptNextNode`
|
||||
* - `ptNextNode->links.higher = ptCurrent`
|
||||
* - 该层的 `ptFirst/ptLast` 都初始化为 `ptNextNode`
|
||||
* b) byCurClass == byNextClass
|
||||
* 表示接入同层兄弟节点:
|
||||
* - `ptNextNode->links.before = ptCurrent`
|
||||
* - `ptCurrent->links.behind = ptNextNode`
|
||||
* - `ptNextNode->links.higher` 继承当前节点的父节点
|
||||
* - 更新该层 `ptLast`
|
||||
* c) byCurClass > byNextClass
|
||||
* 表示从更深层回退到某个上层后,再接入该层的新兄弟节点:
|
||||
* - `ptNextNode->links.before = ptLast[byNextClass]`
|
||||
* - `ptNextNode->links.higher = ptFirst[byNextClass]->links.higher`
|
||||
* 即:新节点的父节点与该层第一个节点保持一致
|
||||
* - 将原尾节点的 `behind` 指向新节点,并更新该层 `ptLast`
|
||||
* - 对回退过程中已经结束的更深层链表做首尾闭环
|
||||
* 5) 全部节点处理完成后,再将 `0 ~ byCurClass` 各层链表统一闭环
|
||||
* - 构建结果:
|
||||
* `higher` - 父节点
|
||||
* `lower` - 第一个子节点
|
||||
* `before` - 同层前一个节点
|
||||
* `behind` - 同层后一个节点
|
||||
* - 同层链表最终被组织为环形结构,便于菜单上下移动时循环导航。
|
||||
* - 本函数只负责树与链表关系构建,不负责菜单位置计算和显示名称装饰。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuModel_BuildTree(tagMenuItem *menuItems, const tagMenuModel *modelTab, uint32_t modelCount)
|
||||
{
|
||||
tagPMenuItem ptFirst[MENU_MODEL_MAX_LEVEL]; /* 每层第一个节点 */
|
||||
tagPMenuItem ptLast[MENU_MODEL_MAX_LEVEL]; /* 每层最后一个节点 */
|
||||
tagPMenuItem ptCurrent; /* 当前节点 */
|
||||
tagPMenuItem ptNextNode; /* 下一个节点 */
|
||||
|
||||
uint8_t byCurClass; /* 当前节点层级 */
|
||||
uint8_t byNextClass; /* 下一个节点层级 */
|
||||
|
||||
/* 初始化第 0 个节点,作为整棵树的起点 */
|
||||
memset(&menuItems[0], 0, sizeof(menuItems[0]));
|
||||
/* 初始化第 0 个节点的业务字段 */
|
||||
memcpy(&menuItems[0].menuDef, &modelTab[0], sizeof(menuItems[0].menuDef));
|
||||
|
||||
/* 初始化第 0 个节点的链路 */
|
||||
ptFirst[0] = &menuItems[0];
|
||||
ptLast[0] = ptFirst[0];
|
||||
ptCurrent = &menuItems[0];
|
||||
|
||||
/* 构建菜单树 */
|
||||
for (uint16_t index = 1; index < modelCount; index++)
|
||||
{
|
||||
/* 初始化第 index 个节点的业务字段 */
|
||||
ptNextNode = &menuItems[index];
|
||||
memset(ptNextNode, 0, sizeof(*ptNextNode));
|
||||
memcpy(&ptNextNode->menuDef, &modelTab[index], sizeof(ptNextNode->menuDef));
|
||||
|
||||
byCurClass = ptCurrent->menuDef.byClass;
|
||||
byNextClass = ptNextNode->menuDef.byClass;
|
||||
|
||||
if (byCurClass < byNextClass) /* 当前节点层级小于下一个节点层级 */
|
||||
{
|
||||
ptCurrent->links.lower = ptNextNode;
|
||||
ptNextNode->links.higher = ptCurrent;
|
||||
/* 更新该层 first/last */
|
||||
ptFirst[byNextClass] = ptNextNode;
|
||||
ptLast[byNextClass] = ptNextNode;
|
||||
}
|
||||
else if (byCurClass == byNextClass) /* 当前节点层级等于下一个节点层级 */
|
||||
{
|
||||
ptNextNode->links.before = ptCurrent;
|
||||
ptNextNode->links.higher = ptCurrent->links.higher;
|
||||
ptCurrent->links.behind = ptNextNode;
|
||||
/* 更新该层 last */
|
||||
ptLast[byNextClass] = ptNextNode;
|
||||
}
|
||||
else /* 当前节点层级大于下一个节点层级,回退层级*/
|
||||
{
|
||||
ptNextNode->links.before = ptLast[byNextClass];
|
||||
ptNextNode->links.higher = ptFirst[byNextClass]->links.higher;
|
||||
/* 更新该层 last 的 behind */
|
||||
ptLast[byNextClass]->links.behind = ptNextNode;
|
||||
ptLast[byNextClass] = ptNextNode;
|
||||
|
||||
/* 形成回环链表 */
|
||||
for (uint16_t index2 = byCurClass; index2 > byNextClass; index2--)
|
||||
{
|
||||
ptLast[index2]->links.behind = ptFirst[index2];
|
||||
ptFirst[index2]->links.before = ptLast[index2];
|
||||
}
|
||||
}
|
||||
/* 更新当前节点 */
|
||||
ptCurrent = ptNextNode;
|
||||
}
|
||||
/* 形成回环链表 */
|
||||
byCurClass = ptCurrent->menuDef.byClass;
|
||||
for (uint16_t index = 0; index <= byCurClass; index++)
|
||||
{
|
||||
ptLast[index]->links.behind = ptFirst[index];
|
||||
ptFirst[index]->links.before = ptLast[index];
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuModel_DecorateDisplayNames
|
||||
* 功能:
|
||||
* 为存在子菜单的菜单项名称追加显示装饰符,提示该节点可继续进入下一级。
|
||||
*
|
||||
* 参数:
|
||||
* menuItems - 菜单项数组首地址
|
||||
* modelCount - 菜单项总数量
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做 menuItems 空指针校验,调用方需保证数组有效。
|
||||
* - 若菜单项没有子节点(links.lower == NULL),直接跳过,不追加装饰符。
|
||||
* - 通过 `sizeof(byName) - 1` 预留字符串结尾 `\0`,避免越界写入。
|
||||
* - 若名称已以 `\x10` 结尾,则不重复追加。
|
||||
*
|
||||
* 说明:
|
||||
* - `\x10` 在当前菜单显示逻辑中作为“右箭头/可下钻”标记,
|
||||
* 用于提示用户该项后面还有子菜单。
|
||||
* - 处理流程:
|
||||
* 1) 遍历所有菜单项
|
||||
* 2) 判断是否存在子菜单
|
||||
* 3) 统计当前名称长度(不超过缓冲区上限)
|
||||
* 4) 若末尾尚未追加装饰符,则写入 `\x10` 和字符串结束符 `\0`
|
||||
* - 该函数只修改显示名称,不改变菜单树链路关系和业务属性。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuModel_DecorateDisplayNames(tagMenuItem *menuItems, uint32_t modelCount)
|
||||
{
|
||||
for (uint32_t i = 0; i < modelCount; i++)
|
||||
{
|
||||
uint16_t nameCapacity = (uint16_t)sizeof(menuItems[i].menuDef.byName);
|
||||
uint16_t maxTextLenWithoutNull = (uint16_t)(nameCapacity - 1);
|
||||
uint16_t len = 0;
|
||||
/* 如果没有子菜单,直接跳过 */
|
||||
if (menuItems[i].links.lower == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
/* 计算名称长度 */
|
||||
while ((len < maxTextLenWithoutNull) && (menuItems[i].menuDef.byName[len] != '\0'))
|
||||
{
|
||||
len++;
|
||||
}
|
||||
/* 补充右箭头 */
|
||||
if ((len == 0) || (menuItems[i].menuDef.byName[len - 1] != '\x10'))
|
||||
{
|
||||
if (len < maxTextLenWithoutNull)
|
||||
{
|
||||
menuItems[i].menuDef.byName[len] = '\x10';
|
||||
menuItems[i].menuDef.byName[len + 1] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuModel_IndexMenuItems
|
||||
* 功能:
|
||||
* 统计整棵菜单树中所有菜单项的 `wPos` 与 `wNum`。
|
||||
*
|
||||
* 参数:
|
||||
* menuItems - 菜单项数组
|
||||
* modelCount - 菜单项总数
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 `menuItems == NULL` 或 `modelCount == 0`,则直接返回。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数分两部分初始化:
|
||||
* 1) 先遍历 0 级同层链表,为所有顶层节点写入 `rect.wPos`
|
||||
* 2) 再遍历每个节点:
|
||||
* a) 若无子节点,则其 `rect.wNum = 0`
|
||||
* b) 若有子节点,则沿子层 `behind` 链统计子节点总数
|
||||
* c) 同时为该组子节点写入各自的 `rect.wPos`
|
||||
* d) 最后把子节点总数写回当前父节点的 `rect.wNum`
|
||||
* - 因此:
|
||||
* `wPos` 表示“当前节点在本层中的 0 基序号”
|
||||
* `wNum` 表示“当前节点拥有的直接子节点总数”
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuModel_IndexMenuItems(tagMenuItem *menuItems, uint32_t modelCount)
|
||||
{
|
||||
tagPMenuItem current;
|
||||
tagPMenuItem parent;
|
||||
tagPMenuItem child;
|
||||
uint16_t count;
|
||||
|
||||
if ((menuItems == NULL) || (modelCount == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* 1. 记录 0 级节点的位置和子节点数量 */
|
||||
current = &menuItems[0];
|
||||
count = 0;
|
||||
for (uint16_t index = 0; index < modelCount; index++)
|
||||
{
|
||||
current->rect.wPos = count;
|
||||
current = current->links.behind;
|
||||
if ((current == NULL) || (current == &menuItems[0]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
/* 2. 记录其他层级的位置和子节点数量 */
|
||||
for (uint32_t i = 0; i < modelCount; i++)
|
||||
{
|
||||
parent = &menuItems[i];
|
||||
current = parent->links.lower;
|
||||
/* a. 如果当前节点没有子节点,说明是最深一层,记录当前节点子节点数量为 0 */
|
||||
if (current == NULL)
|
||||
{
|
||||
parent->rect.wNum = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*b. 若有子节点,则沿子层 `behind` 链统计子节点总数 */
|
||||
child = current;
|
||||
count = 0;
|
||||
|
||||
for (uint16_t index = 0; index < MENU_MODEL_MAX_ITEM; index++)
|
||||
{
|
||||
/*c. 为该组子节点写入各自的 `rect.wPos` */
|
||||
child->rect.wPos = count;
|
||||
count++;
|
||||
|
||||
child = child->links.behind;
|
||||
if ((child == NULL) || (child == current))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* d. 最后把子节点总数写回当前父节点的 `rect.wNum` */
|
||||
parent->rect.wNum = count;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MenuModel_Init(menu_model_t *model)
|
||||
{
|
||||
uint16_t by0LevelNum;
|
||||
model->maxItem = (uint16_t)(sizeof(menuTab) / sizeof(tagMenuModel));
|
||||
memset(model->menuItems, 0, sizeof(model->menuItems));
|
||||
|
||||
by0LevelNum = MenuModel_CountTopLevel(model->maxItem);
|
||||
MenuModel_BuildTree(model->menuItems, menuTab, model->maxItem);
|
||||
MenuModel_DecorateDisplayNames(model->menuItems, model->maxItem);
|
||||
MenuModel_IndexMenuItems(model->menuItems, model->maxItem);
|
||||
MenuModel_PositionCal(&model->menuItems[0], by0LevelNum);
|
||||
}
|
||||
36
src/Drv/pages/menu/model.h
Normal file
36
src/Drv/pages/menu/model.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef MENU_MODEL_H
|
||||
#define MENU_MODEL_H
|
||||
|
||||
#include "display.h"
|
||||
#include "def.h"
|
||||
|
||||
#define MENU_MODEL_MAX_ITEM 300 /* 最大菜单项数 */
|
||||
#define MENU_MODEL_MAX_LEVEL 4 /* 最大菜单层级 */
|
||||
|
||||
typedef struct menu_model_t menu_model_t;
|
||||
|
||||
struct menu_model_t
|
||||
{
|
||||
uint16_t maxItem;
|
||||
tagMenuItem menuItems[MENU_MODEL_MAX_ITEM];
|
||||
};
|
||||
|
||||
|
||||
|
||||
void MenuModel_Init(menu_model_t *model);
|
||||
|
||||
|
||||
|
||||
/* whitebox test hooks */
|
||||
#ifdef UNIT_TEST
|
||||
void MenuModel_IndexMenuItems(tagMenuItem *menuItems, uint32_t modelCount);
|
||||
void MenuModel_PositionCal(tagPMenuItem ptMenuHead, uint16_t by0LevelNum);
|
||||
uint8_t MenuModel_Utf8LenCal(uint8_t *str);
|
||||
uint8_t MenuModel_GetMenuMaxDisplayLen(tagPMenuItem ptFirst);
|
||||
uint16_t MenuModel_CountTopLevel(uint16_t maxItem);
|
||||
void MenuModel_BuildTree(tagMenuItem *menuItems, const tagMenuModel *modelTab, uint32_t modelCount);
|
||||
void MenuModel_DecorateDisplayNames(tagMenuItem *menuItems, uint32_t modelCount);
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
255
src/Drv/pages/menu/page.c
Normal file
255
src/Drv/pages/menu/page.c
Normal file
@@ -0,0 +1,255 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "page.h"
|
||||
|
||||
#include "display.h"
|
||||
#include "presenter.h"
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 模块内静态对象说明:
|
||||
* s_model - 菜单页 Model 实例(菜单数据与运行时结构)
|
||||
* s_view - 菜单页 View 实例(布局与渲染能力)
|
||||
* s_presenter - 菜单页 Presenter 实例(输入处理与状态驱动)
|
||||
* s_menuPage - 页面管理器可注册的 page_t 描述对象
|
||||
*
|
||||
* 说明:
|
||||
* - 以上对象均为文件内静态单例,生命周期覆盖进程运行期。
|
||||
* - 通过 MenuPage_GetInstance() 暴露 s_menuPage 给 PageManager 注册。
|
||||
* ------------------------------------------------------------------------- */
|
||||
static menu_model_t s_model;
|
||||
static menu_view_t s_view;
|
||||
static menu_presenter_t s_presenter;
|
||||
static page_t s_menuPage;
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_OnEnter
|
||||
* 功能:
|
||||
* 页面进入回调:将菜单渲染标记为“首帧全量刷新”,并立即触发一次刷新。
|
||||
*
|
||||
* 参数:
|
||||
* page - 当前页面对象指针(本实现未直接使用)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不依赖 page 内容,统一转为 (void)page 消除未使用告警。
|
||||
*
|
||||
* 说明:
|
||||
* - 通过 dspCtrl.bFirst = 1 告知 Presenter 下一次刷新走首帧路径。
|
||||
* - 进入页面后立即刷新,确保界面可见状态与内部状态同步。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuPage_OnEnter(page_t *page)
|
||||
{
|
||||
(void)page;
|
||||
s_presenter.dspCtrl.bFirst = 1;
|
||||
MenuPresenter_Refresh(&s_presenter);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_OnExit
|
||||
* 功能:
|
||||
* 页面退出回调占位点;当前版本无额外退出动作。
|
||||
*
|
||||
* 参数:
|
||||
* page - 当前页面对象指针(本实现未直接使用)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 使用 (void)page 防止未使用参数告警。
|
||||
*
|
||||
* 说明:
|
||||
* - 预留给后续扩展(如停止定时任务、冻结动画、保存瞬时 UI 状态等)。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuPage_OnExit(page_t *page)
|
||||
{
|
||||
(void)page;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_OnDestroy
|
||||
* 功能:
|
||||
* 页面销毁回调:清空菜单页内部三层对象(Model/View/Presenter)状态。
|
||||
*
|
||||
* 参数:
|
||||
* page - 当前页面对象指针(本实现未直接使用)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 使用 memset 全量清零静态对象,避免残留状态影响后续重建。
|
||||
*
|
||||
* 说明:
|
||||
* - 与 is_cached 策略配合:当页面被标记为非缓存并弹栈销毁时,该函数用于复位。
|
||||
* - page_t 元信息不在此函数复位,由页面生命周期创建阶段重新赋值。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuPage_OnDestroy(page_t *page)
|
||||
{
|
||||
(void)page;
|
||||
memset(&s_model, 0, sizeof(s_model));
|
||||
memset(&s_view, 0, sizeof(s_view));
|
||||
memset(&s_presenter, 0, sizeof(s_presenter));
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_OnEvent
|
||||
* 功能:
|
||||
* 页面事件回调:校验输入事件后,将按键转交 Presenter 处理。
|
||||
*
|
||||
* 参数:
|
||||
* page - 当前页面对象指针(本实现未直接使用)
|
||||
* event - 输入事件指针
|
||||
*
|
||||
* 边界处理:
|
||||
* - event 为 NULL 时返回 EVENT_UNHANDLED。
|
||||
* - 仅处理 PAGE_EVENT_KEY 事件类型,其它类型返回 EVENT_UNHANDLED。
|
||||
* - keyVal 为 0 视为无效按键,返回 EVENT_UNHANDLED。
|
||||
*
|
||||
* 说明:
|
||||
* - 事件有效时调用 MenuPresenter_HandleInput() 执行业务输入流转。
|
||||
* - 返回 EVENT_HANDLED,表示该事件已被菜单页消费,不再交给上层页面逻辑。
|
||||
*
|
||||
* 返回值:
|
||||
* - EVENT_HANDLED : 事件已处理
|
||||
* - EVENT_UNHANDLED : 事件无效或不属于本页处理范围
|
||||
* ------------------------------------------------------------------------- */
|
||||
static event_result_t MenuPage_OnEvent(page_t *page, input_event_t *event)
|
||||
{
|
||||
(void)page;
|
||||
if ((event == NULL) || (event->type != PAGE_EVENT_KEY) || (event->keyVal == 0))
|
||||
{
|
||||
return EVENT_UNHANDLED;
|
||||
}
|
||||
|
||||
MenuPresenter_HandleInput(&s_presenter, event->keyVal);
|
||||
return EVENT_HANDLED;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_OnLoop
|
||||
* 功能:
|
||||
* 页面循环回调:周期性驱动 Presenter 执行刷新逻辑。
|
||||
*
|
||||
* 参数:
|
||||
* page - 当前页面对象指针(本实现未直接使用)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 使用 (void)page 防止未使用参数告警。
|
||||
*
|
||||
* 说明:
|
||||
* - 实际刷新策略(全量/增量)由 Presenter 内部状态控制。
|
||||
* - 该函数通常由 PageManager_Loop() 在主循环节拍中调用。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuPage_OnLoop(page_t *page)
|
||||
{
|
||||
(void)page;
|
||||
MenuPresenter_Refresh(&s_presenter);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_OnCreate
|
||||
* 功能:
|
||||
* 页面创建回调:按 Model -> View -> Presenter 顺序完成菜单页运行时装配,
|
||||
* 并初始化 page_t 描述对象字段。
|
||||
*
|
||||
* 参数:
|
||||
* page - 当前页面对象指针(由 PageManager 传入)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 先清零 s_menuPage,再统一重建其元信息与回调绑定,避免脏状态遗留。
|
||||
* - 假定 page 非空且来自 PageManager 生命周期调用链。
|
||||
*
|
||||
* 说明:
|
||||
* - 初始化顺序固定:
|
||||
* 1) MenuModel_Init(&s_model)
|
||||
* 2) MenuView_Init(&s_view)
|
||||
* 3) MenuPresenter_Init(&s_presenter, &s_model, &s_view)
|
||||
* - 将 model/presenter/view 回填到 page 与 s_menuPage,便于调试与统一访问。
|
||||
* - s_menuPage 作为静态页面实例,对外由 MenuPage_GetInstance() 返回。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuPage_OnCreate(page_t *page)
|
||||
{
|
||||
/* 1) model init */
|
||||
MenuModel_Init(&s_model);
|
||||
|
||||
/* 2) view init */
|
||||
MenuView_Init(&s_view);
|
||||
|
||||
/* 3) presenter setup + runtime build */
|
||||
MenuPresenter_Init(&s_presenter, &s_model, &s_view);
|
||||
|
||||
|
||||
page->model = &s_model;
|
||||
page->presenter = &s_presenter;
|
||||
page->view = &s_view;
|
||||
|
||||
s_menuPage.presenter = &s_presenter;
|
||||
s_menuPage.view = &s_view;
|
||||
s_menuPage.model = &s_model;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_GetInstance
|
||||
* 功能:
|
||||
* 获取菜单页静态实例指针,供 PageManager_Register() 注册使用。
|
||||
*
|
||||
* 参数:
|
||||
* 无
|
||||
*
|
||||
* 边界处理:
|
||||
* - 返回文件内静态对象地址,无空指针分支。
|
||||
*
|
||||
* 说明:
|
||||
* - 该函数仅暴露页面入口,不负责初始化;初始化由 on_create 生命周期完成。
|
||||
*
|
||||
* 返回值:
|
||||
* - 指向静态页面对象 s_menuPage 的 page_t* 指针
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_t *MenuPage_GetInstance(void)
|
||||
{
|
||||
/* 确保在注册到 PageManager 前,页面生命周期回调已就绪 */
|
||||
memset(&s_menuPage, 0, sizeof(s_menuPage));
|
||||
s_menuPage.page_id = PAGE_ID_MENU;
|
||||
s_menuPage.is_cached = 1;
|
||||
s_menuPage.on_create = MenuPage_OnCreate;
|
||||
s_menuPage.on_enter = MenuPage_OnEnter;
|
||||
s_menuPage.on_exit = MenuPage_OnExit;
|
||||
s_menuPage.on_destroy = MenuPage_OnDestroy;
|
||||
s_menuPage.on_event = MenuPage_OnEvent;
|
||||
s_menuPage.on_loop = MenuPage_OnLoop;
|
||||
return &s_menuPage;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPage_TriggerCurrentAction
|
||||
* 功能:
|
||||
* 触发当前菜单项动作的占位接口(当前版本仅输出调试日志)。
|
||||
*
|
||||
* 参数:
|
||||
* 无
|
||||
*
|
||||
* 边界处理:
|
||||
* - 当前实现不依赖外部输入,不涉及参数校验。
|
||||
*
|
||||
* 说明:
|
||||
* - 现阶段用于保留动作触发扩展点,后续可接入真实业务动作执行链路。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuPage_TriggerCurrentAction(void)
|
||||
{
|
||||
printf("MenuPage_TriggerCurrentAction\n");
|
||||
}
|
||||
10
src/Drv/pages/menu/page.h
Normal file
10
src/Drv/pages/menu/page.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef MENU_PAGE_H
|
||||
#define MENU_PAGE_H
|
||||
|
||||
#include "view.h"
|
||||
#include "../page.h"
|
||||
|
||||
page_t *MenuPage_GetInstance(void);
|
||||
void MenuPage_TriggerCurrentAction(void);
|
||||
|
||||
#endif
|
||||
272
src/Drv/pages/menu/presenter.c
Normal file
272
src/Drv/pages/menu/presenter.c
Normal file
@@ -0,0 +1,272 @@
|
||||
#include "presenter.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../../key.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
tagPMenuItem ptHead;
|
||||
tagPMenuItem ptCurrent;
|
||||
tagPMenuItem ptRoute[4];
|
||||
tagPMenuItem ptCurBak;
|
||||
tagPMenuItem pt0Level;
|
||||
} MenuNavState;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t needRefresh;
|
||||
uint8_t skipRenderThisRound;
|
||||
} MenuNavResult;
|
||||
|
||||
static MenuNavResult MenuNavigator_ProcessKey(MenuNavState *navState, uint8_t keyVal)
|
||||
{
|
||||
MenuNavResult result;
|
||||
tagPMenuItem ptCurrent;
|
||||
tagPMenuItem ptHead;
|
||||
tagPMenuItem *ptRoute;
|
||||
|
||||
result.needRefresh = 0;
|
||||
result.skipRenderThisRound = 0;
|
||||
|
||||
ptCurrent = navState->ptCurrent;
|
||||
ptHead = navState->ptHead;
|
||||
ptRoute = navState->ptRoute;
|
||||
|
||||
switch (keyVal)
|
||||
{
|
||||
case KEY_F1:
|
||||
case KEY_F2:
|
||||
break;
|
||||
case KEY_U:
|
||||
ptCurrent = ptCurrent->links.before;
|
||||
result.needRefresh = 1;
|
||||
break;
|
||||
case KEY_D:
|
||||
ptCurrent = ptCurrent->links.behind;
|
||||
result.needRefresh = 1;
|
||||
break;
|
||||
case KEY_L:
|
||||
ptCurrent = ptRoute[ptCurrent->menuDef.byClass - 1];
|
||||
if (ptCurrent->menuDef.byClass == 0)
|
||||
{
|
||||
if (ptCurrent->rect.wPos == 1)
|
||||
{
|
||||
ptCurrent = ptCurrent->links.before->links.before;
|
||||
}
|
||||
else
|
||||
{
|
||||
ptCurrent = ptCurrent->links.before;
|
||||
}
|
||||
ptCurrent = ptCurrent->links.lower;
|
||||
}
|
||||
result.needRefresh = 1;
|
||||
break;
|
||||
case KEY_R:
|
||||
case KEY_ENT:
|
||||
if (ptCurrent->links.lower != NULL)
|
||||
{
|
||||
ptCurrent = ptCurrent->links.lower;
|
||||
result.needRefresh = 1;
|
||||
}
|
||||
else if (ptCurrent->menuDef.pfnWinProc != NULL)
|
||||
{
|
||||
ptCurrent->menuDef.pfnWinProc();
|
||||
}
|
||||
break;
|
||||
case KEY_ESC:
|
||||
if (ptCurrent->menuDef.byClass == 1)
|
||||
{
|
||||
navState->pt0Level = ptHead;
|
||||
navState->ptRoute[0] = ptHead;
|
||||
navState->ptCurrent = ptHead->links.lower;
|
||||
navState->ptCurBak = navState->ptCurrent;
|
||||
navState->ptRoute[1] = navState->ptCurrent;
|
||||
result.skipRenderThisRound = 1;
|
||||
return result;
|
||||
}
|
||||
ptCurrent = ptRoute[ptCurrent->menuDef.byClass - 1];
|
||||
result.needRefresh = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
navState->ptCurrent = ptCurrent;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void MenuNavigator_RebuildRoute(MenuNavState *navState, uint32_t maxItem)
|
||||
{
|
||||
tagPMenuItem ptIndex;
|
||||
|
||||
if (navState->ptCurBak == navState->ptCurrent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ptIndex = navState->ptCurrent;
|
||||
for (uint32_t index = 0; index < maxItem; index++)
|
||||
{
|
||||
if (ptIndex->links.higher == NULL)
|
||||
{
|
||||
ptIndex = ptIndex->links.before;
|
||||
}
|
||||
else
|
||||
{
|
||||
ptIndex = ptIndex->links.higher;
|
||||
navState->ptRoute[ptIndex->menuDef.byClass] = ptIndex;
|
||||
}
|
||||
if (ptIndex->menuDef.byClass == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
navState->ptRoute[navState->ptCurrent->menuDef.byClass] = navState->ptCurrent;
|
||||
}
|
||||
|
||||
static void MenuPresenter_FillNavState(const tagMenuCtrl *ctrl, MenuNavState *nav)
|
||||
{
|
||||
nav->ptHead = ctrl->ptHead;
|
||||
nav->ptCurrent = ctrl->ptCurrent;
|
||||
nav->ptRoute[0] = ctrl->ptRoute[0];
|
||||
nav->ptRoute[1] = ctrl->ptRoute[1];
|
||||
nav->ptRoute[2] = ctrl->ptRoute[2];
|
||||
nav->ptRoute[3] = ctrl->ptRoute[3];
|
||||
nav->ptCurBak = ctrl->ptCurBak;
|
||||
nav->pt0Level = ctrl->pt0Level;
|
||||
}
|
||||
|
||||
static void MenuPresenter_ApplyNavState(tagMenuCtrl *ctrl, const MenuNavState *nav)
|
||||
{
|
||||
ctrl->ptHead = nav->ptHead;
|
||||
ctrl->ptCurrent = nav->ptCurrent;
|
||||
ctrl->ptRoute[0] = nav->ptRoute[0];
|
||||
ctrl->ptRoute[1] = nav->ptRoute[1];
|
||||
ctrl->ptRoute[2] = nav->ptRoute[2];
|
||||
ctrl->ptRoute[3] = nav->ptRoute[3];
|
||||
ctrl->ptCurBak = nav->ptCurBak;
|
||||
ctrl->pt0Level = nav->pt0Level;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuPresenter_RenderByState
|
||||
* 功能:
|
||||
* 根据当前菜单状态差异选择渲染策略(整页刷新 / 同层局部刷新 / 单层补绘)。
|
||||
*
|
||||
* 参数:
|
||||
* presenter - 菜单 Presenter 实例,内部持有 menuCtrl 与 view 接口
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针校验,默认由调用链保证 presenter/view 有效。
|
||||
* - 当菜单路由发生跨 0 级变化时,强制走整页刷新,避免多层残影。
|
||||
* - 仅在“同层且同父”场景使用局部反显更新,其他变化按层级关系降级处理。
|
||||
*
|
||||
* 说明:
|
||||
* - 该函数是 Presenter 的渲染决策核心,不直接参与菜单导航计算。
|
||||
* - 决策顺序如下:
|
||||
* 1) 若 pt0Level 与 route[0] 不一致:
|
||||
* 说明当前顶层上下文已切换,先同步 pt0Level/ptCurBak,再 full_refresh。
|
||||
* 2) 否则若 ptCurBak 与 ptCurrent 不一致:
|
||||
* a) 同层 + 同父:调用 update_selection_same_level,执行“旧选中恢复 + 新选中高亮”
|
||||
* b) 新层级 >= 旧层级:调用 show_other_level,仅补绘受影响层
|
||||
* c) 其他(通常是回退到更高层):调用 full_refresh,保证界面一致性
|
||||
* 3) 渲染后统一将 ptCurBak 更新为 ptCurrent,作为下一轮差异比较基准。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuPresenter_RenderByState(MenuPresenter *presenter)
|
||||
{
|
||||
tagMenuCtrl *menuCtrl = &presenter->menuCtrl;
|
||||
MenuView *view = presenter->view;
|
||||
|
||||
if (menuCtrl->pt0Level != menuCtrl->ptRoute[0])
|
||||
{
|
||||
menuCtrl->pt0Level = menuCtrl->ptRoute[0];
|
||||
menuCtrl->ptCurBak = menuCtrl->ptCurrent;
|
||||
presenter->view->full_refresh(presenter->view, menuCtrl, MODE_NONE);
|
||||
}
|
||||
else if (menuCtrl->ptCurBak != menuCtrl->ptCurrent)
|
||||
{
|
||||
if ((menuCtrl->ptCurrent->menuDef.byClass == menuCtrl->ptCurBak->menuDef.byClass) &&
|
||||
(menuCtrl->ptCurrent->menuDef.byClass > 0) &&
|
||||
(menuCtrl->ptCurrent->links.higher == menuCtrl->ptCurBak->links.higher))
|
||||
{
|
||||
view->update_selection_same_level(view, menuCtrl->ptCurBak, menuCtrl->ptCurrent);
|
||||
}
|
||||
else if (menuCtrl->ptCurrent->menuDef.byClass >= menuCtrl->ptCurBak->menuDef.byClass)
|
||||
{
|
||||
view->update_selection_new_level(view, menuCtrl->ptRoute[menuCtrl->ptCurrent->menuDef.byClass - 1], menuCtrl->ptRoute[menuCtrl->ptCurrent->menuDef.byClass]);
|
||||
}
|
||||
else
|
||||
{
|
||||
presenter->view->full_refresh(presenter->view, menuCtrl, MODE_NONE);
|
||||
}
|
||||
menuCtrl->ptCurBak = menuCtrl->ptCurrent;
|
||||
}
|
||||
}
|
||||
|
||||
void MenuPresenter_Init(MenuPresenter *presenter, menu_model_t *model, menu_view_t *view)
|
||||
{
|
||||
presenter->dspCtrl.bFirst = 0;
|
||||
memset(&presenter->menuCtrl, 0, sizeof(presenter->menuCtrl));
|
||||
presenter->model = model;
|
||||
presenter->view = view;
|
||||
|
||||
|
||||
presenter->dspCtrl.bFirst = 1;
|
||||
presenter->menuCtrl.ptHead = &presenter->model->menuItems[0];
|
||||
presenter->menuCtrl.pt0Level = &presenter->model->menuItems[0];
|
||||
presenter->menuCtrl.ptCurrent = presenter->menuCtrl.ptHead->links.lower;
|
||||
presenter->menuCtrl.ptCurBak = presenter->menuCtrl.ptCurrent;
|
||||
|
||||
presenter->menuCtrl.ptRoute[0] = &presenter->model->menuItems[0];
|
||||
presenter->menuCtrl.ptRoute[1] = presenter->menuCtrl.ptCurrent;
|
||||
presenter->menuCtrl.ptRoute[2] = presenter->menuCtrl.ptCurrent;
|
||||
presenter->menuCtrl.ptRoute[3] = presenter->menuCtrl.ptCurrent;
|
||||
}
|
||||
|
||||
void MenuPresenter_Refresh(MenuPresenter *presenter)
|
||||
{
|
||||
if (presenter->dspCtrl.bFirst)
|
||||
{
|
||||
presenter->dspCtrl.bFirst = 0;
|
||||
presenter->view->full_refresh(presenter->view, &presenter->menuCtrl, MODE_NONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
MenuPresenter_RenderByState(presenter);
|
||||
}
|
||||
}
|
||||
|
||||
void MenuPresenter_HandleInput(MenuPresenter *presenter, uint8_t keyVal)
|
||||
{
|
||||
MenuNavState navState;
|
||||
MenuNavResult navResult;
|
||||
|
||||
if (presenter->dspCtrl.bFirst)
|
||||
{
|
||||
MenuPresenter_Refresh(presenter);
|
||||
return;
|
||||
}
|
||||
|
||||
MenuPresenter_FillNavState(&presenter->menuCtrl, &navState);
|
||||
navResult = MenuNavigator_ProcessKey(&navState, keyVal);
|
||||
MenuPresenter_ApplyNavState(&presenter->menuCtrl, &navState);
|
||||
|
||||
if (navResult.skipRenderThisRound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (navResult.needRefresh)
|
||||
{
|
||||
MenuNavState rebuildState;
|
||||
MenuPresenter_FillNavState(&presenter->menuCtrl, &rebuildState);
|
||||
MenuNavigator_RebuildRoute(&rebuildState, presenter->model->maxItem);
|
||||
MenuPresenter_ApplyNavState(&presenter->menuCtrl, &rebuildState);
|
||||
MenuPresenter_Refresh(presenter);
|
||||
}
|
||||
}
|
||||
33
src/Drv/pages/menu/presenter.h
Normal file
33
src/Drv/pages/menu/presenter.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef MENU_PRESENTER_H
|
||||
#define MENU_PRESENTER_H
|
||||
|
||||
#include "display.h"
|
||||
#include "model.h"
|
||||
#include "view.h"
|
||||
|
||||
typedef struct menu_presenter_t menu_presenter_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t bFirst;
|
||||
} tagDspCtrl;
|
||||
|
||||
|
||||
struct menu_presenter_t
|
||||
{
|
||||
tagDspCtrl dspCtrl;
|
||||
tagMenuCtrl menuCtrl;
|
||||
menu_model_t *model;
|
||||
menu_view_t *view;
|
||||
};
|
||||
|
||||
typedef menu_presenter_t MenuPresenter;
|
||||
|
||||
void MenuPresenter_Init(MenuPresenter *presenter,
|
||||
menu_model_t *model,
|
||||
menu_view_t *view);
|
||||
|
||||
void MenuPresenter_HandleInput(MenuPresenter *presenter, uint8_t keyVal);
|
||||
void MenuPresenter_Refresh(MenuPresenter *presenter);
|
||||
|
||||
#endif
|
||||
313
src/Drv/pages/menu/view.c
Normal file
313
src/Drv/pages/menu/view.c
Normal file
@@ -0,0 +1,313 @@
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include "view.h"
|
||||
|
||||
|
||||
static const PageRenderPort *s_port = NULL;
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_DrawMeitou
|
||||
* 功能:
|
||||
* 绘制菜单标题装饰线(“眉头”样式):中间横线 + 左右两端斜线。
|
||||
*
|
||||
* 参数:
|
||||
* view - 菜单视图对象,提供底层渲染端口
|
||||
* yStart - 装饰线基准 Y 坐标(横线所在行)
|
||||
* width - 线宽参数(传递给 line_h/line)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针判定,调用方需保证 view 与 s_port 有效。
|
||||
* - 坐标范围合法性由上层布局计算保证。
|
||||
*
|
||||
* 说明:
|
||||
* - 颜色统一使用字体前景色(get_color_font),确保装饰与文字风格一致。
|
||||
* - 绘制顺序:
|
||||
* 1) 中间横线(x:16~144)
|
||||
* 2) 左斜线(8, yStart-8 -> 16, yStart)
|
||||
* 3) 右斜线(144, yStart -> 152, yStart-8)
|
||||
* - 该函数用于视觉分隔与层次强调,不改变菜单状态数据。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuView_DrawMeitou(MenuView *view, uint16_t yStart, uint16_t width)
|
||||
{
|
||||
uint8_t fontColor = s_port->get_color_font();
|
||||
s_port->line_h(16, 144, yStart, width, fontColor);
|
||||
s_port->line(8, yStart - 8, 16, yStart, width, fontColor);
|
||||
s_port->line(144, yStart, 152, yStart - 8, width, fontColor);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_DrawBoundaryBox
|
||||
* 功能:
|
||||
* 绘制矩形边界框:先清空内部区域,再绘制四条边线。
|
||||
*
|
||||
* 参数:
|
||||
* view - 菜单视图对象,提供像素与线段绘制接口
|
||||
* leftX - 左边界 X
|
||||
* topY - 上边界 Y
|
||||
* rightX - 右边界 X
|
||||
* bottomY - 下边界 Y
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不校验 view/port 空指针,调用方需保证有效。
|
||||
* - 默认要求 leftX <= rightX、topY <= bottomY;
|
||||
* 若参数异常,像素循环或画线结果由底层驱动行为决定。
|
||||
*
|
||||
* 说明:
|
||||
* - 背景填充:逐像素将内部区域置为背景色(get_color_back),
|
||||
* 避免残影或旧内容叠加。
|
||||
* - 边框绘制:统一使用字体前景色(get_color_font),绘制上/左/下/右四边。
|
||||
* - 下边框使用 rightX + 1,目的是与其它边界在视觉上闭合对齐。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuView_DrawBoundaryBox(MenuView *view, uint16_t leftX, uint16_t topY, uint16_t rightX, uint16_t bottomY)
|
||||
{
|
||||
uint8_t backColor = s_port->get_color_back();
|
||||
uint8_t fontColor = s_port->get_color_font();
|
||||
for (uint16_t y = topY; y < bottomY; y++)
|
||||
{
|
||||
for (uint16_t x = leftX; x < rightX; x++)
|
||||
{
|
||||
s_port->set_pixel(x, y, backColor);
|
||||
}
|
||||
}
|
||||
|
||||
s_port->line_h(leftX, rightX, topY, 1, fontColor);
|
||||
s_port->line_v(topY, bottomY, leftX, 1, fontColor);
|
||||
s_port->line_h(leftX, rightX + 1, bottomY, 1, fontColor);
|
||||
s_port->line_v(topY, bottomY, rightX, 1, fontColor);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_UpdateSelectionNewLevel
|
||||
* 功能:
|
||||
* 在“进入新层级”场景下,重绘目标层级窗口内容(边框 + 文本)
|
||||
* 并对当前选中项执行反显高亮。
|
||||
*
|
||||
* 参数:
|
||||
* view - 菜单视图对象,提供底层渲染接口
|
||||
* currentLevel - 当前层级的“层级框拥有者”节点,提供窗口矩形与数量信息
|
||||
* nextLevel - 当前层级内的选中节点(也是该层列表起始遍历节点)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针判定,调用方需保证 view/currentLevel/nextLevel 有效。
|
||||
* - 默认要求 currentLevel 与 nextLevel 来自同一层级链路,且 behind 链可遍历。
|
||||
* - 当 currentLevel->rect.wNum 为 0 时,文本循环不执行,
|
||||
* 但边框仍会被绘制。
|
||||
*
|
||||
* 说明:
|
||||
* - 绘制步骤:
|
||||
* 1) 使用 currentLevel->rect 绘制层级边框区域
|
||||
* 2) 从 nextLevel 起,按 wNum 数量沿 behind 链逐项绘制名称
|
||||
* 3) 基于 nextLevel 的 wPos 计算行区域并执行 invert 高亮
|
||||
* - 坐标策略:
|
||||
* wPosX = currentLevel->rect.wSPosX + 4(文本左内边距)
|
||||
* wPosY = currentLevel->rect.wSPosY + item->rect.wPos * LINE_HEIGHT + 3
|
||||
* - 该函数对应“新层级内容补绘”路径,与同层移动的局部反显函数职责分离。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static void MenuView_UpdateSelectionNewLevel(MenuView *view, const tagPMenuItem currentLevel, const tagPMenuItem nextLevel)
|
||||
{
|
||||
tagPMenuItem ptIndex;
|
||||
|
||||
uint16_t wPosX;
|
||||
uint16_t wPosY;
|
||||
/* 1. 绘制当前层级的边界框 */
|
||||
MenuView_DrawBoundaryBox(view, currentLevel->rect.wSPosX, currentLevel->rect.wSPosY, currentLevel->rect.wEPosX, currentLevel->rect.wEPosY);
|
||||
/*2. 获取当前层级选中菜单项,并计算X起始坐标*/
|
||||
ptIndex = nextLevel;
|
||||
/* 计算当前菜单项的 X 坐标,对于下一层级起始 X 坐标都是一致的 */
|
||||
wPosX = currentLevel->rect.wSPosX + 4;
|
||||
|
||||
/* 3. 绘制当前层级的所有菜单项 */
|
||||
for (uint16_t index = 0; index < currentLevel->rect.wNum; index++)
|
||||
{
|
||||
wPosY = currentLevel->rect.wSPosY + ptIndex->rect.wPos * LINE_HEIGHT + 3;
|
||||
s_port->show_str(wPosX, wPosY, ptIndex->menuDef.byName);
|
||||
/* 移动到下一个菜单项 */
|
||||
ptIndex = ptIndex->links.behind;
|
||||
}
|
||||
|
||||
/* 4. 绘制当前选中项的反显 */
|
||||
wPosY = currentLevel->rect.wSPosY + nextLevel->rect.wPos * LINE_HEIGHT + 3;
|
||||
s_port->invert(currentLevel->rect.wSPosX + 2, wPosY - 1, currentLevel->rect.wEPosX - 2, wPosY + LINE_HEIGHT);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_ShowTopLevel
|
||||
* 功能:
|
||||
* 绘制菜单页顶部区域(0 级标题栏):清空标题背景、绘制装饰线,
|
||||
* 并按传入模式显示顶部提示文案。
|
||||
*
|
||||
* 参数:
|
||||
* view - 菜单视图对象,提供渲染端口能力
|
||||
* mode - 当前需要显示的运行模式(MenuMode)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做 view/port 空指针校验,调用方需保证对象已正确初始化。
|
||||
* - 模式值若不在已定义枚举分支内,走默认分支显示“当前模式: 无模式”。
|
||||
*
|
||||
* 说明:
|
||||
* - 先清空顶部区域(y: 0~32),避免旧文案残留。
|
||||
* - 再调用 MenuView_DrawMeitou() 绘制标题装饰线,保持页面视觉一致性。
|
||||
* - 模式文案映射:
|
||||
* MODE_OVERFLOW_PROTECTION -> "当前模式: 过流保护"
|
||||
* MODE_LOCAL_FEEDER_SEGMENT -> "就地馈线: 分段模式"
|
||||
* MODE_LOCAL_FEEDER_CONTACT -> "就地馈线: 联络模式"
|
||||
* 其他/未设置 -> "当前模式: 无模式"
|
||||
* - 本函数只负责“顶部区域”渲染,不处理菜单列表区内容刷新。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuView_ShowTopLevel(MenuView *view, MenuMode mode)
|
||||
{
|
||||
uint16_t lcdSizeX = s_port->get_size_x();
|
||||
s_port->fill_rect(0, 0, lcdSizeX - 1, 32, s_port->get_color_back());
|
||||
MenuView_DrawMeitou(view, 16, 2);
|
||||
/* 在 0 级菜单标题栏下方显示当前运行模式提示文字 */
|
||||
if (mode == MODE_OVERFLOW_PROTECTION)
|
||||
{
|
||||
s_port->show_str(16, 20, (uint8_t *)"当前模式: 过流保护");
|
||||
}
|
||||
else if (mode == MODE_LOCAL_FEEDER_SEGMENT)
|
||||
{
|
||||
s_port->show_str(16, 20, (uint8_t *)"就地馈线: 分段模式");
|
||||
}
|
||||
else if (mode == MODE_LOCAL_FEEDER_CONTACT)
|
||||
{
|
||||
s_port->show_str(16, 20, (uint8_t *)"就地馈线: 联络模式");
|
||||
}
|
||||
else
|
||||
{
|
||||
s_port->show_str(16, 20, (uint8_t *)"当前模式: 无模式");
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_UpdateSelectionSameLevel
|
||||
* 功能:
|
||||
* 在“同层同父节点”移动场景下执行局部刷新,仅更新旧/新选中行的反显状态,
|
||||
* 避免整块区域重绘带来的闪烁。
|
||||
*
|
||||
* 参数:
|
||||
* view - 菜单视图对象,提供底层 invert 绘制能力
|
||||
* oldItem - 变更前的选中菜单项(旧焦点)
|
||||
* newItem - 变更后的选中菜单项(新焦点)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针与父链校验,调用方需保证:
|
||||
* 1) view/s_port 有效
|
||||
* 2) oldItem/newItem 非空
|
||||
* 3) oldItem->links.higher 非空且与 newItem 属于同一父节点
|
||||
* - 若上述前置条件不满足,坐标计算与反显结果将不可预期。
|
||||
*
|
||||
* 说明:
|
||||
* - 该函数是 Presenter 在“同层同父移动”场景(典型 U/D)的优化渲染路径。
|
||||
* - 通过两次 invert 完成“旧选中恢复 + 新选中高亮”:
|
||||
* 1) 对旧行区域 invert:撤销旧高亮
|
||||
* 2) 对新行区域 invert:施加新高亮
|
||||
* - 坐标基准来自 oldItem 的父节点(levelRectOwner = oldItem->links.higher):
|
||||
* posY = levelRectOwner->rect.wSPosY + (item->rect.wPos - 1) * LINE_HEIGHT + 3
|
||||
* - X 方向使用边框内缩(+2/-2),避免覆盖边框线条,保持视觉完整性。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuView_UpdateSelectionSameLevel(MenuView *view, const tagPMenuItem oldItem, const tagPMenuItem newItem)
|
||||
{
|
||||
tagPMenuItem levelRectOwner = oldItem->links.higher;
|
||||
|
||||
uint16_t leftX = (uint16_t)(levelRectOwner->rect.wSPosX + 2);
|
||||
uint16_t rightX = (uint16_t)(levelRectOwner->rect.wEPosX - 2);
|
||||
uint16_t oldPosY = (uint16_t)(levelRectOwner->rect.wSPosY + oldItem->rect.wPos * LINE_HEIGHT + 3);
|
||||
uint16_t newPosY = (uint16_t)(levelRectOwner->rect.wSPosY + newItem->rect.wPos * LINE_HEIGHT + 3);
|
||||
|
||||
/* 旧选中行反显恢复 + 新选中行反显高亮 */
|
||||
s_port->invert(leftX, (uint16_t)(oldPosY - 1), rightX, (uint16_t)(oldPosY + LINE_HEIGHT));
|
||||
s_port->invert(leftX, (uint16_t)(newPosY - 1), rightX, (uint16_t)(newPosY + LINE_HEIGHT));
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_FullRefresh
|
||||
* 功能:
|
||||
* 执行菜单页整页刷新:清空工作区、重绘顶部标题区域、按当前层级路径重绘各级菜单框。
|
||||
*
|
||||
* 参数:
|
||||
* view - 菜单视图对象,提供布局参数与绘制能力
|
||||
* menuCtrl - 菜单运行时控制结构,提供当前层级与路由链信息
|
||||
* mode - 顶部标题显示使用的运行模式
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不做空指针校验,调用方需保证 view/menuCtrl/s_port 均有效。
|
||||
* - 当 byClass 为 0 时,for 循环不进入,仅刷新顶部区域。
|
||||
* - 默认要求 ptRoute[index] 与 ptRoute[index + 1] 在有效层级范围内可访问。
|
||||
*
|
||||
* 说明:
|
||||
* - 刷新步骤:
|
||||
* 1) 使用背景色清空菜单工作区域(MENU_YMIN ~ menuYMax)
|
||||
* 2) 调用 MenuView_ShowTopLevel() 重绘顶部模式文案与装饰
|
||||
* 3) 按当前菜单层级(byClass)逐层调用 MenuView_UpdateSelectionNewLevel()
|
||||
* 重绘各层边框、文本和选中态
|
||||
* - 该函数用于“整页一致性恢复”场景(如跨层回退、顶层切换等)。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void MenuView_FullRefresh(MenuView *view, const tagMenuCtrl *menuCtrl, MenuMode mode)
|
||||
{
|
||||
uint8_t backColor = s_port->get_color_back();
|
||||
uint16_t lcdSizeX = s_port->get_size_x();
|
||||
|
||||
s_port->fill_rect(0, MENU_YMIN, lcdSizeX - 1, MENU_YMAX, backColor);
|
||||
|
||||
MenuView_ShowTopLevel(view, mode);
|
||||
for (uint8_t index = 0; index < menuCtrl->ptCurrent->menuDef.byClass; index++)
|
||||
{
|
||||
MenuView_UpdateSelectionNewLevel(view, menuCtrl->ptRoute[index], menuCtrl->ptRoute[index + 1]);
|
||||
}
|
||||
}
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: MenuView_Init
|
||||
* 功能:
|
||||
* 初始化菜单视图对象 MenuView,准备渲染端口引用与布局参数,
|
||||
* 并设置当前模式状态的默认值以及渲染入口函数指针。
|
||||
*
|
||||
* 参数:
|
||||
* view - 菜单视图实例地址(由调用方分配,函数内填充其字段)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数不校验 view 空指针,调用方需保证 view 非空。
|
||||
* - 渲染端口的物理 init 不在视图内执行;
|
||||
* 由入口(main)统一完成屏幕初始化(符合 PageRenderPort 共享策略)。
|
||||
*
|
||||
* 说明:
|
||||
* - 初始化内容包含:
|
||||
* 1) s_port = PageRenderer_Lcd():绑定 LCD 渲染端口表指针
|
||||
* 4) 设置当前模式为 MODE_NONE,避免进入绘制前读取未定义值
|
||||
* 5) 视图层仅保留绘制原子动作,刷新策略由 Presenter 调度
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void MenuView_Init(MenuView *view)
|
||||
{
|
||||
s_port = PageRenderer_Lcd();
|
||||
view->update_selection_new_level = MenuView_UpdateSelectionNewLevel;
|
||||
view->update_selection_same_level = MenuView_UpdateSelectionSameLevel;
|
||||
view->full_refresh = MenuView_FullRefresh;
|
||||
}
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
const PageRenderPort *MenuView_GetPortForTest(void)
|
||||
{
|
||||
return s_port;
|
||||
}
|
||||
#endif
|
||||
|
||||
44
src/Drv/pages/menu/view.h
Normal file
44
src/Drv/pages/menu/view.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef MENU_VIEW_H
|
||||
#define MENU_VIEW_H
|
||||
|
||||
#include "types.h"
|
||||
#include "display.h"
|
||||
#include "def.h"
|
||||
#include "../global/renderer_lcd.h"
|
||||
|
||||
|
||||
|
||||
|
||||
typedef struct menu_view_t menu_view_t;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: menu_view_t
|
||||
* 功能:
|
||||
* 菜单页面 View 层对象,封装对外绘制接口函数指针。
|
||||
*
|
||||
* 字段说明:
|
||||
* update_selection_new_level - 新层级切换时的层级窗口重绘与高亮更新
|
||||
* update_selection_same_level - 同层移动时的局部反显更新(降低闪烁)
|
||||
* full_refresh - 整页刷新入口(顶部 + 多层菜单内容)
|
||||
*
|
||||
* 说明:
|
||||
* - Presenter 通过该结构体调用 View 能力,避免直接依赖 view.c 内部实现细节。
|
||||
* - 函数指针在 MenuView_Init() 中完成绑定。
|
||||
* ------------------------------------------------------------------------- */
|
||||
struct menu_view_t
|
||||
{
|
||||
void (*update_selection_new_level)(struct menu_view_t *self, const tagPMenuItem currentLevel, const tagPMenuItem nextLevel);
|
||||
void (*update_selection_same_level)(struct menu_view_t *self, const tagPMenuItem oldItem, const tagPMenuItem newItem);
|
||||
void (*full_refresh)(struct menu_view_t *self, const tagMenuCtrl *menuCtrl, MenuMode mode);
|
||||
};
|
||||
|
||||
typedef menu_view_t MenuView;
|
||||
|
||||
|
||||
void MenuView_Init(MenuView *view);
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
const PageRenderPort *MenuView_GetPortForTest(void);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
125
src/Drv/pages/page.h
Normal file
125
src/Drv/pages/page.h
Normal file
@@ -0,0 +1,125 @@
|
||||
#ifndef PAGE_H
|
||||
#define PAGE_H
|
||||
|
||||
#include "types.h"
|
||||
/* -------------------------------------------------------------------------
|
||||
* 枚举名: page_id_t
|
||||
* 作用:
|
||||
* 定义系统内可导航页面的逻辑标识(Page ID)。
|
||||
*
|
||||
* 取值说明:
|
||||
* PAGE_ID_NONE - 无效页面 ID / 未初始化占位值
|
||||
* PAGE_ID_MENU - 菜单页 ID(当前主运行页)
|
||||
* PAGE_ID_APP_INFO - 预留页面 ID(当前版本可注册与否由上层决定)
|
||||
* PAGE_ID_MAX - 上界哨兵,不可作为有效页面 ID 使用
|
||||
*
|
||||
* 使用约束:
|
||||
* - PageManager_Register() 会校验 page_id,PAGE_ID_NONE 与 >= PAGE_ID_MAX
|
||||
* 均视为非法值。
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef enum
|
||||
{
|
||||
PAGE_ID_NONE = 0,
|
||||
PAGE_ID_MENU = 1,
|
||||
PAGE_ID_APP_INFO = 2, /* 预留ID:当前版本未注册运行 */
|
||||
PAGE_ID_MAX
|
||||
} page_id_t;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 枚举名: page_event_type_t
|
||||
* 作用:
|
||||
* 定义页面层可分发的事件类型。
|
||||
*
|
||||
* 当前约定:
|
||||
* PAGE_EVENT_KEY - 按键输入事件
|
||||
*
|
||||
* 扩展说明:
|
||||
* - 后续可按需扩展触摸、定时器、通信消息等事件类型。
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef enum
|
||||
{
|
||||
PAGE_EVENT_KEY = 1
|
||||
} page_event_type_t;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: input_event_t
|
||||
* 作用:
|
||||
* 页面事件分发的数据载体,由 PageManager_DispatchEvent() 传入页面 on_event。
|
||||
*
|
||||
* 字段说明:
|
||||
* type - 事件类型,取值来自 page_event_type_t
|
||||
* keyVal - 按键值(当 type 为 PAGE_EVENT_KEY 时有效)
|
||||
*
|
||||
* 使用约束:
|
||||
* - 调用方应保证 type/keyVal 与事件来源一致;
|
||||
* - 页面 on_event 可基于该结构返回 EVENT_HANDLED / EVENT_UNHANDLED。
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef struct
|
||||
{
|
||||
uint8_t type;
|
||||
uint8_t keyVal;
|
||||
} input_event_t;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 枚举名: event_result_t
|
||||
* 作用:
|
||||
* 定义页面事件处理结果,用于控制事件链是否继续兜底处理。
|
||||
*
|
||||
* 取值说明:
|
||||
* EVENT_UNHANDLED - 页面未消费事件;允许交由管理器执行全局兜底逻辑
|
||||
* EVENT_HANDLED - 页面已消费事件;事件链终止
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef enum
|
||||
{
|
||||
EVENT_UNHANDLED = 0,
|
||||
EVENT_HANDLED = 1
|
||||
} event_result_t;
|
||||
|
||||
/* 前置声明:支持在回调签名中使用 page_t* */
|
||||
typedef struct page_t page_t;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: page_t
|
||||
* 作用:
|
||||
* 页面抽象基元,描述“一个可被 PageManager 管理的页面实例”。
|
||||
*
|
||||
* 字段分组:
|
||||
* 1) 基础元信息
|
||||
* page_id - 页面逻辑标识
|
||||
* is_cached - 是否缓存页(1: Pop 后不销毁;0: Pop 后可销毁)
|
||||
* is_created - 是否已执行过 on_create(由管理器维护)
|
||||
*
|
||||
* 2) 生命周期与事件回调
|
||||
* on_create - 首次创建时调用
|
||||
* on_enter - 页面进入前台时调用
|
||||
* on_exit - 页面离开前台时调用
|
||||
* on_destroy - 页面销毁时调用(常用于非缓存页)
|
||||
* on_event - 输入事件处理回调
|
||||
* on_loop - 周期循环回调
|
||||
*
|
||||
* 3) 三层对象挂载指针(MVP)
|
||||
* presenter / view / model - 页面内部层对象地址(可为空,按页面实现决定)
|
||||
*
|
||||
* 生命周期约定(由 PageManager 驱动):
|
||||
* - Push 到新页面时:旧页 on_exit -> 新页(必要时 on_create)-> 新页 on_enter
|
||||
* - Pop 回退时:当前页 on_exit ->(非缓存页可 on_destroy)-> 新栈顶 on_enter
|
||||
* ------------------------------------------------------------------------- */
|
||||
struct page_t
|
||||
{
|
||||
page_id_t page_id;
|
||||
uint8_t is_cached;
|
||||
uint8_t is_created;
|
||||
|
||||
void (*on_create)(page_t *page);
|
||||
void (*on_enter)(page_t *page);
|
||||
void (*on_exit)(page_t *page);
|
||||
void (*on_destroy)(page_t *page);
|
||||
event_result_t (*on_event)(page_t *page, input_event_t *event);
|
||||
void (*on_loop)(page_t *page);
|
||||
|
||||
void *presenter;
|
||||
void *view;
|
||||
void *model;
|
||||
};
|
||||
|
||||
#endif
|
||||
448
src/Drv/pages/page_manager.c
Normal file
448
src/Drv/pages/page_manager.c
Normal file
@@ -0,0 +1,448 @@
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "page_manager.h"
|
||||
#include "../key.h"
|
||||
|
||||
/* 默认全局页面管理器实例(便于主流程直接使用) */
|
||||
static page_manager_t s_pageManager;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_GlobalEventHandle
|
||||
* 功能:
|
||||
* 页面管理器的全局事件兜底处理入口,用于处理未被页面消费的通用按键逻辑。
|
||||
*
|
||||
* 参数:
|
||||
* event - 输入事件指针
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 event 为 NULL,函数直接返回。
|
||||
* - 若事件类型不是 PAGE_EVENT_KEY,函数直接返回,不做处理。
|
||||
* - 仅当 keyVal 为 KEY_ESC 时触发返回上一页,其它按键忽略。
|
||||
*
|
||||
* 说明:
|
||||
* - 该函数通常由 PageManager_DispatchEvent 在页面返回
|
||||
* EVENT_UNHANDLED 时调用。
|
||||
* - 当前全局策略为“ESC = 返回”:内部通过 PageManager_Pop()
|
||||
* 执行页面栈回退。
|
||||
* - 设计目标是在不侵入页面私有逻辑的前提下,提供统一系统级快捷行为。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void PageManager_GlobalEventHandle(input_event_t *event)
|
||||
{
|
||||
ASSERT((event == NULL) || (event->type != PAGE_EVENT_KEY));
|
||||
if ((event == NULL) || (event->type != PAGE_EVENT_KEY))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->keyVal == KEY_ESC)
|
||||
{
|
||||
(void)PageManager_Pop();
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_Init
|
||||
* 功能:
|
||||
* 初始化单例页面管理器 s_pageManager,使其进入“空栈、可注册”的初始状态。
|
||||
*
|
||||
* 参数:
|
||||
* 无
|
||||
*
|
||||
* 边界处理:
|
||||
* - 本函数无外部入参,不涉及空指针校验。
|
||||
* - 通过 memset 全量清零,确保历史运行残留状态(栈、注册表、计数)被彻底复位。
|
||||
*
|
||||
* 说明:
|
||||
* - stack_top 采用 -1 表示页面栈为空,这是后续 Push/GetTop/Depth 的统一判定基准。
|
||||
* - 该函数通常在系统启动阶段调用一次;若重复调用,将清空当前页面管理上下文。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void PageManager_Init(void)
|
||||
{
|
||||
memset(&s_pageManager, 0, sizeof(s_pageManager));
|
||||
s_pageManager.stack_top = -1;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_Register
|
||||
* 功能:
|
||||
* 将页面对象注册到页面管理器的注册表中,供后续按 page_id 查找与导航。
|
||||
*
|
||||
* 参数:
|
||||
* page - 待注册页面指针
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 page 为 NULL,返回 PAGE_MANAGER_ERR_NULL_PARAM,不执行注册。
|
||||
* - 若 page_id 非法(PAGE_ID_NONE 或超出 PAGE_ID_MAX),返回 PAGE_MANAGER_ERR_INVALID_ID。
|
||||
* - 若注册表已满(page_count >= MAX_PAGE_COUNT),返回 PAGE_MANAGER_ERR_REGISTRY_FULL。
|
||||
*
|
||||
* 说明:
|
||||
* - 若检测到相同 page_id 已存在,不新增条目,而是覆盖旧指针(热更新语义)。
|
||||
* - 新页面按 page_count 尾插,成功后 page_count 自增 1。
|
||||
*
|
||||
* 返回值:
|
||||
* - PAGE_MANAGER_OK : 注册成功(含覆盖更新成功)
|
||||
* - PAGE_MANAGER_ERR_NULL_PARAM : 参数无效
|
||||
* - PAGE_MANAGER_ERR_INVALID_ID : page_id 非法
|
||||
* - PAGE_MANAGER_ERR_REGISTRY_FULL : 注册表容量不足
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_manager_result_t PageManager_Register(page_t *page)
|
||||
{
|
||||
ASSERT(page == NULL);
|
||||
if (page == NULL)
|
||||
{
|
||||
return PAGE_MANAGER_ERR_NULL_PARAM;
|
||||
}
|
||||
ASSERT((page->page_id == PAGE_ID_NONE) || (page->page_id >= PAGE_ID_MAX));
|
||||
if ((page->page_id == PAGE_ID_NONE) || (page->page_id >= PAGE_ID_MAX))
|
||||
{
|
||||
return PAGE_MANAGER_ERR_INVALID_ID;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < s_pageManager.page_count; i++)
|
||||
{
|
||||
/* 支持重复注册:同 page_id 视为更新页面指针 */
|
||||
if (s_pageManager.page_registry[i]->page_id == page->page_id)
|
||||
{
|
||||
s_pageManager.page_registry[i] = page;
|
||||
return PAGE_MANAGER_OK;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(s_pageManager.page_count >= MAX_PAGE_COUNT);
|
||||
if (s_pageManager.page_count >= MAX_PAGE_COUNT)
|
||||
{
|
||||
return PAGE_MANAGER_ERR_REGISTRY_FULL;
|
||||
}
|
||||
|
||||
s_pageManager.page_registry[s_pageManager.page_count] = page;
|
||||
s_pageManager.page_count++;
|
||||
return PAGE_MANAGER_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_Find
|
||||
* 功能:
|
||||
* 在管理器的页面注册表中按 page_id 线性查找,返回对应的 page_t 指针。
|
||||
*
|
||||
* 参数:
|
||||
* pageId - 待查找的页面标识
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若注册表为空(page_count 为 0),循环不执行,返回 NULL。
|
||||
* - 若不存在匹配的 page_id,返回 NULL。
|
||||
*
|
||||
* 说明:
|
||||
* - 查找顺序与注册顺序一致;同 id 在 Register 中已保证唯一或覆盖更新。
|
||||
* - 常与 PageManager_Navigate 配合:先 Find 再 Push,上层也可直接 Find 获取页面对象。
|
||||
*
|
||||
* 返回值:
|
||||
* - 命中时返回注册表中的 page_t 指针
|
||||
* - 未命中返回 NULL
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_t *PageManager_Find(page_id_t pageId)
|
||||
{
|
||||
for (uint8_t i = 0; i < s_pageManager.page_count; i++)
|
||||
{
|
||||
if (s_pageManager.page_registry[i]->page_id == pageId)
|
||||
{
|
||||
return s_pageManager.page_registry[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_GetTop
|
||||
* 功能:
|
||||
* 返回当前页面栈栈顶元素,即当前“前台/可见”页面对应的 page_t 指针。
|
||||
*
|
||||
* 参数:
|
||||
* 无
|
||||
*
|
||||
* 边界处理:
|
||||
* - stack_top < 0 表示空栈,返回 NULL。
|
||||
* - 正常栈顶索引由 Push/Pop 维护,假定栈槽位与 stack_top 一致。
|
||||
*
|
||||
* 说明:
|
||||
* - DispatchEvent、Loop、Navigate 等逻辑均以栈顶页为“当前页”。
|
||||
* - 不修改栈状态,仅查询。
|
||||
*
|
||||
* 返回值:
|
||||
* - 非空栈时返回栈顶 page_t 指针
|
||||
* - 空栈时返回 NULL
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_t *PageManager_GetTop(void)
|
||||
{
|
||||
if (s_pageManager.stack_top < 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return s_pageManager.page_stack[s_pageManager.stack_top];
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_Push
|
||||
* 功能:
|
||||
* 将指定页面压入页面栈并切换为当前页:必要时触发旧页 on_exit、新页 on_create/on_enter。
|
||||
*
|
||||
* 参数:
|
||||
* newPage - 要入栈并成为栈顶的目标页面指针
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 newPage 为 NULL,返回 PAGE_MANAGER_ERR_NULL_PARAM。
|
||||
* - 若栈已满(stack_top >= MAX_PAGE_STACK_DEPTH - 1),返回 PAGE_MANAGER_ERR_STACK_FULL。
|
||||
* - 若当前栈顶与 newPage 的 page_id 相同,视为重复导航,直接返回 PAGE_MANAGER_OK,
|
||||
* 不再次调用 on_exit/on_enter(幂等)。
|
||||
*
|
||||
* 说明:
|
||||
* - 切换顺序:旧栈顶 on_exit(若存在)-> 新页入栈 ->
|
||||
* 若 is_created 为 0 则 on_create 并置 is_created,再 on_enter。
|
||||
* - 页面“创建一次”语义由 is_created 控制;非缓存页在 Pop 时可销毁并重置 is_created。
|
||||
*
|
||||
* 返回值:
|
||||
* - PAGE_MANAGER_OK : 成功(含幂等重复入同一逻辑页)
|
||||
* - PAGE_MANAGER_ERR_NULL_PARAM : 参数无效
|
||||
* - PAGE_MANAGER_ERR_STACK_FULL : 栈满
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_manager_result_t PageManager_Push(page_t *newPage)
|
||||
{
|
||||
page_t *currentTop;
|
||||
ASSERT(newPage == NULL);
|
||||
if (newPage == NULL)
|
||||
{
|
||||
return PAGE_MANAGER_ERR_NULL_PARAM;
|
||||
}
|
||||
ASSERT(s_pageManager.stack_top >= (MAX_PAGE_STACK_DEPTH - 1));
|
||||
if (s_pageManager.stack_top >= (MAX_PAGE_STACK_DEPTH - 1))
|
||||
{
|
||||
return PAGE_MANAGER_ERR_STACK_FULL;
|
||||
}
|
||||
|
||||
currentTop = PageManager_GetTop();
|
||||
ASSERT((currentTop != NULL) && (currentTop->page_id == newPage->page_id));
|
||||
if ((currentTop != NULL) && (currentTop->page_id == newPage->page_id))
|
||||
{
|
||||
/* 重复导航到当前页面:按幂等处理 */
|
||||
return PAGE_MANAGER_OK;
|
||||
}
|
||||
ASSERT((currentTop != NULL) && (currentTop->on_exit != NULL));
|
||||
if ((currentTop != NULL) && (currentTop->on_exit != NULL))
|
||||
{
|
||||
currentTop->on_exit(currentTop);
|
||||
}
|
||||
|
||||
s_pageManager.stack_top++;
|
||||
s_pageManager.page_stack[s_pageManager.stack_top] = newPage;
|
||||
|
||||
if (!newPage->is_created)
|
||||
{
|
||||
/* 页面只创建一次(除非在 Pop 时被销毁并重置) */
|
||||
if (newPage->on_create != NULL)
|
||||
{
|
||||
newPage->on_create(newPage);
|
||||
}
|
||||
newPage->is_created = 1;
|
||||
}
|
||||
|
||||
if (newPage->on_enter != NULL)
|
||||
{
|
||||
newPage->on_enter(newPage);
|
||||
}
|
||||
return PAGE_MANAGER_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_Pop
|
||||
* 功能:
|
||||
* 弹出当前栈顶页面并回到下一层:触发当前页 on_exit,非缓存页可 on_destroy,
|
||||
* 再对新栈顶调用 on_enter。
|
||||
*
|
||||
* 参数:
|
||||
* 无
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 stack_top <= 0(仅余底页或空栈),不允许继续弹出,
|
||||
* 返回 PAGE_MANAGER_ERR_STACK_BOTTOM。
|
||||
* - 当前页指针为 NULL 时跳过对应回调(防御性,正常不应出现)。
|
||||
*
|
||||
* 说明:
|
||||
* - 设计为至少保留栈底一页:不能无限 Pop 到空栈,故 stack_top <= 0 即失败。
|
||||
* - 非缓存页(is_cached == 0)在 Pop 后会 on_destroy 且 is_created 清零,下次 Push 会重新 on_create。
|
||||
* - 缓存页仅 on_exit,不销毁,便于保留状态。
|
||||
* - 全局 ESC 兜底通过本函数回退上一页。
|
||||
*
|
||||
* 返回值:
|
||||
* - PAGE_MANAGER_OK : 出栈成功,新栈顶已 on_enter(若实现)
|
||||
* - PAGE_MANAGER_ERR_STACK_BOTTOM : 无法弹出(已到底或空栈)
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_manager_result_t PageManager_Pop(void)
|
||||
{
|
||||
page_t *currentPage;
|
||||
page_t *newTop;
|
||||
|
||||
ASSERT(s_pageManager.stack_top <= 0);
|
||||
if (s_pageManager.stack_top <= 0)
|
||||
{
|
||||
return PAGE_MANAGER_ERR_STACK_BOTTOM;
|
||||
}
|
||||
|
||||
currentPage = s_pageManager.page_stack[s_pageManager.stack_top];
|
||||
ASSERT((currentPage != NULL) && (currentPage->on_exit != NULL));
|
||||
if ((currentPage != NULL) && (currentPage->on_exit != NULL))
|
||||
{
|
||||
currentPage->on_exit(currentPage);
|
||||
}
|
||||
|
||||
if ((currentPage != NULL) && !currentPage->is_cached)
|
||||
{
|
||||
/* 非缓存页离开即销毁,下次进入会重新 on_create */
|
||||
if (currentPage->on_destroy != NULL)
|
||||
{
|
||||
currentPage->on_destroy(currentPage);
|
||||
}
|
||||
currentPage->is_created = 0;
|
||||
}
|
||||
|
||||
s_pageManager.page_stack[s_pageManager.stack_top] = NULL;
|
||||
s_pageManager.stack_top--;
|
||||
|
||||
newTop = s_pageManager.page_stack[s_pageManager.stack_top];
|
||||
ASSERT((newTop != NULL) && (newTop->on_enter != NULL));
|
||||
if ((newTop != NULL) && (newTop->on_enter != NULL))
|
||||
{
|
||||
newTop->on_enter(newTop);
|
||||
}
|
||||
return PAGE_MANAGER_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_Navigate
|
||||
* 功能:
|
||||
* 根据目标 page_id 执行页面导航:先在注册表中查找页面,再将其压入页面栈。
|
||||
*
|
||||
* 参数:
|
||||
* pageId - 目标页面标识
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若注册表中未找到对应 pageId,返回 PAGE_MANAGER_ERR_NOT_FOUND,
|
||||
* 不执行页面切换。
|
||||
* - 目标页找到后,Push 过程中的边界条件(如栈满、重复入栈等)
|
||||
* 由 PageManager_Push 统一处理并返回对应错误码。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数是“按 ID 导航”的统一入口,避免上层直接操作 page_t 指针。
|
||||
* - 导航成功后将触发标准生命周期链路:
|
||||
* 旧页 on_exit -> 新页(必要时 on_create)-> 新页 on_enter。
|
||||
*
|
||||
* 返回值:
|
||||
* - PAGE_MANAGER_OK : 导航成功
|
||||
* - PAGE_MANAGER_ERR_NOT_FOUND : 未找到目标页面
|
||||
* - 其他 : 透传 PageManager_Push 的返回码(如栈满等)
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_manager_result_t PageManager_Navigate(page_id_t pageId)
|
||||
{
|
||||
page_t *target;
|
||||
target = PageManager_Find(pageId);
|
||||
ASSERT(target == NULL);
|
||||
if (target == NULL)
|
||||
{
|
||||
return PAGE_MANAGER_ERR_NOT_FOUND;
|
||||
}
|
||||
return PageManager_Push(target);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_DispatchEvent
|
||||
* 功能:
|
||||
* 将输入事件分发到当前栈顶页面,并在页面未处理时执行全局兜底处理。
|
||||
*
|
||||
* 参数:
|
||||
* event - 待分发的输入事件指针(键值、事件类型等)
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若 event 为 NULL,返回 PAGE_MANAGER_ERR_NULL_PARAM,不进行任何处理。
|
||||
* - 若当前页面栈为空(GetTop 返回 NULL),返回 PAGE_MANAGER_ERR_NO_ACTIVE_PAGE。
|
||||
* - 若栈顶页面未实现 on_event 回调,则视为未处理并进入兜底判定流程。
|
||||
*
|
||||
* 说明:
|
||||
* - 分发优先级:页面私有事件处理 > 管理器全局兜底处理。
|
||||
* - top->on_event 返回 EVENT_UNHANDLED 时,才调用
|
||||
* PageManager_GlobalEventHandle(event)。
|
||||
* - 该机制允许页面覆盖默认按键行为,同时保留统一的系统级快捷逻辑
|
||||
* (例如 ESC 返回上页)。
|
||||
*
|
||||
* 返回值:
|
||||
* - PAGE_MANAGER_OK : 分发流程执行完成
|
||||
* - PAGE_MANAGER_ERR_NULL_PARAM : 事件参数为空
|
||||
* - PAGE_MANAGER_ERR_NO_ACTIVE_PAGE : 当前无活动页面
|
||||
* ------------------------------------------------------------------------- */
|
||||
page_manager_result_t PageManager_DispatchEvent(input_event_t *event)
|
||||
{
|
||||
page_t *top;
|
||||
event_result_t result;
|
||||
|
||||
ASSERT(event == NULL);
|
||||
if (event == NULL)
|
||||
{
|
||||
return PAGE_MANAGER_ERR_NULL_PARAM;
|
||||
}
|
||||
|
||||
top = PageManager_GetTop();
|
||||
ASSERT(top == NULL);
|
||||
if (top == NULL)
|
||||
{
|
||||
return PAGE_MANAGER_ERR_NO_ACTIVE_PAGE;
|
||||
}
|
||||
|
||||
result = EVENT_UNHANDLED;
|
||||
if (top->on_event != NULL)
|
||||
{
|
||||
result = top->on_event(top, event);
|
||||
}
|
||||
|
||||
/* 页面未处理时交给管理器处理全局快捷行为 */
|
||||
if (result == EVENT_UNHANDLED)
|
||||
{
|
||||
PageManager_GlobalEventHandle(event);
|
||||
}
|
||||
return PAGE_MANAGER_OK;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 函数名: PageManager_Loop
|
||||
* 功能:
|
||||
* 驱动当前栈顶页面的循环回调(on_loop),用于执行周期性任务。
|
||||
*
|
||||
* 参数:
|
||||
* 无
|
||||
*
|
||||
* 边界处理:
|
||||
* - 若当前页面栈为空(GetTop 返回 NULL),函数直接返回。
|
||||
* - 若栈顶页面未实现 on_loop 回调,函数直接返回。
|
||||
* - 通过双重判空避免空指针调用导致崩溃。
|
||||
*
|
||||
* 说明:
|
||||
* - 本函数只处理“当前可见页面”(栈顶页),不遍历其它页面。
|
||||
* - 典型调用位置是主循环中的固定节拍点(例如每 N ms 调用一次)。
|
||||
* - 页面切换、事件分发不在本函数处理范围内,分别由 Navigate/Push/Pop、
|
||||
* DispatchEvent 负责。
|
||||
*
|
||||
* 返回值:
|
||||
* - 无
|
||||
* ------------------------------------------------------------------------- */
|
||||
void PageManager_Loop(void)
|
||||
{
|
||||
page_t *top = PageManager_GetTop();
|
||||
ASSERT((top == NULL) || (top->on_loop == NULL));
|
||||
if ((top != NULL) && (top->on_loop != NULL))
|
||||
{
|
||||
top->on_loop(top);
|
||||
}
|
||||
}
|
||||
77
src/Drv/pages/page_manager.h
Normal file
77
src/Drv/pages/page_manager.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#ifndef PAGE_MANAGER_H
|
||||
#define PAGE_MANAGER_H
|
||||
|
||||
#include "page.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 结构体名: page_manager_t
|
||||
* 作用:
|
||||
* 页面管理器运行时上下文,维护“页面栈 + 页面注册表”两套核心状态。
|
||||
*
|
||||
* 设计说明:
|
||||
* - page_stack:
|
||||
* 运行时导航栈,表示当前页面跳转路径。
|
||||
* 栈顶元素是当前可见/可交互页面。
|
||||
* - stack_top:
|
||||
* 栈顶索引。约定 -1 表示空栈。
|
||||
* 当前栈深可由 (stack_top + 1) 推导。
|
||||
* - page_registry:
|
||||
* 可导航页面的注册表,按 page_id 查找目标页面时使用。
|
||||
* - page_count:
|
||||
* 当前注册表有效页面数量,范围 [0, MAX_PAGE_COUNT]。
|
||||
*
|
||||
* 约束关系:
|
||||
* - page_stack 最大深度由 MAX_PAGE_STACK_DEPTH 限制;
|
||||
* - page_registry 最大容量由 MAX_PAGE_COUNT 限制;
|
||||
* - 调用方需通过 PageManager_Init 初始化后再使用。
|
||||
* ------------------------------------------------------------------------- */
|
||||
#define MAX_PAGE_STACK_DEPTH 5
|
||||
#define MAX_PAGE_COUNT 16
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 枚举名: page_manager_result_t
|
||||
* 作用:
|
||||
* 定义 PageManager 相关接口的统一返回码,避免使用无语义的裸整数。
|
||||
*
|
||||
* 取值说明:
|
||||
* PAGE_MANAGER_OK - 操作成功
|
||||
* PAGE_MANAGER_ERR_NULL_PARAM - 传入参数为空
|
||||
* PAGE_MANAGER_ERR_INVALID_ID - 页面 ID 非法
|
||||
* PAGE_MANAGER_ERR_NOT_FOUND - 目标页面未注册/未找到
|
||||
* PAGE_MANAGER_ERR_STACK_FULL - 页面栈已满,无法继续压栈
|
||||
* PAGE_MANAGER_ERR_STACK_BOTTOM - 页面栈已到栈底,无法继续弹栈
|
||||
* PAGE_MANAGER_ERR_REGISTRY_FULL - 页面注册表已满
|
||||
* PAGE_MANAGER_ERR_NO_ACTIVE_PAGE - 当前无活动页面可供分发/循环驱动
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef enum
|
||||
{
|
||||
PAGE_MANAGER_OK = 0,
|
||||
PAGE_MANAGER_ERR_NULL_PARAM = -1,
|
||||
PAGE_MANAGER_ERR_INVALID_ID = -2,
|
||||
PAGE_MANAGER_ERR_NOT_FOUND = -3,
|
||||
PAGE_MANAGER_ERR_STACK_FULL = -4,
|
||||
PAGE_MANAGER_ERR_STACK_BOTTOM = -5,
|
||||
PAGE_MANAGER_ERR_REGISTRY_FULL = -6,
|
||||
PAGE_MANAGER_ERR_NO_ACTIVE_PAGE = -7
|
||||
} page_manager_result_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
page_t *page_stack[MAX_PAGE_STACK_DEPTH]; /* 页面导航栈 */
|
||||
int8_t stack_top; /* 栈顶索引(-1 表示空栈) */
|
||||
page_t *page_registry[MAX_PAGE_COUNT]; /* 页面注册表 */
|
||||
uint8_t page_count; /* 注册页面数量 */
|
||||
} page_manager_t;
|
||||
|
||||
void PageManager_Init(void);
|
||||
page_manager_result_t PageManager_Register(page_t *page);
|
||||
page_manager_result_t PageManager_Navigate(page_id_t pageId);
|
||||
page_manager_result_t PageManager_DispatchEvent(input_event_t *event);
|
||||
|
||||
page_t *PageManager_GetTop(void);
|
||||
page_manager_result_t PageManager_Push(page_t *newPage);
|
||||
page_manager_result_t PageManager_Pop(void);
|
||||
page_t *PageManager_Find(page_id_t pageId);
|
||||
void PageManager_Loop(void);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user