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

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

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

构建一个简易的 UI 框架:

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

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

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

好了,开始我们的实操:

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

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

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

IScean.h

#ifdef __cplusplus

enum SceanResult{
	SceanResult_EXIT = 1, 
	SceanResult_Done = 2	
};

class IScean {
	public:
		IScean();
		virtual ~IScean();
		// 纯虚函数
		virtual SceanResult tick(u32 ticks) = 0;
		virtual int scean_init(cJSON* param) {return 0;};
		virtual int scean_resume(void){return 0;};
		virtual int scean_pause(void){return 0;};
	protected:
		map_t iconMap = NULL;
		unsigned char* getImageResource(char* fn);
};

#endif

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

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

main.cpp

void UserMain(void)
{
。。。
	
	while(1){
		handleMainScreanTask();
		IScean* cur= ((IScean*)sceanList->prev->data);

		_tmp_Run_Ticks = tls_os_get_time();
		keyAdepterProc(_tmp_Run_Ticks - _last_Run_Ticks);
		res = cur->tick(_tmp_Run_Ticks - _last_Run_Ticks);
		_last_Run_Ticks = _tmp_Run_Ticks;
		
		
		switch (res) {
			case SceanResult_EXIT:
				delete cur;
				ListPopBack(sceanList);
				((IScean*)sceanList->prev->data)->scean_resume();
				break;
			case SceanResult_Done:
				break;

		}
        。。。
	}
}

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

IScean.cpp

#include "IScean.h"
#include "../driver/psram.h"


int iterate(any_t item, any_t data){
	psram_heap_free((unsigned char *)data);
    return MAP_OK;
}


IScean::IScean(){
	iconMap = hashmap_new();
}

IScean::~IScean(){
	any_t val;
	hashmap_iterate(iconMap, iterate, val);
	hashmap_free(iconMap);
}

unsigned char* IScean::getImageResource(char* fn){
	
	int error;
	unsigned int w,h;
	unsigned char *val;
	error= hashmap_get(iconMap, fn, (void**)(&val));
	
	if(error == MAP_MISSING){
		lodepng_decode32_file(&val, &w, &h, fn);
		hashmap_put(iconMap, fn, val);
	}
	return val;
}

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

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

IUserControl.h

class IUserControl
{
	public:
		IUserControl();
		virtual ~IUserControl();
		 
		void setPosi(u16 _x, u16 _y, u16 _width, u16 _height){
			x=_x;
			y=_y;
			width = _width;
			height= _height;
		};
		
		virtual void update(void) = 0;
		virtual int tick(u32 ticks) = 0;
		u8 src;
		const char *statusInfo;
		
		
	protected:
		u16 x,y, width, height;
};

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

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

CtlList.h

typedef struct ListItem
{
	const char *text;
	unsigned char *image;
	void* tag;
}ListItem;


class CtlList : public IUserControl
{
   public:
		CtlList(void);   
		~CtlList(void);  
		int tick(u32 ticks);
		void addItem(ListItem *item);
		void clear(void);
		void update(void);
		void up(void);
		void down(void);
		u16 value(void){
			return cntIdx;
		};
		
		ListItem* selectItem(void){
			return (ListItem*)(ListGetNodeAt(items, cntIdx)->data);
		};
		
	private:
	   u16 cntIdx=0;
	   ListNode *items;
	   u8 zk_num;

	   DisplayOption optionC = {FONT_SIZE_1516, WHITE, BLUE,0,1};
	   DisplayOption optionD = {FONT_SIZE_1516, WHITE, BLACK,0,1};
      
};

CtlList.cpp

#include "CtlList.h"

CtlList::CtlList(void)
{
	items =ListCreate();
}

CtlList::~CtlList(void)
{
	printf("Destory CtlList items.\n");
	clear();
	items = NULL;
}
 
 #define ItemHeifht 34
 
