韦东山数码相框任务需求分析

本文详细阐述了嵌入式设备UI设计的框架构思,包括界面抽象、流程分解、Page结构体定义及其功能,展示了如何通过不同按钮切换界面,以及各界面的显示和输入事件处理流程。

前言

只是简单分析了下各个结构体的由来,意淫编程

整体框架参考了韦东山数码相框修正调整,另类介绍程序代码结构

需求界面

整个需求如下图
在这里插入图片描述

抽象流程

理解为是各个界面,通过不同的按钮相关切换,所以将界面抽象出来
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

总共分解成六个小界面,针对每个界面,这时可以想到的操作有:

  1. 显示界面内容 ==》显示数据准备
  2. 响应界面上的触摸事件 =》按键位置判断为哪个按钮

针对界面,则有管理问题,是数组,还是链表?

这里所能想到的对应结构体基本结构应为:

Page {
    char *name;           	        // 页面名字 
    void (*Display)();              // 页面的运行函数
    int (*GetInputEvent)();         // 获得输入数据的函数 
    Page *ptNext;                   // 链表管理
}

界面分解

每个界面又分为类似如下几个图标:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

针对这些图标,想到的可能属性有:

  1. 位置
  2. 使用哪里图片

对应结构体:

Icon{
    iTopLeftX                       // 左上角坐标 
    iTopLeftY
    iBotRightX                      // 右下角坐标 
    iBotRightY
    strIconName                     // 图片位置
}

而图标数目这种明显跟界面强相关,需要保存在界面 Page 结构体中

Page {
    char *name;           	        // 页面名字 
    void (*Display)();              // 页面的运行函数
    int (*GetInputEvent)();         // 获得输入数据的函数 
    Page *ptNext;                   // 链表管理
    Icon[]                          // 所有包含的图标
}

再梳理流程:

Main -> Browser -> manual 
  |
  |---> Auto
  |
  |--->Setting --> interval

这个时候需要一个更高层次的来调用组织 Page,暂时叫 App 结构体吧,但是抽象了发现,切换到哪个界面跟只有界面自己知道,这个逻辑最简单,高内聚,每个程序管好自己内部就行了,切出去时自己指定切到谁。这里发现就需要在 Page 内部做逻辑切换。为此在 Page 内部增加一个 Run() 函数

Page {
    char *name;           	        // 页面名字 
    
    void (*Run)();                  // 页面运行函数	            
    int (*GetInputEvent)();         // 获得输入数据的函数 
    void (*Display)();              // 页面显示函数
    
    Page *ptNext;                   // 链表管理
    Icon[]                          // 所有包含的图标
}

梳理下 Run() 流程大致如下:

Run()
	Display();			// 显示主界面
	for(;;)
		GetInputEvent();				// 获取哪个按钮被点击
		switch()
			PageSelect("目的界面")->Run(); 

这里发现 Display() 跟 GetInputEvent() 似乎不会被其他模块调用,属于 Private 内部就好了

Page {
    char *name;           	        // 页面名字 
    
    void (*Run)();                  // 页面运行函数
            
    static int (*GetInputEvent)();         // 获得输入数据的函数 
    static void (*Display)();              // 页面显示函数
    
    Page *ptNext;                   // 链表管理
    Icon[]                          // 所有包含的图标
}

到这里,大的切换框架已经可以实现了,整体程序暂时框架为:

main()
	PageInit();											// 注册所有界面到链表 PageList 中管理
	PageSelect("Main")->Run();							// 选择主界面运行

剩下再细究先研究 Page 结构体对应函数功能, 然后再针对每个界面不同重载的特殊处理。

Page 结构体

Page {
    char *name;           	        // 页面名字 
    
    void (*Run)();                  // 页面运行函数
            
    static int (*GetInputEvent)();         // 获得输入数据的函数 
    static void (*Display)();              // 页面显示函数
    
    Page *ptNext;                   // 链表管理
    Icon[]                          // 所有包含的图标
}

static void (*Display)();

在这里插入图片描述
比如像这种怎么显示到界面上

Display() 流程:
	1. 获取一块内存
	2. 填充内存
        2.1 读取图片
        2.2 加载到显示内存指定位置
	3. 刷新到显示中

static void (*GetInputEvent)();

