From c01473796659bb579bfe20f030792edf57ac828a Mon Sep 17 00:00:00 2001 From: coffee Date: Thu, 3 Apr 2025 10:48:36 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E5=A2=9E=E5=8A=A0ui=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/HUIPageManage.h | 133 +++++++++++++ src/HUIPageManage.c | 405 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 538 insertions(+) create mode 100644 include/HUIPageManage.h create mode 100644 src/HUIPageManage.c diff --git a/include/HUIPageManage.h b/include/HUIPageManage.h new file mode 100644 index 0000000..e4e3477 --- /dev/null +++ b/include/HUIPageManage.h @@ -0,0 +1,133 @@ +/** + * 日期: 2025-04-02 + * 作者: coffee + * 描述: ui页面管理, 独立模块, 用于管理页面切换和跳转 + */ + + +#ifndef __H_UI_PAGE_MANAGE_H__ +#define __H_UI_PAGE_MANAGE_H__ + + +#include + +#ifndef __COUNT_ARGS_IMPL +#define __COUNT_ARGS_IMPL(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, N, ...) N +#endif +#ifndef __COUNT_ARGS +#define __COUNT_ARGS(...) __COUNT_ARGS_IMPL(dummy, ##__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#endif + +// 注册页面函数辅助宏 +#define UIPAGE_FUNC(func) uint8_t func##PageCall(uint16_t cmd, void *data, uint16_t len) +#define UIPAGE_DATA data +#define UIPAGE_LEN len +#define UIPAGE_CMD cmd + +// 对应事件的data转换具体类型宏 +#define UI_CALL_INDEX_TYPE(value) ((enum eUIPage *)value) + + +// hide -> free -> update Page -> init -> show +// 或者非返回时如下, 此时的hide和index事件都可存储该页面索引: +// hide -> index -> free -> update Page -> init -> show +enum eCallCmd +{ + kCallInitPage, ///< 构建页面, 对应页面自己内部创建数据(非释放页面不调用, 但如未构建则会触发) + kCallFreePage, ///< 释放页面, 对应页面自己内部释放数据(非释放页面不调用) + kCallShowPage, ///< 显示页面 + kCallHidePage, ///< 退出页面, 此事件如果是返回触发的可以存储索引数据 + kCallSwitchPage, ///< 切换页面, 对应页面返回false时则不切换, 所有切换都会触发这个事件 + kCallIndexSave, ///< 存储当前索引事件, 通知此事件说明当前不是返回事件(cmd, enum eUIPage *nextPage, 1); +}; + + +// 每个页面占用一个枚举, 根据枚举切换页面, kUIPageMax最大页面值, 要求格式统一 kUIPage + 页面名 +enum eUIPage +{ + kUIPageMax, ///< ui最大个数, 不超过254个页面, 如超过则需要修改字节栈类型 +}; + +///< cmd命令, data数据, len数据长度 +typedef uint8_t (*pageCallType_t)(uint16_t cmd, void *data, uint16_t len); +typedef void (*initPageType_t)(pageCallType_t *call); + +// 初始化页面管理, 当为NULL时内部初始化, 否则回调初始化, 请需要先设置好主页, 初始化会默认显示主页 +void HUIPageInit(initPageType_t initPage); + +// 设置主页, 切换到主页清空返回栈, 如果返回栈为空时, 当前页面不是主页, 则切换到主页 +void HUIPageSetHome(enum eUIPage page); + +// 切换页面, 当前页面入栈, 相同页面不操作, 需要检查栈, 栈存在就回栈清空之后的, 不存在就压栈 +// 因为有些页面可能不用HUIPageBack来返回, 所以需要兼容这种情况 +void HUIPageSwitch(enum eUIPage page); + +// 返回上一个页面 +void HUIPageBack(); + +// 清空返回栈 +void HUIPageClear(); + +/** + * @brief + * 添加关联页面到返回栈, 用于一些按键跳转到指定页面, 或者一些不想逐个进入, 而是直接跳转, 然后返回时逐个返回的功能使用 + * 如正常是 主页->菜单->该页面, 但快捷跳转是主页->该页面, 需要将菜单页面添加到返回栈 + * 则需要调用 清空返回栈, 然后添加 主页->菜单, 最后再切换到该页面(内部会判断首个页面是否存在返回栈, 自动清空首个页面前的返回栈) + * HUIPageClear(); HUIPageAddStack(主页, 菜单, 该页面); // 栈2个, 显示该页面 + * 注: 调用这个时, 当前页面将不会入栈, 最大参数长度9个 + * 如: 当前页面是菜单, 设置 主页->菜单, 则菜单页面不会入栈 + * 因此, 此方法用于绝对顺序跳转 + */ +void _HUIPageAddStack(int len, ...); +#define HUIPageAddStack(...) _HUIPageAddStack(__COUNT_ARGS(__VA_ARGS__), ##__VA_ARGS__) + +// 当前自身页面不释放内存, 用于某些暂不想释放页面, 等返回后再考虑释放 +void HUIPageSetCurrNotFree(); + +// 恢复当前页面默认释放内存 +void HUIPageResetCurrFree(); + +// 获取当前页面是否不释放 +uint8_t HUIPageGetCurrNotFree(); + +// 设置指定页面不释放内存 +void HUIPageSetNotFree(enum eUIPage page); + +// 恢复指定页面默认释放内存 +void HUIPageResetFree(enum eUIPage page); + +// 获取指定页面是否不释放, 是返回1, 否则返回0 +uint8_t HUIPageGetNotFree(enum eUIPage page); + +// 设置用户数据, 在 kCallShowPage 和 kCallHidePage 都可调用, 但每次切换这些事件后都会重置数据为0 +void HUIPageSetUserData(long userData); + +// 获取用户数据 +long HUIPageGetUserData(); + +// 保存当前页面索引, 仅在 kCallHidePage 和 kCallIndexSave 有效, 成功返回1, 否则返回0 +uint8_t HUIPageSaveIndex(uint8_t value); + +// 获取当前页面索引, 仅在 HUIPageIsBack() 返回1时有效, 否则返回0 +uint8_t HUIPageGetIndex(); + +// 获取当前页面 +enum eUIPage HUIPageGetCurrPage(); + +// 获取上一个页面 +enum eUIPage HUIPageGetPrevPage(); + +// 获取返回栈使用长度 +uint8_t HUIPageGetStackLen(); + +// 获取索引栈使用长度 +uint8_t HUIPageGetIndexStackLen(); + +// 检查当前是否是返回 +uint8_t HUIPageIsBack(); + +// 检查该页面是否在返回栈, 是返回1, 否则返回0 +uint8_t HUIPageFindStack(enum eUIPage page); + + +#endif //__H_UI_PAGE_MANAGE_H__ diff --git a/src/HUIPageManage.c b/src/HUIPageManage.c new file mode 100644 index 0000000..858cbae --- /dev/null +++ b/src/HUIPageManage.c @@ -0,0 +1,405 @@ + + +#include +#include +#include +#include +#include + + +// 页面栈最大深度 +#ifndef HUIPAGE_STACK_SIZE +#define HUIPAGE_STACK_SIZE 10 +#endif + +#ifndef LogD +#if 0 +#include +#define LogD(format, ...) printf("[%s:%s:%d]"format "\r\n", __FILE_NAME__, __FUNCTION__, __LINE__, ##__VA_ARGS__) +#else +#define LogD(...) +#endif +#endif + + +typedef struct HUIPageManage +{ + pageCallType_t pageCall[kUIPageMax]; ///< 页面回调 + long userData; ///< 用户数据 + uint8_t homePage; ///< 首页, 当切换到首页时清空返回栈 + uint8_t currPage; ///< 当前页面 + uint8_t prevPage; ///< 上一个页面 + uint8_t modifyUserData : 1; ///< 标记是否修改用户数据 + uint8_t isInit : 1; ///< 初始化显示页面 + uint8_t saveIndex : 1; ///< 允许保存索引 + uint8_t isBack : 1; ///< 标记是否是返回 +} HUIPageManage; + +// 页面管理 +static struct HUIPageManage pageManage; + +// 返回栈 +static HBYTE_STACK_DEFINE(pageStack, HUIPAGE_STACK_SIZE); + +// 存储索引栈, 切换新页面时, hide和saveIndex事件后增加1, 回退时减少1, 仅切换事件上有效 +static HBYTE_STACK_DEFINE(indexStack, HUIPAGE_STACK_SIZE); + +// 不释放页面 +static HBIT_DEFINE(notFreeBit, kUIPageMax); + +// 已初始化页面 +static HBIT_DEFINE(initPageBit, kUIPageMax); + + +/** =================================================== */ +#define INIT_PAGE(page) pageManage.pageCall[kUIPage##page] = page##PageCall +static void InitPage() { + // 页面统一初始化 +} +#undef INIT_PAGE +/** =================================================== */ + + +static void UpdateIndexEvent(uint8_t currPage, uint8_t isHideEvent, enum eUIPage nextPage) { + pageManage.saveIndex = 1; + if (isHideEvent) { + pageManage.pageCall[currPage](kCallHidePage, NULL, 0); + } else { + pageManage.pageCall[currPage](kCallIndexSave, &nextPage, 1); + // 当存储事件未存储数据时, 则自动补充对齐 + if (HByteStackGetUseLen(pageStack) > HByteStackGetUseLen(indexStack)) { + HByteStackPush(indexStack, 0); + } + } + + pageManage.saveIndex = 0; +} + +static uint8_t CheckPageIsNull(enum eUIPage page) { + if (pageManage.pageCall[page] == NULL) { + LogD("Page[%d] is nullptr", page); + return 0; + } + + return 1; +} + +static uint8_t CheckSwitchPage() { + if (CheckPageIsNull(pageManage.currPage) == 0) { + return 0; + } + + // 检查是否可以允许切换页面 + if (pageManage.pageCall[pageManage.currPage](kCallSwitchPage, NULL, 0) == 0) { + LogD("page[%d] not allow switch", pageManage.currPage); + return 0; + } + + return 1; +} + +static void SwitchPage(enum eUIPage page) { + if (pageManage.isInit == 1) { + // hide -> free -> update Page -> init -> show + UpdateIndexEvent(pageManage.currPage, 1, page); + if (pageManage.isBack == 0) { + // 非回退时触发存储索引事件 + // hide -> index -> free -> update Page -> init -> show + UpdateIndexEvent(pageManage.currPage, 0, page); + } + + if (HBitGet(notFreeBit, pageManage.currPage) == 0) { + pageManage.pageCall[pageManage.currPage](kCallFreePage, NULL, 0); + HBitSet(initPageBit, pageManage.currPage, 0); + } + } else { + pageManage.isInit = 1; + } + + // 上一个页面退出时, 不修改用户数据就重置为0 + if (pageManage.modifyUserData == 0) { + pageManage.userData = 0; + } + + pageManage.modifyUserData = 0; + pageManage.prevPage = pageManage.currPage; + pageManage.currPage = page; + + // 检查要显示的页面是不是已经初始化 + if (HBitGet(initPageBit, pageManage.currPage) == 0) { + pageManage.pageCall[pageManage.currPage](kCallInitPage, NULL, 0); + } else { + // 检查要显示的页面是不是不需要释放 + if (HBitGet(notFreeBit, pageManage.currPage) == 0) { + pageManage.pageCall[pageManage.currPage](kCallInitPage, NULL, 0); + } + } + + HBitSet(initPageBit, pageManage.currPage, 1); + pageManage.pageCall[pageManage.currPage](kCallShowPage, NULL, 0); + if (pageManage.modifyUserData == 0) { + pageManage.userData = 0; + } + + pageManage.modifyUserData = 0; + pageManage.isBack = 0; +} + +void HUIPageInit(initPageType_t initPage) { + HByteStackSetUseLen(pageStack, 0); + HBitGet(notFreeBit, 0); + HBitGet(initPageBit, 0); + + // 注册所有页面 + if (initPage != NULL) { + initPage(pageManage.pageCall); + } else { + // 不是外部注册就需要内部注册了 + InitPage(); + } + + SwitchPage(pageManage.homePage); +} + +void HUIPageSetHome(enum eUIPage page) { + pageManage.homePage = page; +} + +// 切换页面, 当前页面入栈, 相同页面不操作, 需要检查栈, 栈存在就回栈清空之后的, 不存在就压栈 +// 因为有些页面可能不用HUIPageBack来返回, 所以需要兼容这种情况 +void HUIPageSwitch(enum eUIPage page) { + if (page < 0 || page >= kUIPageMax) { + LogD("page[%d] out of range", page); + return ; + } + + // 如果当前页面和上一个页面相同, 则不切换 + if (pageManage.currPage == page) { + return ; + } + + + if (CheckPageIsNull(page) == 0) { + return ; + } + + // 检查当前页面是否允许切换 + if (CheckSwitchPage() == 0) { + return ; + } + + // 切换到首页时清空返回栈 + pageManage.isBack = 0; + if (page == pageManage.homePage) { + HUIPageClear(); + } else { + // 如果查找页面在栈中, 说明这个是返回, 当前页面不能入栈 + HByteLenType pos = HByteStackFind(pageStack, page); + if (pos != HBYTE_STACK_ERROR) { + // 回栈 + HByteStackSetUseLen(pageStack, pos); + HByteStackSetUseLen(indexStack, pos); + pageManage.isBack = 1; + } else { + // 当前页面压栈 + HByteStackPush(pageStack, pageManage.currPage); + } + } + + SwitchPage(page); +} + +// 返回上一个页面 +void HUIPageBack() { + pageManage.isBack = 1; + if (HByteStackEmpty(pageStack)) { + SwitchPage(pageManage.homePage); + return; + } + + SwitchPage(HByteStackPop(pageStack)); + HByteStackPop(indexStack); +} + +// 清空返回栈 +void HUIPageClear() { + HByteStackClear(pageStack); + HByteStackClear(indexStack); +} + +/** + * @brief + * 添加关联页面到返回栈, 用于一些按键跳转到指定页面, 或者一些不想逐个进入, 而是直接跳转, 然后返回时逐个返回的功能使用 + * 如正常是 主页->菜单->该页面, 但快捷跳转是主页->该页面, 需要将菜单页面添加到返回栈 + * 则需要调用 清空返回栈, 然后添加 主页->菜单, 最后再切换到该页面(内部会判断首个页面是否存在返回栈, 自动清空首个页面前的返回栈) + * HUIPageClear(); HUIPageAddStack(主页, 菜单, 该页面); + * 注: 调用这个时, 当前页面将不会入栈 + * 如: 当前页面是菜单, 设置 主页->菜单, 则菜单页面不会入栈 + * 因此, 此方法用于绝对顺序跳转 + */ +void _HUIPageAddStack(int len, ...) { + if (len <= 0) { + LogD("len error[%d]", len); + return ; + } + + // 检查页面是否允许切换 + if (CheckSwitchPage() == 0) { + return ; + } + + va_list args; + va_start(args, len); + enum eUIPage curr = va_arg(args, enum eUIPage); + HByteLenType findPos = HByteStackFind(pageStack, curr); + if (findPos != HBYTE_STACK_ERROR) { + HByteStackSetUseLen(pageStack, findPos); + HByteStackSetUseLen(indexStack, findPos); + pageManage.isBack = 1; + } else if (curr == pageManage.homePage) { + // 如果查找不到, 传入的首个页面又是首页, 则清空返回栈 + HUIPageClear(); + } + + findPos = HByteStackGetUseLen(pageStack); + HByteStackPush(pageStack, curr); + for (int i = 1; i < len; ++i) { + curr = va_arg(args, enum eUIPage); + if (curr < 0 || curr >= kUIPageMax) { + LogD("page[%d] out of range", curr); + continue; + } + + // 检查新加入的页面是否已经在栈中 + HByteLenType checkPos = HByteStackFindEx(pageStack, curr, findPos); + if (checkPos != HBYTE_STACK_ERROR) { + LogD("page[%d] already in stack, startIndex[%d] FindIndex[%d], stackSize[%d]", curr, findPos, checkPos, HByteStackGetUseLen(pageStack)); + continue; + } + + // 开始不是首页, 然后中间页面传入首页, 这是个异常情况 + if (curr == pageManage.homePage) { + LogD("page[%d] error, it is home page", curr); + continue; + } + + // 检查新加入的页面是否为空, 避免异常崩溃 + if (CheckPageIsNull(curr) == 0) { + LogD("page[%d] is null", curr); + continue; + } + + // 处理对应页面的存储事件 + if (i + 1 < len) { + UpdateIndexEvent(HByteStackTop(pageStack), 0, curr); + } + + HByteStackPush(pageStack, curr); + } + + va_end(args); + SwitchPage(HByteStackPop(pageStack)); +} + +// 当前自身页面不释放内存, 用于某些暂不想释放页面, 等返回后再考虑释放 +void HUIPageSetCurrNotFree() { + HUIPageSetNotFree(pageManage.currPage); +} + +// 恢复当前页面默认释放内存 +void HUIPageResetCurrFree() { + HUIPageResetFree(pageManage.currPage); +} + +// 获取当前页面是否不释放 +uint8_t HUIPageGetCurrNotFree() { + return HUIPageGetNotFree(pageManage.currPage); +} + +// 设置指定页面不释放内存 +void HUIPageSetNotFree(enum eUIPage page) { + HBitSet(notFreeBit, page, 1); +} + +// 恢复指定页面默认释放内存 +void HUIPageResetFree(enum eUIPage page) { + HBitSet(notFreeBit, page, 0); +} + +// 获取指定页面是否不释放 +uint8_t HUIPageGetNotFree(enum eUIPage page) { + return HBitGet(notFreeBit, page); +} + +// 设置用户数据, 在 kCallShowPage 和 kCallHidePage 都可调用, 但每次切换这些事件后都会重置数据为0 +void HUIPageSetUserData(long userData) { + pageManage.modifyUserData = 1; + pageManage.userData = userData; +} + +// 获取用户数据 +long HUIPageGetUserData() { + return pageManage.userData; +} + +// 保存当前页面索引, 仅在 kCallHidePage 和 kCallIndexSave 有效, 成功返回1, 否则返回0 +uint8_t HUIPageSaveIndex(uint8_t value) { + if (pageManage.saveIndex == 0) { + LogD("save Index error, not event"); + return 0; + } + + uint16_t len = HByteStackGetUseLen(indexStack); + uint16_t pageLen = HByteStackGetUseLen(pageStack); + if (len >= pageLen) { + HByteStackPop(indexStack); + } + + HByteStackPush(indexStack, value); + return 1; +} + +// 获取当前页面索引 +uint8_t HUIPageGetIndex() { + if (pageManage.isBack == 0) { + LogD("not back Event, value[%d] size[%d]", HByteStackTop(indexStack), HByteStackGetUseLen(indexStack)); + return 0; + } + + if (HByteStackEmpty(indexStack)) { + return 0; + } + + return HByteStackTop(indexStack); +} + +// 获取当前页面 +enum eUIPage HUIPageGetCurrPage() { + return pageManage.currPage; +} + +// 获取上一个页面 +enum eUIPage HUIPageGetPrevPage() { + return pageManage.prevPage; +} + +// 获取返回栈使用长度 +uint8_t HUIPageGetStackLen() { + return HByteStackGetUseLen(pageStack); +} + +// 获取索引栈使用长度 +uint8_t HUIPageGetIndexStackLen() { + return HByteStackGetUseLen(indexStack); +} + +// 检查当前是否是返回 +uint8_t HUIPageIsBack() { + return pageManage.isBack; +} + +// 检查该页面是否在返回栈, 是返回1, 否则返回0 +uint8_t HUIPageFindStack(enum eUIPage page) { + return HByteStackFind(pageStack, page) != HBYTE_STACK_ERROR; +} +