void CtlList::update(void){
 	ListNode *node;
	u16 showc = height / ItemHeifht;
	u16 ty = 0;
	u16 count = ListCount(items);
	if(count==0) return;

	u16 itemH = height / count;
	Display_Fill_Rectangle(x + width-10, y, x + width-1, y + height, GRAY);
	for(int i= cntIdx - showc /2 ;i<= cntIdx + showc /2;i++){
		Display_Fill_Rectangle(x, y + ty * ItemHeifht, x + width-11, y + (ty+1) * ItemHeifht - 2, BLACK);
		if(i>=0 && i< count){
			node = ListGetNodeAt(items,i);
			if(i == cntIdx){
				Display_Fill_Rectangle(x+32, y + ty * ItemHeifht, x + width-11, y + (ty+1) * ItemHeifht - 2, BLUE);
				Display_String(x+38, y + ty * ItemHeifht + 6, &optionC, (const char*) ((ListItem *)node->data)->text);		
			}else{
				Display_String(x+38, y + ty * ItemHeifht + 6, &optionD, (const char*) ((ListItem *)node->data)->text);
			}
			if(((ListItem *)node->data)->image !=NULL){
				Display_Show_Picture_RGBA(x, y + ty * ItemHeifht, 32, 32, (const unsigned char*) ((ListItem *)node->data)->image);
			}
			Display_Fill_Rectangle(x + width-9, y + i*itemH, x + width-2, y + + (i+1)*itemH, GREEN);
		}
		ty++;
	}
}

void CtlList::addItem(ListItem *item){
	ListPushBack(items, (LTDataType)item);
}

void CtlList::up(void){
	if(cntIdx > 0 ) cntIdx--; else cntIdx = ListCount(items) -1;
	update();
	
}

void CtlList::down(void){
	if(cntIdx < ListCount(items) -1  ) cntIdx++; else cntIdx = 0;
	update();
}

void CtlList::clear(void){
	Display_Fill_Rectangle(x, y, x + width-1, y + height, BLACK);
	if(items !=NULL){
		for(ListNode *cur = items->next; cur != items; cur=cur->next){
			delete (ListItem*)cur->data;
		}	
	}
	ListDestory(items);
	items=ListCreate();
	cntIdx = 0;
}

int CtlList::tick(u32 ticks){
	if(KEY_UP) {up();}
	if(KEY_DOWN) {down();}
	return 0;
}

四:写Menu类。

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

menu.h

class Menu : public IScean
{
	public:
      Menu(void);
      ~Menu(void);  
      SceanResult tick(u32 ticks);
	  int scean_init(u32 param);
	  int scean_resume(void);
	  int scean_pause(void){return 0;};
	  int  id = 100;
	private:
		void showMenu();
		IScean* createScean(u32 idx);
		unsigned char* getMenuImage(char* fn);
		ListNode *menuList;
		CtlList *ctlList;
		u8 gameMode=0;
		cJSON *menuRoot;
		
};

menu.cpp

#include "menu.h"
#include "wm_osal.h"
#include "../driver/KeyAdepter.h"
#include "NetConfig.h"
#include "About.h"
#include "Tetris.h"
#include "yingyu.h"
#include "YuWenTs.h"

extern ListNode *sceanList;

#define SceanId_About 102
#define SceanId_NetConfig 103

#define SceanId_YingYu 301
#define SceanId_YuWen 302

#define SceanId_Nes 400

#define SceanId_Tetris 501
#define SceanId_Plane 502


Menu::Menu(void)
{
	menuList = ListCreate(); 
	ctlList = new CtlList();
	ctlList->setPosi(100, 70, 280, 170);
	
	unsigned char *readBuffer;
	size_t readsize = fatfs_readFile("menu/menu.txt", &readBuffer);
	printf("load menu finished! %d\n", readsize );
	menuRoot = cJSON_Parse((char *)readBuffer);
}


Menu::~Menu(void)
{
	ListDestory(menuList);
	delete menuList;
	delete ctlList;
	cJSON_Delete((cJSON*)menuRoot);

}
//×○△□
const char *topMenuInfo="              SEL:选择";
const char *subMenuInfo="EXIT:返回     SEL:选择";

int Menu::scean_init(u32 param){
	scean_resume();					
	return 0;
}

int Menu::scean_resume(void){
	clear_screen();
	ListDestory(menuList);
	menuList = ListCreate(); 
	ListPushBack(menuList, menuRoot);
	showMenu();
	setKeyAdepterIntervalAll(200);
	setKeyAdepterInterval(KEY_GPIO_SEL, 65535);
	setKeyAdepterInterval(KEY_GPIO_EXIT, 65535);
	return 0;
}