static int (*GetInputEvent)();         // 获得输入数据的函数 
    1. 获取报点
    2. 判断点是否有在某个图标位置内部,有返回数组下标

第一界面:主界面

在这里插入图片描述
都是些界面跳转,最简单的一页,流程不需要特别改

Page {
    char *name;           	            // 页面名字 
    
    void (*Run)();                      // 页面运行函数
        Display();			                // 显示主界面
		for(;;)
			GetInputEvent();				// 获取哪个按钮被点击
			switch()
				case: 浏览按钮
					PageSelect("选择界面")->Run(); 
				case: 连播按钮
					PageSelect("连播界面")->Run(); 
				case: 设置按钮
                	PageSelect("设置界面")->Run(); 
            
    static int (*GetInputEvent)();         // 获得输入数据的函数 
        1. 获取报点
        2. 判断点是否有在某个图标位置内部,有返回数组下标
    
    static void (*Display)();              // 页面显示函数
        1. 获取一块内存
		2. 填充内存
            2.1 读取图片
            2.2 加载到显示内存指定位置
		3. 刷新到显示中
    
    Page *ptNext;                       // 链表管理
    Icon[]                              // 所有包含的图标
}

第二界面:选择界面

在这里插入图片描述
涉及到目录切换与图标显示,而且实际图标只有四个,剩下 9 个图标是可变的

Page {
    char *name;           	            // 页面名字 
    
    void (*Run)();                      // 页面运行函数
        Display(“当前根目录”,1);			                // 显示主界面
        1 打开目录
        2 遍历目录,保存 文件名+是否文件, 目录名+是否目录到缓存中
        for(;;)
            GetInputEvent();				// 获取哪个按钮被点击
            判断是这 13 个按钮哪个被按到,是文件还是目录
                文件或目录的话,则保存名称及类型
            switch()
                case: 向上:
                    当前目录缩短一段
                    1 打开目录
                    2 遍历目录,保存 文件名+是否文件, 目录名+是否目录到缓存中
                    Display(“当前目录”,缓存[index 第几个 9]);
                case: 选择:
                    目录:
                        当前目录增加一段
                        1 打开目录
                        2 遍历目录,保存 文件名+是否文件, 目录名+是否目录到缓存中
                        Display(“当前目录”,缓存[index 第几个 9]);
                    文件:目前仅支持图片
                        当前路径增加一段
                        PageSelect("浏览界面")->Run("当前图片路径");  # Run() 需要增加参数
                case: 上一页:
                    index++
                    Display(“当前目录”,缓存[index 第几个 9]);
                case: 下一页 
                    index--
                    Display(“当前目录”,缓存[index 第几个 9]);
                        
    static int (*GetInputEvent)();         // 获得输入数据的函数 
        1. 获取报点
        2. 判断点是否有在某个图标位置内部,有返回数组下标
    
    static void (*Display)("目录路径",缓存[index 第几个 9]);              // 页面显示函数
        1. 获取一块内存
        2. 填充内存
            2.1 根据 缓存[index 第几个 9] 更新下九个图标的名称
            2.1 读取图标
            2.2 刷新到显示中
        3. 刷新到显示中
    
    Page *ptNext;                       // 链表管理
    Icon[]                              // 所有包含的图标
}

第三界面:浏览界面

在这里插入图片描述
Page {
char *name; // 页面名字

    void (*Run)("当前图片路径");        // 页面运行函数
        Display();			                // 显示主界面
        1 当前图片路径
        2 遍历目录,保存 文件名 缓存中
        for(;;)
            GetInputEvent();				// 获取哪个按钮被点击
            switch()
                case: 返回
                    退出当前 Run()
                case: 缩小
                    缩放标志--
                    Display();
                case: 放大
                    缩放标志++
                    Display();
                case: 上一张 
                    更新当前图片路径为上一张图片
                    Display();
                case: 下一张 
                    更新当前图片路径为下一张图片
                    Display();
                case: 连播 
                    PageSelect("连播界面")->Run("当前图片路径");
                    
            
    static int (*GetInputEvent)();         // 获得输入数据的函数 
        1. 获取报点
        2. 判断点是否有在某个图标位置内部,有返回数组下标
    
    static void (*Display)();              // 页面显示函数
        1. 获取一块内存
        2. 填充内存
            2.1 读取图标
            2.2 读取当前图片路径 + 缩放标志
            2.2 加载到显示内存指定位置
        3. 刷新到显示中
    
    Page *ptNext;                       // 链表管理
    Icon[]                              // 所有包含的图标
}

