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

前言

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

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

需求界面

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

抽象流程

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

在这里插入图片描述

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

  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. 刷新到显示中

猜你喜欢

转载自blog.csdn.net/wangjun7121/article/details/88315460
今日推荐