unsigned char* Menu::getMenuImage(char* fn){

	if(fn[0] == 45){
		return NULL;
	}
		
	int error;
	unsigned int w,h;
	unsigned char *val;
	error= hashmap_get(iconMap, fn, (void**)(&val));
	
	if(error == MAP_MISSING){
		lodepng_decode32_file(&val, &w, &h, fn);
		hashmap_put(iconMap, fn, val);
	}
	return val;
}

void Menu::showMenu(){
	ctlList->clear();
	
	cJSON*  currentMenu =  (cJSON *)menuList->prev->data;
	int count =	cJSON_GetArraySize(currentMenu);
	printf("menu count=%d\n", count);
	for(u8 i=0;i< count;i++){
	  cJSON* cj = 	cJSON_GetArrayItem(currentMenu, i);
		ListItem *_item = new ListItem();
		_item->text = cJSON_GetObjectItem(cj,"t")->valuestring;
		if(cJSON_GetObjectItem(cj,"i")->valuestring[0] != '-')
			_item->image = getImageResource(cJSON_GetObjectItem(cj,"i")->valuestring);
		_item->tag= (void *) cj;
		ctlList->addItem(_item);
	}
	ctlList->update();
	
	if(currentMenu == menuRoot)
		show_status_info(topMenuInfo);
	else
		show_status_info(subMenuInfo);
}

SceanResult Menu::tick(u32 ticks){
	if(KEY_EXIT){ // 返回
		cJSON*  currentMenu =  (cJSON *)menuList->prev->data;
		if(	currentMenu == menuRoot) return SceanResult_Done;
		ListPopBack(menuList);
		showMenu ();
		return SceanResult_Done;
	}
	
	if(KEY_SEL){ //进入
		cJSON* item =  (cJSON*) ctlList->selectItem()->tag;
		int sceanId = cJSON_GetObjectItem(item,"d")->valueint;
		if(sceanId!= 0){
			IScean *scean =createScean(sceanId);
			if(scean !=NULL){
				scean->scean_init(cJSON_GetObjectItem(item,"g"));
				ListPushBack(sceanList, scean);
			}
			return SceanResult_Done;
		}
		cJSON* sitem =  cJSON_GetObjectItem(item,"s");
		
		if(cJSON_GetArraySize(sitem) > 0){
			ListPushBack(menuList, sitem);			
			showMenu();
			return SceanResult_Done;
		}
		return SceanResult_Done;
	}
	ctlList->tick(ticks);
	ran_max(10);
	return SceanResult_Done;
}

IScean* Menu::createScean(u32 idx){
	switch (idx) {
		case SceanId_About:
			return new About();
		case SceanId_Tetris:
			return new Tetris();
		case SceanId_NetConfig:
			return new NetConfig();
		case SceanId_YingYu:
			return new YingYu();
		case SceanId_YuWen:
			return new YuWenTS();

	}
	return NULL;
}

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

五:定义菜单项的JSON结构

{
		"t": "三分钟限时挑战",
		"i": "menu/yuwen.png",
		"d": 0,
		"g": {},
		"s": [
			{
				"t": "语文",
				"i": "menu/yuwen.png",
				"g": {},
				"s": [
					{
						"t": "唐诗三百首",
						"i": "-",
                		"d": 301,
						"g": {"w":1, "m":1},
						"s": []
					},
					{
						"t": "宋词三百首",
						"i": "-",
                		"d": 301,
						"g": {"w":1, "m":2},
						"s": []
					}
				]
			}
         ]
}

其中:

t为菜单项标题

i为菜单项的图标

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

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

s为下一级菜单

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

void UserMain(void)
{
。。。
	
	sceanList = ListCreate();
	Menu *menu =  new Menu();
	menu->scean_init(0);
	ListPushBack(sceanList, menu); 
	SceanResult res;
	u32 _last_Run_Ticks = tls_os_get_time();
	u32 _tmp_Run_Ticks = tls_os_get_time();
	
	while(1){
		handleMainScreanTask();
		IScean* cur= ((IScean*)sceanList->prev->data);

		_tmp_Run_Ticks = tls_os_get_time();
		keyAdepterProc(_tmp_Run_Ticks - _last_Run_Ticks);
		res = cur->tick(_tmp_Run_Ticks - _last_Run_Ticks);
		_last_Run_Ticks = _tmp_Run_Ticks;
		
		
		switch (res) {
			case SceanResult_EXIT:
				delete cur;
				ListPopBack(sceanList);
				((IScean*)sceanList->prev->data)->scean_resume();
				break;
			case SceanResult_Done:
				break;

		}
		FrameCount++;
	}
}