第四界面:连播界面

在这里插入图片描述
Page {
char *name; // 页面名字

    void (*Run)("当前图片路径");        // 页面运行函数
        Display();			                // 显示主界面
        1 当前图片路径
        2 遍历目录,保存 文件名 缓存中
        for(;;)
            GetInputEvent();				// 获取哪个按钮被点击
            switch()
                case: 返回
                    退出当前 Run()
            延时指定时间间隔
            更新当前图片路径为下一张 
            Display()
                    
            
    static int (*GetInputEvent)();         // 获得输入数据的函数 
        1. 获取报点
        2. 判断点是否有在某个图标位置内部,有返回数组下标
    
    static void (*Display)();              // 页面显示函数
        1. 获取一块内存
        2. 填充内存
            2.1 读取图标
            2.2 读取当前图片路径 + 缩放标志
            2.3 加载到显示内存指定位置
        3. 刷新到显示中
    
    Page *ptNext;                       // 链表管理
    Icon[]                              // 所有包含的图标
}

第五界面:设置界面

在这里插入图片描述
Page {
char *name; // 页面名字

    void (*Run)("当前图片路径");        // 页面运行函数
        Display();			                // 显示主界面
        当前图片路径 = 根目录
        for(;;)
            GetInputEvent();				// 获取哪个按钮被点击
            switch()
                case: 选择目录
                    PageSelect("选择界面")->Run("当前图片路径");
                case: 设置间隔
                    PageSelect("连播界面")->Run("当前图片路径");
                    
            
    static int (*GetInputEvent)();         // 获得输入数据的函数 
        1. 获取报点
        2. 判断点是否有在某个图标位置内部,有返回数组下标
    
    static void (*Display)();              // 页面显示函数
        1. 获取一块内存
        2. 填充内存
            2.1 读取图标
            2.2 加载到显示内存指定位置
        3. 刷新到显示中
    
    Page *ptNext;                       // 链表管理
    Icon[]                              // 所有包含的图标
}

第六界面:间隔界面

在这里插入图片描述
这里发现需要在添加一个时间间隔全局变量

Page {
    char *name;           	            // 页面名字 
    
    void (*Run)("当前图片路径");        // 页面运行函数
        Display();			                // 显示主界面
        当前图片路径 = 根目录
        for(;;)
            GetInputEvent();				// 获取哪个按钮被点击
            switch()
                case: 增加
                    时间间隔++
                    Display()
                case: 减小
                    时间间隔--
                    Display()
                    
            
    static int (*GetInputEvent)();         // 获得输入数据的函数 
        1. 获取报点
        2. 判断点是否有在某个图标位置内部,有返回数组下标
    
    static void (*Display)();              // 页面显示函数
        1. 获取一块内存
        2. 填充内存            
            2.1 根据 时间间隔 选择中间图标用哪张图
            2.2 读取图标
            2.3 加载到显示内存指定位置
        3. 刷新到显示中
    
    Page *ptNext;                       // 链表管理
    Icon[]                              // 所有包含的图标
}

到这里,感觉程序整体框架已经搭完了,下面思考下各个页面中使用到的模块抽象

其他过程抽象

显示接口

显示在哪里使用呢?在 Display() 流程有使用

Display() 流程:
	1. 获取一块内存
	2. 填充内存
        2.1 读取图片
        2.2 加载到显示内存指定位置
	3. 刷新到显示中

针对 Linux 的话,显示就是将显存映射为一块内存,然后往内存里面填东西就能显示

// 显示接口
Display{
	char *name;			// 显示接口名称
    void (*Init)();     // 显示接口初始化流程,比如打开,映射显示设备
    void (*Flush)(“包含显示的缓存”);    // 刷新显示     
}

这些都是根据上面流程想到的比较直接的接口定义

