• W801学习笔记十四:掌机系统——菜单——尝试打造自己的UI


    未来将会有诸多应用,这些应用将通过菜单进行有序组织和管理。因此,我们需要率先打造好菜单。

    LCD 驱动通常是直接写屏的,虽然速度较快,但用于界面制作则不太适宜。所以,最好能拥有一套 UI 框架。如前所述,我们的目标是学习,故而不采用如 LVGL 之类的框架,一切都需亲力亲为。

    构建一个简易的 UI 框架:

    每个页面,我们称之为场景(scean),其中包含众多控件。所有页面存放在链表中,每次仅执行最后一个页面的操作。若删除页面,则会退回到前一个页面。

    每个页面可以拥有多个控件。这些控件放置在一个链表中。每次仅执行最后一个控件的操作。当前掌机没有触摸功能,故暂不考虑多个控件同时接收事件。若仅有一个控件,也可不使用链表。

    之前已设计好,大循环放置在主 main 函数内。每次通过 tick 方法将运行间隔传递给下面的场景(scean),再传递给下面的用户控件(usercontrol)。

    好了,开始我们的实操:

    菜单分为多个级别,每级都有自己的页面,同时每个应用也有自己的页面。我们将每个页面用场景(SCEAN)来表示。所有实例化的场景都存放在场景列表(sceanList)中进行管理,以便于页面的前进和后退。

    一:写个scean基类,或者说是接口也行。

    个人比较习惯JAVA的面向对象技术,所以编码风格带有JAVA的色彩。

    IScean.h

    1. #ifdef __cplusplus
    2. enum SceanResult{
    3. SceanResult_EXIT = 1,
    4. SceanResult_Done = 2
    5. };
    6. class IScean {
    7. public:
    8. IScean();
    9. virtual ~IScean();
    10. // 纯虚函数
    11. virtual SceanResult tick(u32 ticks) = 0;
    12. virtual int scean_init(cJSON* param) {return 0;};
    13. virtual int scean_resume(void){return 0;};
    14. virtual int scean_pause(void){return 0;};
    15. protected:
    16. map_t iconMap = NULL;
    17. unsigned char* getImageResource(char* fn);
    18. };
    19. #endif

    scean中有几个重要的方法:初始化,暂停,恢复,执行(tick)

    原则上没有使用系统中断、钩子之类的东西。而是在main函数中进行循环,处理系统级任务,每每次循环的时间消耗通过tick函数传递给当前scean。

    main.cpp

    1. void UserMain(void)
    2. {
    3. 。。。
    4. while(1){
    5. handleMainScreanTask();
    6. IScean* cur= ((IScean*)sceanList->prev->data);
    7. _tmp_Run_Ticks = tls_os_get_time();
    8. keyAdepterProc(_tmp_Run_Ticks - _last_Run_Ticks);
    9. res = cur->tick(_tmp_Run_Ticks - _last_Run_Ticks);
    10. _last_Run_Ticks = _tmp_Run_Ticks;
    11. switch (res) {
    12. case SceanResult_EXIT:
    13. delete cur;
    14. ListPopBack(sceanList);
    15. ((IScean*)sceanList->prev->data)->scean_resume();
    16. break;
    17. case SceanResult_Done:
    18. break;
    19. }
    20. 。。。
    21. }
    22. }

    scean实现中,加入了scean图标的读取。

    IScean.cpp

    1. #include "IScean.h"
    2. #include "../driver/psram.h"
    3. int iterate(any_t item, any_t data){
    4. psram_heap_free((unsigned char *)data);
    5. return MAP_OK;
    6. }
    7. IScean::IScean(){
    8. iconMap = hashmap_new();
    9. }
    10. IScean::~IScean(){
    11. any_t val;
    12. hashmap_iterate(iconMap, iterate, val);
    13. hashmap_free(iconMap);
    14. }
    15. unsigned char* IScean::getImageResource(char* fn){
    16. int error;
    17. unsigned int w,h;
    18. unsigned char *val;
    19. error= hashmap_get(iconMap, fn, (void**)(&val));
    20. if(error == MAP_MISSING){
    21. lodepng_decode32_file(&val, &w, &h, fn);
    22. hashmap_put(iconMap, fn, val);
    23. }
    24. return val;
    25. }

    二:写个用户控件的基类,或者说是接口也行。

    随着东西的增多,我们不能每次都直接写屏,所以需要适当抽一些用户控件以方便页面交互。比如说菜单就需要一个List控件。

    IUserControl.h

    1. class IUserControl
    2. {
    3. public:
    4. IUserControl();
    5. virtual ~IUserControl();
    6. void setPosi(u16 _x, u16 _y, u16 _width, u16 _height){
    7. x=_x;
    8. y=_y;
    9. width = _width;
    10. height= _height;
    11. };
    12. virtual void update(void) = 0;
    13. virtual int tick(u32 ticks) = 0;
    14. u8 src;
    15. const char *statusInfo;
    16. protected:
    17. u16 x,y, width, height;
    18. };

    三:写个List用户控件类。这应该是比较常用的一种用户控件类了。

    里面用一个链表保存所有的选项,然后处理各种按键事件,实现显示效果。

    CtlList.h

    1. typedef struct ListItem
    2. {
    3. const char *text;
    4. unsigned char *image;
    5. void* tag;
    6. }ListItem;
    7. class CtlList : public IUserControl
    8. {
    9. public:
    10. CtlList(void);
    11. ~CtlList(void);
    12. int tick(u32 ticks);
    13. void addItem(ListItem *item);
    14. void clear(void);
    15. void update(void);
    16. void up(void);
    17. void down(void);
    18. u16 value(void){
    19. return cntIdx;
    20. };
    21. ListItem* selectItem(void){
    22. return (ListItem*)(ListGetNodeAt(items, cntIdx)->data);
    23. };
    24. private:
    25. u16 cntIdx=0;
    26. ListNode *items;
    27. u8 zk_num;
    28. DisplayOption optionC = {FONT_SIZE_1516, WHITE, BLUE,0,1};
    29. DisplayOption optionD = {FONT_SIZE_1516, WHITE, BLACK,0,1};
    30. };

    CtlList.cpp

    1. #include "CtlList.h"
    2. CtlList::CtlList(void)
    3. {
    4. items =ListCreate();
    5. }
    6. CtlList::~CtlList(void)
    7. {
    8. printf("Destory CtlList items.\n");
    9. clear();
    10. items = NULL;
    11. }
    12. #define ItemHeifht 34
    13. void CtlList::update(void){
    14. ListNode *node;
    15. u16 showc = height / ItemHeifht;
    16. u16 ty = 0;
    17. u16 count = ListCount(items);
    18. if(count==0) return;
    19. u16 itemH = height / count;
    20. Display_Fill_Rectangle(x + width-10, y, x + width-1, y + height, GRAY);
    21. for(int i= cntIdx - showc /2 ;i<= cntIdx + showc /2;i++){
    22. Display_Fill_Rectangle(x, y + ty * ItemHeifht, x + width-11, y + (ty+1) * ItemHeifht - 2, BLACK);
    23. if(i>=0 && i< count){
    24. node = ListGetNodeAt(items,i);
    25. if(i == cntIdx){
    26. Display_Fill_Rectangle(x+32, y + ty * ItemHeifht, x + width-11, y + (ty+1) * ItemHeifht - 2, BLUE);
    27. Display_String(x+38, y + ty * ItemHeifht + 6, &optionC, (const char*) ((ListItem *)node->data)->text);
    28. }else{
    29. Display_String(x+38, y + ty * ItemHeifht + 6, &optionD, (const char*) ((ListItem *)node->data)->text);
    30. }
    31. if(((ListItem *)node->data)->image !=NULL){
    32. Display_Show_Picture_RGBA(x, y + ty * ItemHeifht, 32, 32, (const unsigned char*) ((ListItem *)node->data)->image);
    33. }
    34. Display_Fill_Rectangle(x + width-9, y + i*itemH, x + width-2, y + + (i+1)*itemH, GREEN);
    35. }
    36. ty++;
    37. }
    38. }
    39. void CtlList::addItem(ListItem *item){
    40. ListPushBack(items, (LTDataType)item);
    41. }
    42. void CtlList::up(void){
    43. if(cntIdx > 0 ) cntIdx--; else cntIdx = ListCount(items) -1;
    44. update();
    45. }
    46. void CtlList::down(void){
    47. if(cntIdx < ListCount(items) -1 ) cntIdx++; else cntIdx = 0;
    48. update();
    49. }
    50. void CtlList::clear(void){
    51. Display_Fill_Rectangle(x, y, x + width-1, y + height, BLACK);
    52. if(items !=NULL){
    53. for(ListNode *cur = items->next; cur != items; cur=cur->next){
    54. delete (ListItem*)cur->data;
    55. }
    56. }
    57. ListDestory(items);
    58. items=ListCreate();
    59. cntIdx = 0;
    60. }
    61. int CtlList::tick(u32 ticks){
    62. if(KEY_UP) {up();}
    63. if(KEY_DOWN) {down();}
    64. return 0;
    65. }

    四:写Menu类。

    menu要实现ISCEAN,里面有个CtlList。

    menu.h

    1. class Menu : public IScean
    2. {
    3. public:
    4. Menu(void);
    5. ~Menu(void);
    6. SceanResult tick(u32 ticks);
    7. int scean_init(u32 param);
    8. int scean_resume(void);
    9. int scean_pause(void){return 0;};
    10. int id = 100;
    11. private:
    12. void showMenu();
    13. IScean* createScean(u32 idx);
    14. unsigned char* getMenuImage(char* fn);
    15. ListNode *menuList;
    16. CtlList *ctlList;
    17. u8 gameMode=0;
    18. cJSON *menuRoot;
    19. };

    menu.cpp

    1. #include "menu.h"
    2. #include "wm_osal.h"
    3. #include "../driver/KeyAdepter.h"
    4. #include "NetConfig.h"
    5. #include "About.h"
    6. #include "Tetris.h"
    7. #include "yingyu.h"
    8. #include "YuWenTs.h"
    9. extern ListNode *sceanList;
    10. #define SceanId_About 102
    11. #define SceanId_NetConfig 103
    12. #define SceanId_YingYu 301
    13. #define SceanId_YuWen 302
    14. #define SceanId_Nes 400
    15. #define SceanId_Tetris 501
    16. #define SceanId_Plane 502
    17. Menu::Menu(void)
    18. {
    19. menuList = ListCreate();
    20. ctlList = new CtlList();
    21. ctlList->setPosi(100, 70, 280, 170);
    22. unsigned char *readBuffer;
    23. size_t readsize = fatfs_readFile("menu/menu.txt", &readBuffer);
    24. printf("load menu finished! %d\n", readsize );
    25. menuRoot = cJSON_Parse((char *)readBuffer);
    26. }
    27. Menu::~Menu(void)
    28. {
    29. ListDestory(menuList);
    30. delete menuList;
    31. delete ctlList;
    32. cJSON_Delete((cJSON*)menuRoot);
    33. }
    34. //×○△□
    35. const char *topMenuInfo=" SEL:选择";
    36. const char *subMenuInfo="EXIT:返回 SEL:选择";
    37. int Menu::scean_init(u32 param){
    38. scean_resume();
    39. return 0;
    40. }
    41. int Menu::scean_resume(void){
    42. clear_screen();
    43. ListDestory(menuList);
    44. menuList = ListCreate();
    45. ListPushBack(menuList, menuRoot);
    46. showMenu();
    47. setKeyAdepterIntervalAll(200);
    48. setKeyAdepterInterval(KEY_GPIO_SEL, 65535);
    49. setKeyAdepterInterval(KEY_GPIO_EXIT, 65535);
    50. return 0;
    51. }
    52. unsigned char* Menu::getMenuImage(char* fn){
    53. if(fn[0] == 45){
    54. return NULL;
    55. }
    56. int error;
    57. unsigned int w,h;
    58. unsigned char *val;
    59. error= hashmap_get(iconMap, fn, (void**)(&val));
    60. if(error == MAP_MISSING){
    61. lodepng_decode32_file(&val, &w, &h, fn);
    62. hashmap_put(iconMap, fn, val);
    63. }
    64. return val;
    65. }
    66. void Menu::showMenu(){
    67. ctlList->clear();
    68. cJSON* currentMenu = (cJSON *)menuList->prev->data;
    69. int count = cJSON_GetArraySize(currentMenu);
    70. printf("menu count=%d\n", count);
    71. for(u8 i=0;i< count;i++){
    72. cJSON* cj = cJSON_GetArrayItem(currentMenu, i);
    73. ListItem *_item = new ListItem();
    74. _item->text = cJSON_GetObjectItem(cj,"t")->valuestring;
    75. if(cJSON_GetObjectItem(cj,"i")->valuestring[0] != '-')
    76. _item->image = getImageResource(cJSON_GetObjectItem(cj,"i")->valuestring);
    77. _item->tag= (void *) cj;
    78. ctlList->addItem(_item);
    79. }
    80. ctlList->update();
    81. if(currentMenu == menuRoot)
    82. show_status_info(topMenuInfo);
    83. else
    84. show_status_info(subMenuInfo);
    85. }
    86. SceanResult Menu::tick(u32 ticks){
    87. if(KEY_EXIT){ // 返回
    88. cJSON* currentMenu = (cJSON *)menuList->prev->data;
    89. if( currentMenu == menuRoot) return SceanResult_Done;
    90. ListPopBack(menuList);
    91. showMenu ();
    92. return SceanResult_Done;
    93. }
    94. if(KEY_SEL){ //进入
    95. cJSON* item = (cJSON*) ctlList->selectItem()->tag;
    96. int sceanId = cJSON_GetObjectItem(item,"d")->valueint;
    97. if(sceanId!= 0){
    98. IScean *scean =createScean(sceanId);
    99. if(scean !=NULL){
    100. scean->scean_init(cJSON_GetObjectItem(item,"g"));
    101. ListPushBack(sceanList, scean);
    102. }
    103. return SceanResult_Done;
    104. }
    105. cJSON* sitem = cJSON_GetObjectItem(item,"s");
    106. if(cJSON_GetArraySize(sitem) > 0){
    107. ListPushBack(menuList, sitem);
    108. showMenu();
    109. return SceanResult_Done;
    110. }
    111. return SceanResult_Done;
    112. }
    113. ctlList->tick(ticks);
    114. ran_max(10);
    115. return SceanResult_Done;
    116. }
    117. IScean* Menu::createScean(u32 idx){
    118. switch (idx) {
    119. case SceanId_About:
    120. return new About();
    121. case SceanId_Tetris:
    122. return new Tetris();
    123. case SceanId_NetConfig:
    124. return new NetConfig();
    125. case SceanId_YingYu:
    126. return new YingYu();
    127. case SceanId_YuWen:
    128. return new YuWenTS();
    129. }
    130. return NULL;
    131. }

    在其实例化的过程中完成菜单JSON的读取。JSON文件放在SD卡中:menu/menu.txt

    五:定义菜单项的JSON结构

    1. {
    2. "t": "三分钟限时挑战",
    3. "i": "menu/yuwen.png",
    4. "d": 0,
    5. "g": {},
    6. "s": [
    7. {
    8. "t": "语文",
    9. "i": "menu/yuwen.png",
    10. "g": {},
    11. "s": [
    12. {
    13. "t": "唐诗三百首",
    14. "i": "-",
    15. "d": 301,
    16. "g": {"w":1, "m":1},
    17. "s": []
    18. },
    19. {
    20. "t": "宋词三百首",
    21. "i": "-",
    22. "d": 301,
    23. "g": {"w":1, "m":2},
    24. "s": []
    25. }
    26. ]
    27. }
    28. ]
    29. }

    其中:

    t为菜单项标题

    i为菜单项的图标

    d为需要启动的应用编号,根据此编号启动对应的应用

    g为将传递给应用的各种参数

    s为下一级菜单

    六:在main函数中,实例化menu的scean,并把它加入sceanList中。

    1. void UserMain(void)
    2. {
    3. 。。。
    4. sceanList = ListCreate();
    5. Menu *menu = new Menu();
    6. menu->scean_init(0);
    7. ListPushBack(sceanList, menu);
    8. SceanResult res;
    9. u32 _last_Run_Ticks = tls_os_get_time();
    10. u32 _tmp_Run_Ticks = tls_os_get_time();
    11. while(1){
    12. handleMainScreanTask();
    13. IScean* cur= ((IScean*)sceanList->prev->data);
    14. _tmp_Run_Ticks = tls_os_get_time();
    15. keyAdepterProc(_tmp_Run_Ticks - _last_Run_Ticks);
    16. res = cur->tick(_tmp_Run_Ticks - _last_Run_Ticks);
    17. _last_Run_Ticks = _tmp_Run_Ticks;
    18. switch (res) {
    19. case SceanResult_EXIT:
    20. delete cur;
    21. ListPopBack(sceanList);
    22. ((IScean*)sceanList->prev->data)->scean_resume();
    23. break;
    24. case SceanResult_Done:
    25. break;
    26. }
    27. FrameCount++;
    28. }
    29. }

    我们看看效果:

    W801学习笔记十四:掌机系统——菜单——尝试打造自己的UI

  • 相关阅读:
    bat一键给windows server 2012 打补丁
    HCIP-AI神经网络基础
    JAVASE 第二十三天
    ZLToolKit网络库的自问自答
    基于SSM的住院病人监测预警信息管理系统毕业设计源码021054
    非零基础自学Java (老师:韩顺平) 第8章 面向对象编程(中级部分) 8.11 面向对象编程 - 多态
    搭建自己的脚手架
    【多线程】Thread类的基本用法
    IPWorks EDI Translator Delphi Edition
    yum小bug
  • 原文地址:https://blog.csdn.net/vvind/article/details/138135010