我们看看效果:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/571142.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

4.26日学习记录

[湖湘杯 2021 final]Penetratable SUID提权 SUID是一种对二进制程序进行设置的特殊权限&#xff0c;可以让二进制程序的执行者临时拥有属主的权限 SUID具有一定的限制&#xff1a; 1.仅对于二进制有效&#xff1b; 2.执行者在程序中有可以执行的权限&#xff1b; 3.权限仅在程序…

使用Spring 完成转账业务添加日志功能

(完整的代码在文章附带文件中 , 文章里的代码仅作展示 , 可能有部分不完善 代码地址 :下载:https://javazhang.lanzn.com/i5oLI1vyiile 密码:1234 ) 任务目标 具体实现方法和心得 步骤1. 导入依赖项Spring依赖 , aop依赖,德鲁伊依赖,mybatis依赖 , mysql驱动 , mybatis-sprin…

深度学习框架pytorch:tensor.data和tensor.detach()的区别

本文重点 本文我们区别一下tensor.data和tensor.detach(),我们所讲解的都是pytorch的1.0版本的情况 官方解释 返回一个新的张量,它与当前图形分离。结果永远不需要梯度。返回的张量与原始张量共享相同的存储空间。将看到对其中任何一个的就地修改,并且可能在正确性检查中…

【神经网络结构可视化】PlotNeuralNet的安装、测试及创建自己的神经网络结构可视化图形

文章目录 前提准备1、下载MikTeX2、下载Git bash3、下载PlotNeuralNet 进行测试1、解压PlotNeuralNet-master.zip2、打开Git bash3、 在my_project中查看生成的pdf文件 创建自己的神经网络结构可视化图形 前提准备 1、下载MikTeX 下载链接&#xff1a; MikTeX ( https://mikt…

闲话 ASP.NET Core 数据校验(一):内置数据校验

前言 所谓输入的是垃圾&#xff0c;输出也必然是垃圾&#xff0c;有多少安全问题隐藏在请求的数据中&#xff0c;所以永远不能相信来自用户端的输入。 对请求数据的合法性进行校验&#xff0c;不仅有助于提升用户界面的友好性&#xff0c;而且有助于提高后台程序的安全性和稳…

区块链安全应用------压力测试

测试要求&#xff1a; 1. 对以下AccountManager智能合约进行压测(基础要求set函数测试&#xff0c;balanceOf涵为20分加分项)2. 在本地链进行测试&#xff0c;需要监控本地进程的资源使用情况。每个进程的multiOutput属性为Avg3. 需要将每一个更改的配置文件截图&#xff0c;和…

初入数据库

SQL&#xff1a;操作关系型数据库的编程语言&#xff0c;定义了一套操作关系型数据库的统一标准。 DDL&#xff08;Data Definition Language&#xff09;数据定义语言 数据库 show databases;create database db01;use db01;select database(); 显示当前使用的数据库drop d…

制作一个RISC-V的操作系统十三-抢占式多任务和兼容协作式多任务

文章目录 强占式多任务流程代码具体流程兼容协作式多任务&#xff08;软中断&#xff09;寄存器 msip流程代码结果 强占式多任务 流程 抢占式多任务由计时器中断触发&#xff0c;最后在处理程序中切换到下一个进程 代码具体流程 上下文中增加pc寄存器 寄存器保留上下文和切…

AI计算中的光学模块:波分复用器的应用前景

在人工智能&#xff08;AI&#xff09;的计算领域&#xff0c;光学模块扮演着至关重要的角色。随着AI技术的飞速发展&#xff0c;对数据处理速度和带宽的需求日益增长。光学模块&#xff0c;特别是波分复用器&#xff08;WDM&#xff09;&#xff0c;因其高速、大容量的数据传输…

实战技巧:Android 14适配从挂号到出院

公众号「稀有猿诉」 原文链接 实战技巧&#xff1a;Android 14适配从挂号到出院 啥&#xff1f;这都4202年了&#xff0c;你的应用还没有升级到targetSDK 34&#xff1f;莫慌&#xff0c;本文就带着你全面的了解升级targetSDK 34的方法以及避坑指南。 注意&#xff0c;A…

机器学习/算法工程师面试题目与答案-深度学习部分1

机器学习/算法工程师面试题目与答案-深度学习部分 BatchNormalization的作用梯度消失循环神经网络&#xff0c;为什么好?什么是GroupConvolution什么是RNN神经网络中权重共享的是&#xff1f;神经网络激活函数&#xff1f;为什么在深度学习中常进行finetuning画GRU结构图什么是…

JavaEE初阶之IO流快速顿悟一(超详细)

目录 题外话 正题 IO流 Java.io.FileInputStream int read() int read(byte[] b) 关于异常 Java7的新特性: try-with-resources ( 资源自动关闭) Java.io.FileOutputStream void write(int b) void write(byte[] b) 小结 题外话 十年青铜无人问,一朝顿悟冲王者 前天…

网工内推 | 深圳网工专场,上市公司、国企,安全认证优先

01 深圳市同为数码科技股份有限公司武汉分公司 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、负责网络设备的管理、调试、配置、维护等&#xff1b; 2、负责信息安全网络安全设备、系统的运维&#xff1b; 3、负责整体网络系统技术的相关工作&#xff0c;包括架构…

使用C++实现尾插式循环链表结构

在编码中避免不了使用链表&#xff0c;特别是循环链表&#xff0c;很多同学使用时为了省事直接使用C STL库中的链表实现&#xff0c;这样当然很简单也不容易出错&#xff0c;但同时也不可避免的带来了一些问题&#xff1a; 是半个黑盒&#xff0c;虽然能看源码&#xff0c;但是…

如何免费生成网址二维码?支持自定义设计的二维码生成器

在国内外的许多创意广告中都在使用网址二维码。比如&#xff1a;大众汽车隐藏在汽车零件上的企业招聘二维码&#xff0c;扫码后进入大众汽车官网在线申请投递简历&#xff1b;帕森斯设计学院的户外广告中打印在红色沙滩椅上的二维码&#xff0c;扫描后可以在线申请暑期课程&…

详细分析mysqlslap的基本知识 | 压力测试(附Demo)

目录 前言1. 基本知识2. 参数解读2.1 auto-generate-sql2.2 only-print2.3 iterations2.4 并发处理参数 前言 对数据库进行压力测试&#xff0c;对此补充这方面的详细知识点 1. 基本知识 mysqlslap 是 MySQL 自带的用于模拟数据库负载的压力测试工具 可以模拟多个客户端并发…

【Java | 多线程】LockSupport 的使用和注意事项

了解一下 LockSupport LockSupport是一个类&#xff0c;位于java.util.concurrent.locks包中&#xff0c;提供了基本的线程同步机制。 LockSupport的主要作用是挂起和唤醒线程。它提供了两个主要的静态方法&#xff1a;park()和unpark()。 park()&#xff1a;用于挂起当前线…

AI论文速读 |从图结构角度统一车道级交通预测:基准和基线

题目&#xff1a;Unifying Lane-Level Traffic Prediction from a Graph Structural Perspective: Benchmark and Baseline 作者&#xff1a;Shuhao Li, Yue Cui, Jingyi Xu, Libin Li, Lingkai Meng, Weidong Yang(杨卫东), Fan Zhang, Xiaofang Zhou(周晓方) 机构&#xff…

【Python】Python函数的黑魔法:递归,嵌套函数与装饰器

欢迎来到CILMY23的博客 本篇主题为&#xff1a; Python函数的黑魔法&#xff1a;递归&#xff0c;嵌套函数与装饰器 个人主页&#xff1a;CILMY23-CSDN博客 系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法 感谢观看&#xff0c;支持的可以给个一键三连&#xff…

redis基于Stream类型实现消息队列,命令操作,术语概念,个人总结等

个人大白话总结 1 在Redis Stream中&#xff0c;即使消息被消费者确认&#xff08;acknowledged, ACK&#xff09;&#xff0c;消息也不会自动从Stream数据结构中删除。这与Kafka或RabbitMQ等传统消息队列系统的做法不同&#xff0c;在那些系统中&#xff0c;一旦消息被消费并…