读取图片/图标

  1. 图片图标使用位置

     Display() 流程:
     	1. 获取一块内存
     	2. 填充内存
             2.1 读取图片
             2.2 加载到显示内存指定位置
     	3. 刷新到显示中
    
  2. 图片,图标那肯定有不同的格式的,所以会需要不同的格式解析模块

  3. 不同图片格式那也是需要管理的,链表吧,就用 g_PicFmtsList

     PicFmt{
     	char *name;								        // 图片解析模块名称,比如 Bmp, Png
     	void (*Read)(显示缓存,Icon 图片信息);			// 读取图片到显示缓存中指定位置
         ptNext		                                    // 下一个模块
     }
    

这里还有个问题,程序还需要判断这是什么图片类型后,才好调用具体的 PicFmts 格式处理的,所以 PicFmts 还需要有个判断本模块是否支持的功能

PicFmt{
	char *name;								        // 图片解析模块名称
	void (*Read)(显示缓存,Icon 图片信息);		    // 读取图片到显示缓存中指定位置
        1. 针对 Icon 所有图片,打开图片
        2. 解析图片内容放进显示缓存指定位置
        
    void (*isSupport)(Icon 图片信息)
        1. 打开图片
        2. 判断格式是否是本模块支持的
        
    ptNext		                                    // 下一个模块
}

这样针对每个传入的 Icon 图片,需要先遍历链表 g_PicFmtsList 通过 isSupport() 找到对应模块,再调用读入函数

GetPicFmts(Icon 图片信息):

需要先遍历链表 g_PicFmtsList 通过 isSupport() 找到对应模块,再调用读入函数

1. 遍历 g_PicFmtsList 链表,调用 PicFmt->isSupport() 判断是否有模块支持 
2. 返回支持的 PicFmt 结构体

所以 Display 流程会更新类似如下:

	Display() 流程:
		1. 获取一块内存
		2. 填充内存
	        2.1 遍历当前页面 Icon[]
	        	2.2 GetPicFmts(Icon):获取对应处理格式模块
	        	2.3 PicFmt->Read(显示缓存,Icon ):读入显存中
		3. 刷新到显示中 	

输入接口

输入接口使用位置:

static int (*GetInputEvent)();         // 获得输入数据的函数 
    1. 获取报点
    2. 判断点是否有在某个图标位置内部,有返回数组下标

在第一步获取报点处使用,输入接口相对于六个界面是独立存在的,所以可以用个独立的循环线程存在

Input{
    char *name;                 // 输入模块名称
    void (*Init)()              // 输入设备初始化
        1. 打开设备,创建线程轮询等待事件上报
        2. 在线程中,有数据上报就唤醒 GetInputData() 上的睡眠进程
        
    void (*GetInputData)()      // 获取输入数据
        等待输入事件并上报
}

当然感觉输入设备不应该只有触摸屏,想以后也可以响应按键,响应网络,响应终端等输入设备,所以这个结构体还需要再改改
需要用链表管理

Input{
    char *name;                 // 输入模块名称
    void (*Init)()              // 输入设备初始化
        1. 打开设备,创建线程轮询等待事件上报
        2. 在线程中,有数据上报就唤醒 GetInputData() 上的睡眠进程
        
    void (*GetInputData)()      // 获取输入数据
        检查是否有事件上报,有则上报,无则睡眠
        
    ptNext                      // 下一个模块 
}

也需要修改界面的 GetInputEvent() 函数,以及 Run() 函数因为每个界面响应的按键方式可能不一定

static int (*GetInputEvent)();         // 获得输入数据的函数 
    1. 获取报点
    2. 判断点是否有在某个图标位置内部,有返回数组下标
    3. 获取按键
 
void (*Run)("当前图片路径");        // 页面运行函数
        Display();			                // 显示主界面
        当前图片路径 = 根目录
        for(;;)
            GetInputEvent();				// 获取哪个按钮被点击
            switch()
                case: 选择目录
                    PageSelect("选择界面")->Run("当前图片路径");
                case: 设置间隔
                    PageSelect("连播界面")->Run("当前图片路径");
				
				################
				添加按键等响应处理

调试输出接口

调试输出接口嘛,就是支持各种 log 输出,可以从 文件输出、标准输出、网络输出、串口输出, 屏上输出等等

Debug{
    char *name;             // 输出名称
    void (*Init)()          // 调试模块初始化 
        1. 打开输出模块
        2. 创建线程,等待 DebugPrint() 函数输入,再转发输出模块输出
    
    void (*DebugPrint)(格式化字符串)    // 输出函数
        唤醒线程,让其通过特定模块输出 log
    
    ptNext;                 // 下一个模块 
}

