续:【IMX6ULL项目】IMX6ULL上Linux系统实现产测工具框架(一):https://developer.aliyun.com/article/1532307
四、 文字系统:
*4.1 FontBitMap
在font_manager.h描述一个文字的位图, 定义字体位图结构体,用于存储字体的位图信息
// 定义字体位图结构体,用于存储字体的位图信息 typedef struct FontBitMap { int iLeftUpX; // 字体位图左上角的X坐标 int iLeftUpY; // 字体位图左上角的Y坐标 int iWidth; // 字体位图的宽度 int iRows; // 字体位图的行数 int iCurOriginX; // 当前字符的原点X坐标 int iCurOriginY; // 当前字符的原点Y坐标 int iNextOriginX; // 下一个字符的原点X坐标 int iNextOriginY; // 下一个字符的原点Y坐标 unsigned char *pucBuffer; // 指向字体位图数据的指针 } FontBitMap, *PFontBitMap; // FontBitMap是结构体类型,PFontBitMap是指向该结构体的指针类型
Region
因为显示系统和文字系统都需要用到这个区域结构体,所以我们直接把它拿出来定义成一个公共的头文件common.h
// 定义矩形区域结构体 typedef struct Region { int iLeftUpX; // 区域左上角的X坐标 int iLeftUpY; // 区域左上角的Y坐标 int iWidth; // 区域的宽度 int iHeigh; // 区域的高度 }Region, *PRegion; // 定义矩形区域及其指针类型
*4.2 FontOpr
在font_manager.h定义字体操作结构体,用于管理不同字体的操作函数
// 定义字体操作结构体,用于管理不同字体的操作函数 typedef struct FontOpr { char *name; // 字体操作的名称 int (*FontInit)(char *aFineName); // 初始化字体的函数指针 int (*SetFontSize)(int iFontSize); // 设置字体大小的函数指针 int (*GetFontBitMap)(unsigned int dwCode, PFontBitMap ptFontBitMap); // 获取字体位图的函数指针 struct FontOpr *ptNext; // 指向下一个字体操作结构体的指针,用于链表管理 } FontOpr, *PFontOpr; // FontOpr是结构体类型,PFontOpr是指向该结构体的指针类型
*4.3 FontOpr g_tFreetypeOpr
在freetype.c中定义一个字体操作结构体,包含字体名字、字体初始化、设置字体大小、获取字体位图等函数指针
// 定义一个字体操作结构体,包含字体初始化、设置字体大小、获取字体位图等函数指针 static FontOpr g_tFreetypeOpr = { .name = "freetype", .FontInit = FreeTypeFontInit, .SetFontSize = FreeTypeSetFontSize, .GetFontBitMap = FreeTypeGetFontBitMap, };
4.4 FreeTypeFontInit(char *aFineName)
在freetype.c中初始化FreeType字体库
// 初始化FreeType字体库 static int FreeTypeFontInit(char *aFineName) { FT_Library library; int error; // 初始化FreeType库 error = FT_Init_FreeType( &library ); if (error) { printf("FT_Init_FreeType err\n"); return -1; } // 从指定文件创建字体面 error = FT_New_Face(library, aFineName, 0, &g_tFace ); if (error) { printf("FT_New_Face err\n"); return -1; } // 设置字体大小 FT_Set_Pixel_Sizes(g_tFace, g_iDefaultFontSize, 0); return 0; }
4.5 FreeTypeSetFontSize(int iFontSize)
在freetype.c中设置字体大小
// 设置字体大小 static int FreeTypeSetFontSize(int iFontSize) { // 设置字体大小 FT_Set_Pixel_Sizes(g_tFace, iFontSize, 0); return 0; }
4.6 FreeTypeGetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap)
在freetype.c中获取字体位图
// 获取字体位图 static int FreeTypeGetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap) { int error; FT_Vector pen; FT_Glyph glyph; // 设置当前光标位置,单位为1/64像素 pen.x = ptFontBitMap->iCurOriginX * 64; pen.y = ptFontBitMap->iCurOriginY * 64; // 设置变换矩阵 FT_Set_Transform(g_tFace, 0, &pen); // 加载字符位图 error = FT_Load_Char(g_tFace, dwCode, FT_LOAD_RENDER); if (error) { printf("FT_Load_Char error\n"); return -1; } // 获取位图缓冲区 ptFontBitMap->pucBuffer = slot->bitmap.buffer; // 设置字体位图的区域信息 ptFontBitMap->tRegion.iLeftUpX = slot->bitmap_left; ptFontBitMap->tRegion.iLeftUpY = ptFontBitMap->iCurOriginY*2 - slot->bitmap_top; ptFontBitMap->tRegion.iWidth = slot->bitmap.width; ptFontBitMap->tRegion.iHeigh = slot->bitmap.rows; ptFontBitMap->iNextOriginX = ptFontBitMap->iCurOriginX + slot->advance.x / 64; ptFontBitMap->iNextOriginY = ptFontBitMap->iCurOriginY; return 0; }
4.7 FreetypeRegister(void)
在freetype.c中注册FreeType字体操作结构体
// 注册FreeType字体操作结构体 void FreetypeRegister(void) { RegisterFont(&g_tFreetypeOpr); }
&& 4.8 字符管理器框架
我们可能要用到的字体有多种,那么怎么选择用哪个字符呢,所以我们要编写一个程序管理多种字符。
4.9 FontsRegister(void)
在font_manager.c注册字体操作结构体,这里示例了注册FreeType字体
// 注册字体操作结构体,这里示例了注册FreeType字体 void FontsRegister(void) { extern void FreetypeRegister(void); FreetypeRegister(); }
4.10 SelectAndInitFont(char *aFontOprName, char *aFontFileName)
在font_manager.c选择并初始化一个字体操作结构体
// 选择并初始化一个字体操作结构体 int SelectAndInitFont(char *aFontOprName, char *aFontFileName) { PFontOpr ptTmp = g_ptFonts; while (ptTmp) { // 比较字体操作结构体的名称,找到匹配的结构体 if (strcmp(ptTmp->name, aFontOprName) == 0) break; ptTmp = ptTmp->ptNext; } // 如果没有找到匹配的结构体,返回错误 if (!ptTmp) return -1; // 设置默认的字体操作结构体 g_ptDefaulFontOpr = ptTmp; // 调用结构体的初始化函数,初始化字体 return ptTmp->FontInit(aFontFileName); }
4.10 SetFontSize(int iFontSize)
在font_manager.c设置当前默认字体的字体大小
// 设置当前默认字体的字体大小 int SetFontSize(int iFontSize) { return g_ptDefaulFontOpr->SetFontSize(iFontSize); }
4.11 GetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap)
在font_manager.c获取当前默认字体的字符位图
// 获取当前默认字体的字符位图 int GetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap) { return g_ptDefaulFontOpr->GetFontBitMap(dwCode, ptFontBitMap); }
*4.12 DrawFontBitMap(PFontBitMap ptFontBitMap, unsigned int dwColor)
在LCD上显示出来,在disp_manager.c中还要 实现一个绘制函数:DrawFontBitMap
void DrawFontBitMap(PFontBitMap ptFontBitMap, unsigned int dwColor) { // 定义并初始化各种变量 int i, j, p, q; // 获取绘制区域的左上角X坐标 int x = ptFontBitMap->tRegion.iLeftUpX; // 获取绘制区域的左上角Y坐标 int y = ptFontBitMap->tRegion.iLeftUpY; // 计算绘制区域的右下角X坐标 int x_max = x + ptFontBitMap->tRegion.iWidth; // 计算绘制区域的右下角Y坐标 int y_max = y + ptFontBitMap->tRegion.iHeigh; // 获取区域宽度 int width = ptFontBitMap->tRegion.iWidth; // 获取位图缓冲区 unsigned char *buffer = ptFontBitMap->pucBuffer; // 遍历绘制区域中的每个像素 for ( j = y, q = 0; j < y_max; j++, q++ ) { for ( i = x, p = 0; i < x_max; i++, p++ ) { // 如果像素位置超出屏幕范围,忽略该像素 if ( i < 0 || j < 0 || i >= g_tDispBuff.iXres || j >= g_tDispBuff.iYres ) continue; // 如果位图缓冲区中该像素点有数据(即需要绘制的点),则用指定颜色在屏幕上绘制该像素 if (buffer[q * width + p]) PutPixel(i, j, dwColor); } } }
*4.13 测试代码:
#include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <linux/fb.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <sys/ioctl.h> #include <stdlib.h> #include <font_manager.h> #include <disp_manager.h> #define FONTDATAMAX 4096 // 8x16 字体数据 static const unsigned char fontdata_8x16[FONTDATAMAX] = { // 省略了点阵数据 }; // 在LCD指定位置上显示一个8*16的字符 void lcd_put_ascii(int x, int y, unsigned char c) { unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16]; int i, b; unsigned char byte; for (i = 0; i < 16; i++) { byte = dots[i]; for (b = 7; b >= 0; b--) { if (byte & (1<<b)) { // 显示像素点 PutPixel(x+7-b, y+i, 0xffffff); // 白色 } else { // 不显示像素点 PutPixel(x+7-b, y+i, 0); // 黑色 } } } } int main(int argc, char **argv) { PDispBuff ptBuffer; int error; FontBitMap tFontBitMap; char *str= "Hello Linux"; int i = 0; int lcd_x; int lcd_y; int font_size; // 检查输入参数个数 if (argc != 5) { printf("Usage: %s <font_file> <lcd_x> <lcd_y> <font_size>\n", argv[0]); return -1; } lcd_x = strtol(argv[2], NULL, 0); lcd_y = strtol(argv[3], NULL, 0); font_size = strtol(argv[4], NULL, 0); // 初始化显示设备 DisplayInit(); SelectDefaultDisplay("fb"); InitDefaultDisplay(); ptBuffer = GetDisplayBuffer(); // 注册并初始化字体 FontsRegister(); error = SelectAndInitFont("freetype", argv[1]); if (error) { printf("SelectAndInitFont err\n"); return -1; } // 设置字体大小 SetFontSize(font_size); // 循环显示每个字符 while (str[i]) { // 获取字符位图 tFontBitMap.iCurOriginX = lcd_x; tFontBitMap.iCurOriginY = lcd_y; error = GetFontBitMap(str[i], &tFontBitMap); if (error) { printf("SelectAndInitFont err\n"); return -1; } // 绘制字符到缓冲区 DrawFontBitMap(&tFontBitMap,0xff0000); // 红色 // 刷新显示区域 FlushDisplayRegion(&tFontBitMap.tRegion, ptBuffer); // 更新下一个字符的位置 lcd_x = tFontBitMap.iNextOriginX; lcd_y = tFontBitMap.iNextOriginY; i++; } return 0; }
五、UI系统:
所谓UI,就是User Interface(用户界面),有图像界面(GUI)等,我们的UI系统,就是构造各类GUI元素,比如按钮(目前只实现按钮)
*5.1 Button
在ui.h定义Button结构体,包含按钮的名称、状态、区域、绘制函数和按下处理函数
// 定义两个函数指针类型,用于按钮的绘制和按下事件处理 typedef int (*ONDRAW_FUNC)(struct Button *ptButton, PDispBuff ptDispBuff); // 绘制函数指针类型 typedef int (*ONPRESSED_FUNC)(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent); // 按下处理函数指针类型 // 定义Button结构体,包含按钮的名称、状态、区域、绘制函数和按下处理函数 typedef struct Button { char *name; // 按钮名称 int status; // 按钮状态 Region tRegion; // 按钮区域 ONDRAW_FUNC OnDraw; // 绘制函数指针 ONPRESSED_FUNC OnPressed; // 按下处理函数指针 }Button, *PButton; // Button是结构体类型,PButton是指向Button的指针类型
*5.2 InitButton(PButton ptButton, char *name, PRegion ptRegion, ONDRAW_FUNC OnDraw, ONPRESSED_FUNC OnPressed)
在button.c初始化按钮的函数
// 初始化按钮的函数 void InitButton(PButton ptButton, char *name, PRegion ptRegion, ONDRAW_FUNC OnDraw, ONPRESSED_FUNC OnPressed) { ptButton->status = 0; // 初始化状态为0,即未按下 ptButton->name = name; // 设置按钮名称 ptButton->tRegion = *ptRegion; // 设置按钮区域 ptButton->OnDraw = OnDraw ? OnDraw : DefaultOnDraw; // 设置绘制函数,如果未提供,则使用默认绘制函数 ptButton->OnPressed = OnPressed ? OnPressed : DefaultOnPressed; // 设置按下处理函数,如果未提供,则使用默认处理函数 }
5.3 DrawRegion(PRegion ptRegion, unsigned int dwColor)
在disp_manager.c中将ptRegion这一部分区域绘制成dwColor的颜色
// 函数DrawRegion用于绘制一个指定颜色和区域的矩形 void DrawRegion(PRegion ptRegion, unsigned int dwColor) { // 获取区域的左上角坐标 int x = ptRegion->iLeftUpX; int y = ptRegion->iLeftUpY; // 获取区域的宽度和高度 int width = ptRegion->iWidth; int heigh = ptRegion->iHeigh; // 使用两个嵌套的循环来遍历区域内的每一个像素 int i, j; for (j = y; j < y + heigh; j++) { for (i = x; i < x + width; i++) { // 对于每个像素,调用PutPixel函数来设置像素的颜色 PutPixel(i, j, dwColor); } } }
5.4 DrawTextInRegionCentral(char *name, PRegion ptRegion, unsigned int dwColor)
在disp_manager.c中实现函数DrawTextInRegionCentral用于在指定区域内居中显示文本
// 函数DrawTextInRegionCentral用于在指定区域内居中显示文本 void DrawTextInRegionCentral(char *name, PRegion ptRegion, unsigned int dwColor) { // 计算文本字符串的长度 int n = strlen(name); // 计算每个字符的宽度,以便文本能够居中显示 int iFontSize = ptRegion->iWidth / n / 2; FontBitMap tFontBitMap; // 字体位图结构体 int iOriginX, iOriginY; // 字符绘制的起始坐标 int i = 0; // 用于遍历字符串的索引 int error; // 用于存储错误代码 // 如果计算出的字体大小超过了区域的高度,则将字体大小设置为区域的高度 if (iFontSize > ptRegion->iHeigh) iFontSize = ptRegion->iHeigh; // 计算文本在区域内的起始X坐标,确保文本居中 iOriginX = (ptRegion->iWidth - n * iFontSize)/2 + ptRegion->iLeftUpX; // 计算文本在区域内的起始Y坐标,确保文本居中 iOriginY = (ptRegion->iHeigh - iFontSize)/2 + iFontSize + ptRegion->iLeftUpY; // 设置字体大小 SetFontSize(iFontSize); // 遍历文本字符串中的每个字符 while (name[i]) { // 获取当前字符的字体位图 tFontBitMap.iCurOriginX = iOriginX; tFontBitMap.iCurOriginY = iOriginY; error = GetFontBitMap(name[i], &tFontBitMap); if (error) { printf("SelectAndInitFont err\n"); return; } // 在缓冲区上绘制当前字符的字体位图 DrawFontBitMap(&tFontBitMap, dwColor); // 更新下一个字符的起始坐标 iOriginX = tFontBitMap.iNextOriginX; iOriginY = tFontBitMap.iNextOriginY; i++; } }
* 5.5 DefaultOnDraw(struct Button *ptButton, PDispBuff ptDispBuff)
在button.c默认的绘制按钮的函数
// 默认的绘制按钮的函数 static int DefaultOnDraw(struct Button *ptButton, PDispBuff ptDispBuff) { /* 绘制按钮的底色 */ DrawRegion(&ptButton->tRegion, BUTTON_DEFAULT_COLOR); /* 在按钮的中央绘制文本 */ DrawTextInRegionCentral(ptButton->name, &ptButton->tRegion, BUTTON_TEXT_COLOR); /* 将绘制的区域刷新到显示设备上,比如LCD或网页 */ FlushDisplayRegion(&ptButton->tRegion, ptDispBuff); return 0; // 返回0表示成功 }
*5.6 DefaultOnPressed(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent)
在button.c默认的按钮按下处理函数
// 默认的按钮按下处理函数 static int DefaultOnPressed(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent) { unsigned int dwColor = BUTTON_DEFAULT_COLOR; // 默认底色 ptButton->status = !ptButton->status; // 切换按钮状态 if (ptButton->status) dwColor = BUTTON_PRESSED_COLOR; // 如果按钮被按下,改变底色 /* 绘制按钮的底色 */ DrawRegion(&ptButton->tRegion, dwColor); /* 在按钮的中央绘制文本 */ DrawTextInRegionCentral(ptButton->name, &ptButton->tRegion, BUTTON_TEXT_COLOR); /* 将绘制的区域刷新到显示设备上 */ FlushDisplayRegion(&ptButton->tRegion, ptDispBuff); }
以上在ui.h中定义按钮的默认颜色、按下时的颜色和文本颜色
#define BUTTON_DEFAULT_COLOR 0xff0000 // 默认颜色,通常是红色 #define BUTTON_PRESSED_COLOR 0x00ff00 // 按下时的颜色,通常是绿色 #define BUTTON_TEXT_COLOR 0x000000 // 文本颜色,通常是黑色
5.7 测试代码:
// 包含必要的系统头文件 #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <linux/fb.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <sys/ioctl.h> #include <stdlib.h> // 包含自定义的显示管理器和字体管理器头文件 #include <disp_manager.h> #include <font_manager.h> #include <ui.h> // 假设这是一个包含GUI相关定义的头文件 // 主函数 int main(int argc, char **argv) { // 定义显示缓冲区和错误代码的指针,以及按钮和区域的结构体 PDispBuff ptBuffer; int error; Button tButton; Region tRegion; // 检查命令行参数是否正确 if (argc != 2) { printf("Usage: %s <font_size>\n", argv[0]); return -1; } // 初始化显示 DisplayInit(); // 选择默认的显示操作接口,这里假设"fb"是一个帧缓冲设备 SelectDefaultDisplay("fb"); // 初始化默认的显示操作接口 InitDefaultDisplay(); // 获取显示缓冲区 ptBuffer = GetDisplayBuffer(); // 注册字体 FontsRegister(); // 选择并初始化字体,这里使用的是"freetype"字体,字体大小由命令行参数指定 error = SelectAndInitFont("freetype", argv[1]); if (error) { printf("SelectAndInitFont err\n"); return -1; } // 初始化区域的坐标和大小 tRegion.iLeftUpX = 200; tRegion.iLeftUpY = 200; tRegion.iWidth = 300; tRegion.iHeigh = 100; // 初始化按钮,设置按钮的文本和区域,以及回调函数 InitButton(&tButton, "test", &tRegion, NULL, NULL); // 绘制按钮 tButton.OnDraw(&tButton, ptBuffer); // 进入主循环,当按钮被按下时执行相应的操作 while (1) { tButton.OnPressed(&tButton, ptBuffer, NULL); // 暂停2秒 sleep(2); } // 程序正常结束 return 0; }
六、页面系统:
*6.1 PageAction
定义一个结构体类型 PageAction,用于表示页面动作
// 定义一个结构体类型 PageAction,用于表示页面动作 typedef struct PageAction { // 页面动作的名称,是一个字符串指针 char *name; // 指向执行页面动作的函数指针,该函数接受一个void指针参数 void (*Run)(void *pParams); // 指向下一个PageAction结构体的指针,用于链表结构 struct PageAction *ptNext; } PageAction, *PPageAction;
&& 6.2 页面管理器框架
页面管理器用来管理页面,只需要实现2个函数:
1. PagesRegister : 把多个页面注册进链表
2. Page(name) :取出某个页面
6.3 PageRegister(PPageAction ptPageAction)
在page_manager.c 中实现页面动作注册函数,将新的页面动作添加到链表的头部
// 声明一个静态的全局变量,用于存储页面动作链表的头指针 static PPageAction g_ptPages = NULL; // 实现页面动作注册函数,将新的页面动作添加到链表的头部 void PageRegister(PPageAction ptPageAction) { // 将新动作的下一个指针指向当前链表的头 ptPageAction->ptNext = g_ptPages; // 更新链表头指针,使其指向新添加的动作 g_ptPages = ptPageAction; }
6.4 PPageAction Page(char *name)
在page_manager.c 中实现根据名称查找页面动作的函数
// 实现根据名称查找页面动作的函数 PPageAction Page(char *name) { // 从链表头开始遍历 PPageAction ptTmp = g_ptPages; // 遍历链表直到找到匹配的名称或者遍历完整个链表 while (ptTmp) { // 使用strcmp函数比较名称是否相等 if (strcmp(name, ptTmp->name) == 0) // 如果找到匹配的名称,返回对应的页面动作指针 return ptTmp; // 移动到下一个页面动作 ptTmp = ptTmp->ptNext; } // 如果没有找到匹配的名称,返回NULL return NULL; }
6.5 PageAction MainPage
在main_page.c中创建一个PageAction MainPage,定义一个静态的PageAction结构体变量,表示主页面动作
// 包含页面管理器头文件,这个文件中声明了页面动作相关的结构体和函数 #include <page_manager.h> // 包含标准输入输出头文件,提供了printf等函数 #include <stdio.h> // 定义一个静态函数,用于执行主页面动作 static void MainPageRun(void *pParams) { // 使用printf打印文件名、函数名和当前行号,用于调试信息 printf("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); } // 定义一个静态的PageAction结构体变量,表示主页面动作 static PageAction g_tMainPage = { // 设置页面动作的名称为"main" .name = "main", // 设置执行页面动作的函数为MainPageRun .Run = MainPageRun, }; // 定义一个函数,用于注册主页面动作 void MainPageRegister(void) { // 调用页面管理器的注册函数,将主页面动作添加到页面动作链表中 PageRegister(&g_tMainPage); }
6.6 测试代码:
// 包含内存映射相关的头文件 #include <sys/mman.h> // 包含系统类型定义的头文件 #include <sys/types.h> // 包含文件状态相关的头文件 #include <sys/stat.h> // 包含Unix标准函数定义的头文件 #include <unistd.h> // 包含Linux帧缓冲设备相关的头文件 #include <linux/fb.h> // 包含文件控制选项相关的头文件 #include <fcntl.h> // 包含标准输入输出函数定义的头文件 #include <stdio.h> // 包含字符串处理函数定义的头文件 #include <string.h> // 包含输入输出控制函数定义的头文件 #include <sys/ioctl.h> // 包含标准库函数定义的头文件 #include <stdlib.h> // 包含页面管理器头文件,这个文件中声明了页面动作相关的结构体和函数 #include <page_manager.h> // 程序入口点,接受命令行参数 int main(int argc, char **argv) { // 注册所有页面动作,这通常会在程序启动时执行 PagesRegister(); // 查找名为"main"的页面动作,并执行其Run函数 // 这里传递了一个NULL参数,表示没有额外的参数传递给Run函数 Page("main")->Run(NULL); // 程序正常退出,返回0 return 0; }
七、业务系统
7.1 业务系统流程图
在main.c中实现业务系统
// 包含内存管理、系统类型、文件状态、Unix标准函数、Linux帧缓冲设备、文件控制、标准输入输出、字符串处理、输入输出控制和标准库函数相关的头文件 #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <linux/fb.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <sys/ioctl.h> #include <stdlib.h> // 包含显示管理、字体管理、输入管理和页面管理相关的头文件 #include <disp_manager.h> #include <font_manager.h> #include <input_manager.h> #include <page_manager.h> // 程序的主函数,接受命令行参数 int main(int argc, char **argv) { // 声明一个显示缓冲区指针和错误码变量 PDispBuff ptBuffer; int error; // 检查命令行参数的数量,如果不等于2(程序名和字体文件名),则打印使用说明并返回错误码 if (argc != 2) { printf("Usage: %s <font_file>\n", argv[0]); return -1; } // 初始化显示系统 DisplayInit(); // 选择默认的显示设备,这里是帧缓冲设备 SelectDefaultDisplay("fb"); // 初始化默认的显示设备 InitDefaultDisplay(); // 初始化输入系统,包括输入设备的初始化 InputInit(); IntpuDeviceInit(); // 注册并初始化字体系统 FontsRegister(); error = SelectAndInitFont("freetype", argv[1]); // 如果字体选择和初始化失败,打印错误信息并返回错误码 if (error) { printf("SelectAndInitFont err\n"); return -1; } // 注册页面系统 PagesRegister(); // 运行主页面 Page("main")->Run(NULL); // 程序正常退出,返回0 return 0; }
- 程序开始运行,进入main函数,首先检查命令行参数的数量。如果参数数量不等于2(一个是程序自身的路径,另一个是字体文件的路径),则打印使用说明并返回错误码,程序结束。
- 如果命令行参数数量正确,程序会进行显示系统的初始化(DisplayInit)。这可能包括分配内存、设置显示分辨率等操作。
- 然后,程序会选择默认的显示设备(SelectDefaultDisplay),在这个例子中是帧缓冲设备(frame buffer),并初始化该显示设备(InitDefaultDisplay)。帧缓冲设备是一种可以直接在内存中操作像素来绘制图像的设备。
- 接下来,程序会初始化输入系统(InputInit和IntpuDeviceInit)。输入设备的初始化。
- 然后,程序会注册并初始化字体系统(FontsRegister和SelectAndInitFont)。字体文件的路径从命令行参数中获取。如果字体选择和初始化失败,程序会打印错误信息并返回错误码,结束运行。
- 接着,程序会注册页面系统(PagesRegister)。页面系统通常用于管理GUI的各个页面(如主页、菜单页等)。
- 最后,程序会运行主页面(Page("main")->Run(NULL))。这通常意味着显示主页面并等待用户的输入。
7.2 主页面流程图:
在main_page.c中实现主页面流程
&&& 7.3 抽象出配置文件结构体(ItemCfg)
在config.h中定义一个结构体,用于表示配置文件中的一个条目
// 定义一个结构体,用于表示配置文件中的一个条目 typedef struct ItemCfg { int index; // 条目的索引 char name[100]; // 条目的名称,最大长度为100个字符 int bCanBeTouched; // 条目是否可以被触摸(例如,在GUI中) char command[100]; // 条目对应的命令,最大长度为100个字符 } ItemCfg, *PItemCfg; // ItemCfg是结构体类型,PItemCfg是指向该结构体的指针类型
7.4 ParseConfigFile(char *strFileName)
在config.h中定义一个函数,用于解析配置文件
// 定义一个静态数组,用于存储配置文件中的条目,最大数量由ITEMCFG_MAX_NUM定义 static ItemCfg g_tItemCfgs[ITEMCFG_MAX_NUM]; // 定义一个静态变量,用于记录当前配置文件中条目的数量 static int g_iItemCfgCount = 0; // 定义一个函数,用于解析配置文件 int ParseConfigFile(char *strFileName) { FILE *fp; // 文件指针,用于打开和读取文件 char buf[100]; // 缓冲区,用于存储从文件读取的一行数据 char *p = buf; // 指向缓冲区的指针,用于处理读取的数据 /* 1. 打开配置文件 */ fp = fopen(CFG_FILE, "r"); if (!fp) { printf("can not open cfg file %s\n", CFG_FILE); return -1; // 打开文件失败,返回-1 } while (fgets(buf, 100, fp)) // 循环读取文件的每一行 { /* 2.1 读取每一行数据 */ buf[99] = '\0'; // 确保缓冲区末尾有一个字符串结束符 /* 2.2 跳过开头的空格或制表符 */ p = buf; while (*p == ' ' || *p =='\t') p++; /* 2.3 忽略注释行(以#开头的行) */ if (*p == '#') continue; /* 2.4 处理有效行数据 */ g_tItemCfgs[g_iItemCfgCount].command[0] = '\0'; // 初始化命令字段为空字符串 g_tItemCfgs[g_iItemCfgCount].index = g_iItemCfgCount; // 设置索引为当前条目计数器值 sscanf(p, "%s %d %s", g_tItemCfgs[g_iItemCfgCount].name, &g_tItemCfgs[g_iItemCfgCount].bCanBeTouched, \ g_tItemCfgs[g_iItemCfgCount].command); // 从缓冲区读取数据到结构体字段 g_iItemCfgCount++; // 增加条目计数器 } return 0; // 解析成功,返回0 }
7.4 GetItemCfgCount(void)
在config.h中定义一个函数,用于获取配置文件中条目的数量
// 定义一个函数,用于获取配置文件中条目的数量 int GetItemCfgCount(void) { return g_iItemCfgCount; // 返回条目计数器的值 }
7.5 GetItemCfgByIndex(int index)
在config.h中定义一个函数,用于根据索引获取配置文件中的条目
// 定义一个函数,用于根据索引获取配置文件中的条目 PItemCfg GetItemCfgByIndex(int index) { if (index < g_iItemCfgCount) // 检查索引是否在有效范围内 return &g_tItemCfgs[index]; // 返回指向对应条目的指针 else return NULL; // 索引无效,返回NULL }
7.6 GetItemCfgByName(char *name)
在config.h中定义一个函数,用于根据名称获取配置文件中的条目
// 定义一个函数,用于根据名称获取配置文件中的条目 PItemCfg GetItemCfgByName(char *name) { int i; for (i = 0; i < g_iItemCfgCount; i++) // 遍历所有条目 { if (strcmp(name, g_tItemCfgs[i].name) == 0) // 比较名称是否匹配 return &g_tItemCfgs[i]; // 返回指向匹配条目的指针 } return NULL; // 没有找到匹配的条目,返回NULL }
*7.7 生成界面
想显示这样的界面
通过配置文件可以知道有哪些按钮,知道每个按钮的名字
只剩最后一项了:怎么确定每个按钮的位置、大小?需要计算!
7.8 *MainPageRun(void *pParams)
在main_page.c中定义了主页面的运行函数 MainPageRun,它负责读取配置文件、生成按钮和界面,并进入一个无限循环来监听和处理
static void MainPageRun(void *pParams) { int error; // 用于存储函数执行过程中的错误代码 InputEvent tInputEvent; // 用于存储输入事件的结构体 PButton ptButton; // 指向按钮对象的指针 PDispBuff ptDispBuff = GetDisplayBuffer(); // 获取显示缓冲区的指针 /* 读取配置文件 */ error = ParseConfigFile(); // 解析配置文件,将错误代码存储在error变量中 if (error) // 如果解析配置文件出错 return; // 直接返回,不继续执行 /* 根据配置文件生成按钮、界面 */ GenerateButtons(); // 根据配置文件中的信息生成按钮和界面 while (1) // 无限循环,用于持续监听和处理输入事件 { /* 读取输入事件 */ error = GetInputEvent(&tInputEvent); // 获取用户的输入事件,将错误代码存储在error变量中 if (error) // 如果获取输入事件出错 continue; // 跳过本次循环,继续下一次循环 /* 根据输入事件找到按钮 */ ptButton = GetButtonByInputEvent(&tInputEvent); // 根据输入事件找到对应的按钮对象 if (!ptButton) // 如果没有找到对应的按钮 continue; // 跳过本次循环,继续下一次循环 /* 调用按钮的OnPressed函数 */ ptButton->OnPressed(ptButton, ptDispBuff, &tInputEvent); // 调用找到的按钮的OnPressed函数,处理按钮按下事件 } }
7.9 GenerateButtons(void)
在main_page.c中根据屏幕的分辨率和按钮总数计算每个按钮的大小和位置,并居中显示这些按钮。按钮的宽度和高度根据屏幕分辨率和黄金分割比(0.618)进行计算以确定其尺寸,同时保留了一定的间隔(X_GAP 和 Y_GAP)。代码中利用两层嵌套循环计算并设置每个按钮的位置和区域,并对每个按钮对象进行初始化和绘制。
static void GenerateButtons(void) { int width, height; // 用于存储单个按钮的宽度和高度 int n_per_line; // 用于存储每行能放置的按钮数量 int row, rows; // row用于记录当前行号,rows用于记录总行数 int col; // 用于记录当前列号 int n; // 用于存储按钮总数 PDispBuff pDispBuff; // 指向显示缓冲区的指针 int xres, yres; // 用于存储显示区域的水平和垂直分辨率 int start_x, start_y; // 用于存储按钮区域开始的x,y坐标 int pre_start_x, pre_start_y; // 用于记录前一个按钮的起始x,y坐标 PButton pButton; // 用于指向当前操作的按钮对象 int i = 0; // 用于在循环中迭代按钮对象数组 /* 算出单个按钮的width/height */ g_tButtonCnt = n = GetItemCfgCount(); // 从配置获取按钮总数 pDispBuff = GetDisplayBuffer(); // 获取显示缓冲区对象 xres = pDispBuff->iXres; // 获取屏幕的水平分辨率 yres = pDispBuff->iYres; // 获取屏幕的垂直分辨率 width = sqrt(1.0 / 0.618 * xres * yres / n); // 根据页面比例和按钮总数计算单个按钮的宽度 n_per_line = xres / width + 1; // 计算每行可以放置的按钮数目 width = xres / n_per_line; // 调整按钮宽度,以适应每行的按钮数目 height = 0.618 * width; // 按照黄金比例计算按钮的高度 /* 居中显示: 计算每个按钮的region */ start_x = (xres - width * n_per_line) / 2; // 计算按钮起始x坐标,使其居中显示 rows = n / n_per_line; // 计算总行数 if (rows * n_per_line < n) // 如果按钮总数无法被整除,则行数+1 rows++; start_y = (yres - rows * height) / 2; // 计算按钮起始y坐标,使其居中显示 /* 计算每个按钮的region */ for (row = 0; (row < rows) && (i < n); row++) // 遍历所有行 { pre_start_y = start_y + row * height; // 计算当前行的y坐标 pre_start_x = start_x - width; // 设置初始x坐标值 for (col = 0; (col < n_per_line) && (i < n); col++) // 遍历每行的按钮 { pButton = &g_tButtons[i]; // 获取当前按钮对象的引用 pButton->tRegion.iLeftUpX = pre_start_x + width; // 设置按钮的左上角x坐标 pButton->tRegion.iLeftUpY = pre_start_y; // 设置按钮的左上角y坐标 pButton->tRegion.iWidth = width - X_GAP; // 设置按钮的宽度,并减去预设的间隔 pButton->tRegion.iHeigh = height - Y_GAP; // 设置按钮的高度,并减去预设的间隔 pre_start_x = pButton->tRegion.iLeftUpX; // 更新x坐标以供下一个按钮使用 /* InitButton */ InitButton(pButton, GetItemCfgByIndex(i)->name, NULL, NULL, MainPageOnPressed); // 初始化按钮,设置按钮的名称和事件处理函数 i++; // 按钮索引递增,移动到下一个按钮对象 } } /* OnDraw */ for (i = 0; i < n; i++) // 遍历所有按钮对象 g_tButtons[i].OnDraw(&g_tButtons[i], pDispBuff); // 调用按钮的OnDraw方法绘制按钮 }
*7.10 处理输入事件:GetButtonByInputEvent(PInputEvent ptInputEvent)
在main_page.c中定义了一个名为 GetButtonByInputEvent 的函数,其目的是根据提供的输入事件来寻找对应的按钮对象。输入事件 PInputEvent 包含事件类型 iType 以及与特定事件类型相关的数据(例如触摸事件的坐标或网络事件的字符串数据)。
static PButton GetButtonByInputEvent(PInputEvent ptInputEvent) { int i; char name[100]; // 用于存储从网络事件中解析出的按钮名称 if (ptInputEvent->iType == INPUT_TYPE_TOUCH) // 检查输入事件是否为触摸类型 { for (i = 0; i < g_tButtonCnt; i++) // 遍历所有按钮 { // 检查触摸点是否在当前遍历按钮的区域内 if (isTouchPointInRegion(ptInputEvent->iX, ptInputEvent->iY, &g_tButtons[i].tRegion)) return &g_tButtons[i]; // 如果是,返回对应的按钮对象 } } else if (ptInputEvent->iType == INPUT_TYPE_NET) // 检查输入事件是否为网络事件 { // 解析网络事件中的字符串,提取按钮名称 sscanf(ptInputEvent->str, "%s", name); return GetButtonByName(name); // 根据按钮名称查找并返回对应的按钮对象 } else { return NULL; // 如果事件类型既不是触摸也不是网络,返回NULL } return NULL; // 如果没有找到匹配的按钮,返回NULL }
7.11 isTouchPointInRegion(int iX, int iY, PRegion ptRegion)
在main_page.c中定义了一个名为 isTouchPointInRegion 的静态函数,用于检查给定的触摸点坐标 (iX, iY) 是否位于指定的区域 ptRegion 内。区域由其左上角坐标 (iLeftUpX, iLeftUpY) 和宽度 iWidth 以及高度 iHeigh 定义。
- 函数首先检查触摸点的X坐标是否在区域的左边界和右边界之间。如果触摸点的X坐标小于区域的左上角X坐标,或者大于或等于左上角X坐标加上区域的宽度(即右下角X坐标),则触摸点不在区域内,函数返回0。
- 接着,函数检查触摸点的Y坐标是否在区域的上边界和下边界之间。如果触摸点的Y坐标小于区域的左上角Y坐标,或者大于或等于左上角Y坐标加上区域的高度(即右下角Y坐标),则触摸点不在区域内,函数返回0。
- 如果触摸点的X和Y坐标都通过了上述检查,即触摸点完全位于区域内,函数返回1,表示触摸点在区域内。
static int isTouchPointInRegion(int iX, int iY, PRegion ptRegion) { // 检查触摸点的X坐标是否在区域的左上角X坐标和右下角X坐标之间 if (iX < ptRegion->iLeftUpX || iX >= ptRegion->iLeftUpX + ptRegion->iWidth) return 0; // 如果不在,返回0,表示触摸点不在区域内 // 检查触摸点的Y坐标是否在区域的左上角Y坐标和右下角Y坐标之间 if (iY < ptRegion->iLeftUpY || iY >= ptRegion->iLeftUpY + ptRegion->iHeigh) return 0; // 如果不在,返回0,表示触摸点不在区域内 return 1; // 如果触摸点的X和Y坐标都在区域内,返回1,表示触摸点在区域内 }
7.12 GetButtonByName(char *name)
在main_page.c中定义了一个名为 GetButtonByName 的静态函数,其目的是根据提供的名称来查找并返回对应的按钮对象。函数接受一个字符串指针 name 作为参数,该参数代表要查找的按钮的名称。
- 函数通过一个循环遍历全局数组 g_tButtons 中的所有按钮,数组的大小由 g_tButtonCnt 决定。
- 在循环中,使用 strcmp 函数来比较传入
1. static PButton GetButtonByName(char *name) 2. { 3. int i; 4. 5. // 遍历所有按钮 6. for (i = 0; i < g_tButtonCnt; i++) 7. { 8. // 使用strcmp函数比较传入的名称与当前按钮的名称是否相同 9. if (strcmp(name, g_tButtons[i].name) == 0) 10. return &g_tButtons[i]; // 如果名称匹配,返回当前按钮的指针 11. } 12. 13. return NULL; // 如果遍历完所有按钮都没有找到匹配的名称,返回NULL 14. }
7.13 MainPageOnPressed(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent)
在main_page.c中定义了一个名为 MainPageOnPressed 的静态函数,用于处理按钮的按下事件。函数接受三个参数:一个指向按钮结构体的指针 ptButton,一个指向显示缓冲区的指针 ptDispBuff,以及一个指向输入事件结构体的指针 ptInputEvent。
- 函数首先初始化按钮的颜色为默认颜色,并准备用于存储名称和状态的缓冲区。
- 然后,根据输入事件的类型(触摸屏事件或网络事件),执行不同的逻辑。
- 对于触摸屏事件,函数检查按钮是否可以被点击,如果可以,则切换按钮的状态并相应地修改按钮的颜色。
- 对于网络事件,函数从输入事件中解析出名称和状态,并根据状态的不同设置按钮的颜色。如果状态以数字开头,则将按钮名称更新为该数字,并设置为百分比颜色。
- 如果事件类型不是触摸屏或网络事件,或者状态不符合任何条件,函数返回-1。
- 最后,函数绘制按钮的底色,在按钮区域中央绘制文字,并将更新后的按钮区域刷新到显示缓冲区,然后返回0表示成功处理事件。
static PButton GetButtonByName(char *name) { int i; // 遍历所有按钮 for (i = 0; i < g_tButtonCnt; i++) { // 使用strcmp函数比较传入的名称与当前按钮的名称是否相同 if (strcmp(name, g_tButtons[i].name) == 0) return &g_tButtons[i]; // 如果名称匹配,返回当前按钮的指针 } return NULL; // 如果遍历完所有按钮都没有找到匹配的名称,返回NULL }