优化接口

显示内存管理

显示过程中发现还有获取一块内存的操作,像这种也可以使用缓冲池管理

Display() 流程:
		1. 获取一块内存
		2. 填充内存
	        2.1 遍历当前页面 Icon[]
	        	2.2 GetPicFmts(Icon):获取对应处理格式模块
	        	2.3 PicFmt->Read(显示缓存,Icon ):读入显存中
		3. 刷新到显示中 		

可用如下结构体管理:

VideoMem{
    int count;
    void (*Init)(内存大小,内存块数)         // 建池
    void (*Get)()                            // 从缓冲池获取数据
    void (*Set)()                            // 释放到缓冲池中
}

后续想法

阅读界面

比如想在浏览界面,支持文本阅读,那要怎么实现呢?
阅读界面逻辑如下:

Page {
    char *name;           	            // 页面名字 
    
    void (*Run)("当前文件路径");        // 页面运行函数
        Display();			                // 显示主界面
        for(;;)
            GetInputEvent();				// 获取哪个按钮被点击
            switch()
                case: 上一页
                	根据屏大小,及字体大小,更新上一页开始文件中位置
                    Display()
                case: 下一页
                	根据屏大小,及字体大小,更新下一页开始文件中位置
               		Display()

    static int (*GetInputEvent)();         // 获得输入数据的函数 
        1. 获取报点
        2. 判断点位置,左半屏上翻,右半屏下翻
    
    static void (*Display)();              // 页面显示函数
        1. 获取一块内存
        2. 填充内存
            2.1 读取文件
            2.2 加载到显示内存指定位置
        3. 刷新到显示中
    
    Page *ptNext;                       // 链表管理
    Icon[]                              // 所有包含的图标
}

根据之前学习过程可知道

	怎样在 LCD 上显示文件:
		1. 根据文件获得字符编码 {
				ASCII, GBK【这一行是大陆用户默认的】
				UTF-8,
				UTF16LE,
				UTF16BE,
			}
			
		2. 根据编码从字体文件中得到 字体数据【包括点阵图】{
				ASCII字体数组,
				HZK16,
				GBK字体文件,freetype,
			}
		
		3. 把 点阵图 在 LCD 上显示出来

字库:主要是将不同编码的字符,转换成对应的点阵图,所以
一个字库可以支持多种编码方式

freetype: 支持 ASCII/GBK/UTF-8/UTF16LE/UTF16BE
HZK16: 支持 ASCII/GBK
ASCII: 支持 ASCII /UTF-16LE/UTF-16BE/UTF-8 			# ? 有这么多?参考程序提取

针对浏览界面读取 txt 场景,处理流程大致如下:

static void (*Display)();              // 页面显示函数
        1. 获取一块内存
        2. 填充内存
            2.1 读取文件,判断文本字符编码
			2.2 根据字符字符获取让对应模块处理,获取其点阵图
			2.3 将获取的点阵图显示在显示缓存合适位置
        3. 刷新到显示中

故可以针对上面的字符编码,字库进行抽象如下:

Encoding{
    char *name;                     // 编码名称 
    void (*isSupport)(文件路径)     // 是否是某字符文件
        1. 打开文件,读取到内存中
        2. 判断是否支持此种编码文件
        
    void (*DisplayTxt)(显示缓存,文件路径,文件内部位置,字体大小)
        1. 打开文件,
        2. 根据屏幕尺寸及字体大小,更新可从指定位置读取多少字符
        3. 将读入的字符通过支持此编码的字库的 GetBmpData() 获得位图
        4. 将获得的位图填充到显示缓存中
    
    ptNext                          // 链表管理 
    ptNextFont						// 会有个绑定支持字符操作,放到这个链表中
}

Fonts{
    char *name;                     // 字库名称
    void (*Init)()                  // 字库初始化
    void (*GetBmpData)(字符,返回位图)        // 根据字符,返回对应的位图
    ptNext                          // 指向下一个字库

}

更新显示场景逻辑:

static void (*Display)();              // 页面显示函数
        1. 获取一块内存
        2. 填充内存
            2.1 遍历编码表, 调用 isSupport() 判断是否有支持的
				2.2 对应编码的 DisplayTxt() 显示字符
        3. 刷新到显示中
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值