前言

韦东山项目实战之七步从零编写带GUI的应用(项目开发|论文参考|C|GUI)学习笔记
文章中大多内容来自韦东山老师的文档,还有部分个人根据自己需求补充的内容

视频教程地址: https://www.bilibili.com/video/BV1it4y1Q75z

1、UI系统

在这里插入图片描述

▲程序分层

1.1、数据结构抽象

ui.h

#ifndef _UI_H
#define _UI_H

#include <common.h>
#include <disp_manager.h>
#include <input_manager.h>

/*
	按钮设备结构体
	name:按钮名称
	tRegion:按钮的位置
	OnDraw:绘制按钮 //ptButton:按钮设备指针 ptDispBuff:显示缓冲区指针
	OnPressed:按下反馈 //输入事件结构体指针
*/
typedef struct Button {
	char *name;
	int iFontSize;
	int status;
	Region tRegion;
	ONDRAW_FUNC OnDraw;
	ONPRESSED_FUNC OnPressed;
}Button, *PButton;

#endif

1.2、代码编写

1.2.1、typedef函数指针

为了代码的整洁,这里要用到typedef函数指针的概念
什么是函数指针?
函数指针的本质是一个指针类型变量
指针类型变量里保存的是什么?是地址
是什么的地址?是函数的地址
举个栗子🌰:

char (*p)(int, int);

上面的语句就定义了一个函数指针
这个指针变量p可以保存一个返回值类型为char,入口参数数量为2,入口参数类型为int
所以可以说函数指针的定义方式为

函数返回值类型 (* 指针变量名) (函数参数列表);

函数指针是一个指针变量,那么它的变量类型是什么?它的变量类型如下

char (*)(int,int);

函数指针怎么使用?

void Func(int x) // 声明一个函数*/
{
    printf("%d",x);
}
void (*p) (int) // 定义一个函数指针*/
p = Func; // 将Func函数的首地址赋给指针变量p*/
(*p)(a, b);  // 通过函数指针调用Func函数

什么是typedef函数指针?

typedef char (*p)(int);   

一般typedef的用法是 typedef 源类型 字面类型名 的表达方式,但是在这里的用法不一样
看下面的栗子🌰:

typedef char (*p)(int);   
p pFun;   //定义一个函数指针
char glFun(int a){ return;}   
void main()   
{   
    pFun = glFun;   
    (*pFun)(2);   
} 

和不使用typedef相对比,使用typedef之后定义一个函数指针可以不用输入返回值类型和入口参数个数和类型,看会起来清爽整洁许多

void (*p) (int) // 定义一个函数指针*/
p pFun;  			 //定义一个函数指针

现在不难理解typedef char (*p)(int); 的作用就是可以方便使用p pFun创建一个返回值、入口参数个数和类型已经固定的函数指针

1.2.2、程序分析

disp_manage.c添加函数

/* 用dwColor颜色填充ptRegion指向的区域 */
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(i, j, dwColor);
	}
}

在这里插入图片描述

▲字符居中显示解决方法

disp_manage.c添加函数

/* 字符居中显示 */
void DrawTextInRegionCentral(char *name, PRegion ptRegion, unsigned int dwColor)
{
	FontBitMap tFontBitMap;
	RegionCartesian tRegionCar;

	int iOriginX, iOriginY;
	int i = 0;
	int error;

	/* 计算字符串的外框 */
	GetStringRegionCar(name, &tRegionCar);

	/* 算出第一个字符的origin */
	iOriginX = ptRegion->iLeftUpX + (ptRegion->iWidth - tRegionCar.iWidth)/2 - tRegionCar.iLeftUpX;
	iOriginY = ptRegion->iLeftUpY + (ptRegion->iHeigh - tRegionCar.iHeigh)/2 + tRegionCar.iLeftUpY;


	/* 逐个绘制 */
	while (name[i])
	{
		/* get bitmap */
		tFontBitMap.iCurOriginX = iOriginX;
		tFontBitMap.iCurOriginY = iOriginY;
		error = GetFontBitMap(name[i], &tFontBitMap);
		if (error)
		{
			printf("SelectAndInitFont err\n");
			return;
		}

		/* draw on buffer */		
		DrawFontBitMap(&tFontBitMap, dwColor);		

		iOriginX = tFontBitMap.iNextOriginX;
		iOriginY = tFontBitMap.iNextOriginY;	
		i++;
	}
	
}

button.c:创建button设备,编写默认绘制按钮函数和默认按下反馈函数

#include <ui.h>

/* 默认绘制按钮函数 */
static int DefaultOnDraw(struct Button *ptButton, PDispBuff ptDispBuff)
{
	/* 绘制底色 */
	DrawRegion(&ptButton->tRegion, BUTTON_DEFAULT_COLOR);

	/* 居中写文字 */
	SetFontSize(ptButton->iFontSize);
	DrawTextInRegionCentral(ptButton->name, &ptButton->tRegion, BUTTON_TEXT_COLOR);

	/* flush to lcd/web */
	FlushDisplayRegion(&ptButton->tRegion, ptDispBuff);

	return 0;
}

/* 默认按下反馈函数 按下改变按键按键区域的颜色 */
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);

	/* flush to lcd/web */
	FlushDisplayRegion(&ptButton->tRegion, ptDispBuff);
	return 0;
}

/* 创建并初始化一个Button设备 */
void InitButton(PButton ptButton, char *name, PRegion ptRegion, ONDRAW_FUNC OnDraw, ONPRESSED_FUNC OnPressed)
{
	ptButton->status = 0;
	ptButton->name = name;
	if (ptRegion)
		ptButton->tRegion = *ptRegion;
	
	/* 如果OnDraw函数存在使用OnDraw函数,否则使用DefaultOnDraw函数 */
	ptButton->OnDraw    = OnDraw ? OnDraw : DefaultOnDraw;
	ptButton->OnPressed = OnPressed ? OnPressed : DefaultOnPressed;
}

1.3、单元测试

ui_test.c:2秒切换一次点按状态

#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>

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();

	SelectDefaultDisplay("fb");

	InitDefaultDisplay();

	ptBuffer = GetDisplayBuffer();

	FontsRegister();
	
	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)
	{
		/* 假定2秒切换一次点按状态 */
		tButton.OnPressed(&tButton, ptBuffer, NULL);
		sleep(2);
	}
	
	return 0;	
}

2、页面系统

在这里插入图片描述

▲程序分层

2.1、数据结构抽象

page_manage.h

#ifndef _PAGE_MANAGER_H
#define _PAGE_MANAGER_H

/* 
	页面结构体
	name:页面的名称
	Run:调用以运行页面	//pParams:指向页面参数
	ptNext:指向下一个页面结构体
 */
typedef struct PageAction {
	char *name;
	void (*Run)(void *pParams);
	struct PageAction *ptNext;
}PageAction, *PPageAction;

void PageRegister(PPageAction ptPageAction);
void PageSystemRegister(void);
PPageAction Page(char *name);

#endif

2.2、页面管理器

在这里插入图片描述

▲代码结构

page_manage.c


#include <common.h>
#include <page_manager.h>
#include <string.h>

static PPageAction g_ptPages = NULL;//页面指针指向一个页面结构体

/* 将一个页面注册进页面链表中 */
void PageRegister(PPageAction ptPageAction)
{
	ptPageAction->ptNext = g_ptPages;
	g_ptPages = ptPageAction;
}

/* 在页面链表中寻找 name 页面并回返其地址 */
PPageAction Page(char *name)
{
	PPageAction ptTmp = g_ptPages;

	while (ptTmp)
	{
		if (strcmp(name, ptTmp->name) == 0)
			return ptTmp;
		ptTmp = ptTmp->ptNext;
	}

	return NULL;
}

/* 注册多个页面页面链表中 */
void PageSystemRegister(void)
{
	extern void MainPageRegister(void);
	MainPageRegister();
}

2.3、单元测试

main_page.c

#include <page_manager.h>
#include <stdio.h>

/* 主页面函数 */
static void MainPageRun(void *pParams)
{
	printf("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

/* 创建一个name为main,Run为MainPageRun的页面 */
static PageAction g_tMainPage = {
	.name = "main",
	.Run  = MainPageRun,
};

/* 将g_tMainPage注册进页面链表中 */
void MainPageRegister(void)
{
	PageRegister(&g_tMainPage);
}

page_test.c:在主页面打印当前文件,当前函数,当前行

#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 <page_manager.h>

int main(int argc, char **argv)
{
	PagesRegister();
	Page("main")->Run(NULL);
	return 0;	
}

3、业务系统

3.1、流程及代码框架

在这里插入图片描述

▲业务系统程序流程图

main.c

#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)
{
	int error;

	if (argc != 2)
	{
		printf("Usage: %s <font_file>\n", argv[0]);
		return -1;
	}
	
	/* 初始化显示系统 */		
	DisplaySystemRegister();  /* 以前是: DisplayInit(); */

	SelectDefaultDisplay("fb");

	InitDefaultDisplay();

	/* 初始化输入系统 */		
	InputSystemRegister(); /* 以前是: InputInit(); */
	IntpuDeviceInit();


	/* 初始化文字系统 */		
	FontSystemRegister(); /* 以前是: FontsRegister(); */
	
	error = SelectAndInitFont("freetype", argv[1]);
	if (error)
	{
		printf("SelectAndInitFont err\n");
		return -1;
	}

	/* 初始化页面系统 */		
	PageSystemRegister(); /* 以前是: PagesRegister(); */

	/* 运行业务系统的主页面 */
	Page("main")->Run(NULL);
	
	return 0;	
}

在这里插入图片描述

▲业务系统主页面流程图

main_page.c

...
static void MainPageRun(void *pParams)
{
	int error;
	InputEvent tInputEvent;
	PButton ptButton;
	PDispBuff ptDispBuff = GetDisplayBuffer();
	
	/* 读取配置文件 */
	error = ParseConfigFile();
	if (error)
		return ;

	/* 根据配置文件生成按钮、界面 */
	GenerateButtons();

	while (1)
	{
		/* 读取输入事件 */
		error = GetInputEvent(&tInputEvent);
		if (error)
			continue;

		/* 根据输入事件找到按钮 */
		ptButton = GetButtonByInputEvent(&tInputEvent);
		if (!ptButton)
			continue;

		/* 调用按钮的OnPressed函数 */
		ptButton->OnPressed(ptButton, ptDispBuff, &tInputEvent);
	}
}
...

其中的ParseConfigFile(); GenerateButtons(); GetButtonByInputEvent();函数将在后面编写

3.2、处理配置文件

在这里插入图片描述

▲GUI主界面

  为什么需要配置文件:在配置文件中可以保存一些绘制主界面需要的信息,比如每个button的name以及button是否能被touch和button被touch后需要的一些command等,这样可以增添系统的灵活性

3.2.1、数据结构抽象

config.h

#ifndef _CONFIG_H
#define _CONFIG_H

#include <common.h>

#define ITEMCFG_MAX_NUM 30					//最多按钮数量
#define CFG_FILE  "/etc/test_gui/gui.conf"	//config文件路径

/* 
*	用来保存gui.conf文件中的一行信息及一个button的信息
*	index:索引
*	name:名称
*	bCanBeTouched: BOOL变量,表示button是否可以被touch
*	command: button被touch之后执行的command (选填)
*/
typedef struct ItemCfg {
	int index;
	char name[100];
	int bCanBeTouched;
	char command[100];
}ItemCfg, *PItemCfg;

int ParseConfigFile(void);
int GetItemCfgCount(void);
PItemCfg GetItemCfgByIndex(int index);
PItemCfg GetItemCfgByName(char *name);


#endif

3.2.2、程序分析

config.c:处理cfg文件



#include <config.h>
#include <stdio.h>
#include <string.h>

static ItemCfg g_tItemCfgs[ITEMCFG_MAX_NUM];	//保存按钮的结构体数组
static int g_iItemCfgCount = 0;					//处理cfg文件时使用的计数变量

/* 解析配置文件,成功返回0 */
int ParseConfigFile(void)
{
	FILE *fp;
	char buf[100];
	char *p = buf;
	
	/* 1. open config file */
	fp = fopen(CFG_FILE, "r");
	if (!fp)
	{
		printf("can not open cfg file %s\n", CFG_FILE);
		return -1;
	}

	while (fgets(buf, 100, fp))
	{
		/* 2.1 read each line */
		buf[99] = '\0';		

		/* 2.2 吃掉开头的空格或TAB */
		p = buf;
		while (*p == ' ' || *p =='\t')
			p++;

		/* 2.3 忽略注释 */
		if (*p == '#')
			continue;

		/* 2.4 处理 */
		/* 可以根据g_tItemCfgs.command的第0项是否为'\0'来判断这个button是否有command */
		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;
}

/* 获得文件中内容的行数即button的个数并返回 */
int GetItemCfgCount(void)
{
	return g_iItemCfgCount;
}

/* 根据index寻找并返回g_tItemCfgs地址 */
PItemCfg GetItemCfgByIndex(int index)
{
	if (index < g_iItemCfgCount)
		return &g_tItemCfgs[index];
	else
		return NULL;
}

/* 根据name寻找并返回g_tItemCfgs地址 */
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;
}

3.3、生成界面

在这里插入图片描述

▲计算每个按钮的Region

main_page.c:添加在主界面绘制按钮函数

...
#define X_GAP 5	//按钮之间的x轴方向间隔像素个数
#define Y_GAP 5	//按钮之间的y轴方向间隔像素个数

/* 在主界面绘制按钮 */
static void GenerateButtons(void)
{
	int width, height;		//每个button的宽度和高度
	int n_per_line;			//每行的按钮个数
	int row, rows;			//行索引,行数
	int col;				//列索引
	int n;					//按钮的个数
	PDispBuff pDispBuff;	//显示缓冲区指针,里面包含了屏幕的分辨率信息
	int xres, yres;			//LCD的x,y轴分辨率
	int start_x, start_y;	//按钮绘制区域的起始坐标
	int pre_start_x, pre_start_y;//前一个按钮的起始坐标
	PButton pButton;		//按钮设备指针
	int i = 0;				//按钮个数计数索引
	int iFontSize;
	
	/* 算出单个按钮的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;// +1 防止越界
	width  = xres / n_per_line;
	height = 0.618 * width;	

	/* 居中显示:  计算每个按钮的region  */
	start_x = (xres - width * n_per_line) / 2;
	rows    = n / n_per_line;
	if (rows * n_per_line < n)//如果不能整除
		rows++;
	
	start_y = (yres - rows*height)/2;

	/* 计算每个按钮的region */
	for (row = 0; (row < rows) && (i < n); row++)
	{
		pre_start_y = start_y + row * height;
		pre_start_x = start_x - width;
		for (col = 0; (col < n_per_line) && (i < n); col++)
		{
			pButton = &g_tButtons[i];
			pButton->tRegion.iLeftUpX = pre_start_x + width;
			pButton->tRegion.iLeftUpY = pre_start_y;
			pButton->tRegion.iWidth   = width - X_GAP;
			pButton->tRegion.iHeigh   = height - Y_GAP;
			pre_start_x = pButton->tRegion.iLeftUpX;

			/* InitButton */
			InitButton(pButton, GetItemCfgByIndex(i)->name, NULL, NULL, MainPageOnPressed);
			i++;
		}
	}

	/* 自动获取字符大小 */
	iFontSize = GetFontSizeForAllButton();
	//SetFontSize(iFontSize);

	/* OnDraw */
	for (i = 0; i < n; i++)
	{
		g_tButtons[i].iFontSize = iFontSize;
		g_tButtons[i].OnDraw(&g_tButtons[i], pDispBuff);
	}
}
...

其中GetFontSizeForAllButton();函数将在后面实现

3.4、处理输入事件

  1. 我们得到的输入事件,可能来自触摸屏,也可能是其他APP发来的网络数据
    在这里插入图片描述
▲struct InputEvent
  1. 对于触摸屏事件,根据iX、iY找到按钮
  2. 对于网络数据,我们限定为这样的格式:
      “name ok”、“name err”、“name 70%”
      根据name找到按钮
  3. 对于按钮,我们要提供自己的OnPressed函数,不适用UI系统默认的函数

main_page.c:添加input事件处理相关函数

/* 如果(iX,iY)在ptRegion指向区域返回1,否则返回0 */
static int isTouchPointInRegion(int iX, int iY, PRegion ptRegion)
{
	if (iX < ptRegion->iLeftUpX || iX >= ptRegion->iLeftUpX + ptRegion->iWidth)
		return 0;

	if (iY < ptRegion->iLeftUpY || iY >= ptRegion->iLeftUpY + ptRegion->iHeigh)
		return 0;

	return 1;
}

/* 寻找并返回名称为name的按钮设备地址 */
static PButton GetButtonByName(char *name)
{
	int i;
	
	for (i = 0; i < g_tButtonCnt; i++)
	{
		if (strcmp(name, g_tButtons[i].name) == 0)
			return &g_tButtons[i];
	}

	return NULL;
}

/* 根据ptInputEvent指向的输入事件返回一个按键设备结构体指针 */
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;
	}
	return NULL;
}

main_page.c:添加主界面按下反馈处理函数MainPageOnPressed();

...
/* 主界面按下反馈处理函数 */
static int MainPageOnPressed(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent)
{
	unsigned int dwColor = BUTTON_DEFAULT_COLOR;//设置按钮颜色为默认颜色
	char name[100];								//存放网络事件传入字符串中事件的名称			
	char status[100];							//存放网络事件的状态
	char *strButton;							//存放按钮待显示字符串
	/* 事件状态数组 */
	char *command_status[3] = {"err", "ok", "percent"};
	int command_status_index = 0;				//事件状态数组索引
	char command[1000];							//存放事件对应command
	PItemCfg ptItemCfg;

	strButton = ptButton->name;
	
	/* 1. 对于触摸屏事件 */
	if ((ptInputEvent->iType == INPUT_TYPE_TOUCH) && (ptInputEvent->iPressure))
	{
		/* 1.1 分辨能否被点击 */
		if (GetItemCfgByName(ptButton->name)->bCanBeTouched == 0)
			return -1;

		/* 1.2 修改颜色 */
		ptButton->status = !ptButton->status;
		if (ptButton->status)
		{
			dwColor = BUTTON_PRESSED_COLOR;
			command_status_index = 1;
		}
	}
	else if (ptInputEvent->iType == INPUT_TYPE_NET)
	{
		/* 2. 对于网络事件 */
		
		/* 根据传入的字符串修改颜色 : wifi ok, wifi err, burn 70 */
		sscanf(ptInputEvent->str, "%s %s", name, status);
		if (strcmp(status, "ok") == 0)//ok -> green
		{
			command_status_index = 1;
			dwColor = BUTTON_PRESSED_COLOR;
		}
		else if (strcmp(status, "err") == 0)//err -> red
		{
			command_status_index = 0;
			dwColor = BUTTON_DEFAULT_COLOR;
		}
		else if (status[0] >= '0' && status[0] <= '9')//burn 70 -> blue
		{			
			command_status_index = 2;
			dwColor = BUTTON_PERCENT_COLOR;
			strButton = status;			
		}
		else
			return -1;
	}
	else
	{
		return -1;
	}

	/* 绘制底色 */
	DrawRegion(&ptButton->tRegion, dwColor);

	/* 居中写文字 */
	DrawTextInRegionCentral(strButton, &ptButton->tRegion, BUTTON_TEXT_COLOR);

	/* flush to lcd/web */
	FlushDisplayRegion(&ptButton->tRegion, ptDispBuff);

	/* 执行command */
	ptItemCfg = GetItemCfgByName(ptButton->name);
	if (ptItemCfg->command[0] != '\0')
	{
		sprintf(command, "%s %s", ptItemCfg->command, command_status[command_status_index]);
		system(command);
	}
	
	return 0;
}
...

4、系统改进

4.1、按钮文字

common.h:添加数据结构RegionCartesian用来表示笛卡尔坐标系下的区域信息

/*
*	区域信息 笛卡尔坐标系
*	iLeftUpX iLeftUpY:里面包含了绘制的起始坐标
*	iWidth iHeigh:字符的长宽信息
*/
typedef struct RegionCartesian {
	int iLeftUpX;
	int iLeftUpY;
	int iWidth;
	int iHeigh;
}RegionCartesian, *PRegionCartesian;

freetype.c:添加函数FreetypeGetStringRegionCar();

/* 获得字符串str的笛卡尔区域信息保存在ptRegionCar指向的结构体中 */
static int FreetypeGetStringRegionCar(char *str, PRegionCartesian ptRegionCar)
{
    int i;
    int error;
    FT_BBox bbox;
    FT_BBox glyph_bbox;
    FT_Vector pen;
    FT_Glyph  glyph;
    FT_GlyphSlot slot = g_tFace->glyph;

    /* 初始化 */
    bbox.xMin = bbox.yMin = 32000;
    bbox.xMax = bbox.yMax = -32000;

    /* 指定原点为(0, 0) */
    pen.x = 0;
    pen.y = 0;

    /* 计算每个字符的bounding box */
    /* 先translate, 再load char, 就可以得到它的外框了 */
    for (i = 0; i < strlen(str); i++)
    {
        /* 转换:transformation */
        FT_Set_Transform(g_tFace, 0, &pen);

        /* 加载位图: load glyph image into the slot (erase previous one) */
        error = FT_Load_Char(g_tFace, str[i], FT_LOAD_RENDER);
        if (error)
        {
            printf("FT_Load_Char error\n");
            return -1;
        }

        /* 取出glyph */
        error = FT_Get_Glyph(g_tFace->glyph, &glyph);
        if (error)
        {
            printf("FT_Get_Glyph error!\n");
            return -1;
        }
        
        /* 从glyph得到外框: bbox */
        FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox);

        /* 更新外框 */
        if ( glyph_bbox.xMin < bbox.xMin )
            bbox.xMin = glyph_bbox.xMin;

        if ( glyph_bbox.yMin < bbox.yMin )
            bbox.yMin = glyph_bbox.yMin;

        if ( glyph_bbox.xMax > bbox.xMax )
            bbox.xMax = glyph_bbox.xMax;

        if ( glyph_bbox.yMax > bbox.yMax )
            bbox.yMax = glyph_bbox.yMax;
        
        /* 计算下一个字符的原点: increment pen position */
        pen.x += slot->advance.x;
        pen.y += slot->advance.y;
    }

    /* return string bbox */
    //*abbox = bbox;
    ptRegionCar->iLeftUpX = bbox.xMin;
    ptRegionCar->iLeftUpY = bbox.yMax;
    ptRegionCar->iWidth     = bbox.xMax - bbox.xMin + 1;
    ptRegionCar->iHeigh     = bbox.yMax - bbox.yMin + 1;

	return 0;	
}

font_manage.c:添加函数GetStringRegionCar();

/* 上层可以得到字符串str的笛卡尔坐标系下的区域信息存放在ptRegionCar指向的结构体 */
int GetStringRegionCar(char *str, PRegionCartesian ptRegionCar)
{
	return g_ptDefaulFontOpr->GetStringRegionCar(str, ptRegionCar);
}

main_page.c:添加函数GetFontSizeForAllButton();

/* 找出name最长的Button,算出合适的font size */
static int GetFontSizeForAllButton(void)
{
	int i;
	int max_len = -1;
	int max_index = 0;
	int len;
	RegionCartesian tRegionCar;
	float k, kx, ky;
	
	/* 1. 找出name最长的Button */
	for (i = 0; i < g_tButtonCnt; i++)
	{
		len = strlen(g_tButtons[i].name);
		if (len > max_len)
		{
			max_len = len;
			max_index = i;
		}
	}

	/* 2. 以font_size =100, 算出它的外框 */
	SetFontSize(100);
	GetStringRegionCar(g_tButtons[max_index].name, &tRegionCar);

	/* 3. 把文字的外框缩放为Button的外框 */
	kx = (float)g_tButtons[max_index].tRegion.iWidth / tRegionCar.iWidth;
	ky = (float)g_tButtons[max_index].tRegion.iHeigh / tRegionCar.iHeigh;
	//printf("button width / str width   = %d/%d = %f\n", g_tButtons[max_index].tRegion.iWidth, tRegionCar.iWidth, kx);
	//printf("button height / str height = %d/%d = %f\n", g_tButtons[max_index].tRegion.iHeigh, tRegionCar.iHeigh, ky);
	if (kx < ky)
		k = kx;
	else
		k = ky;

	//printf("font size = %d\n", (int)(k*100));
	/* 4. 反算出font size, 只取0.80, 避免文字过于接近边界 */
	return k * 100 * 0.8;
}

在这里插入图片描述

▲字符居中显示方案

disp_manage.c:修改DrawTextInRegionCentral();函数

/* 字符居中显示 */
void DrawTextInRegionCentral(char *name, PRegion ptRegion, unsigned int dwColor)
{
	FontBitMap tFontBitMap;
	RegionCartesian tRegionCar;

	int iOriginX, iOriginY;
	int i = 0;
	int error;

	/* 计算字符串的外框 */
	GetStringRegionCar(name, &tRegionCar);

	/* 算出第一个字符的origin */
	iOriginX = ptRegion->iLeftUpX + (ptRegion->iWidth - tRegionCar.iWidth)/2 - tRegionCar.iLeftUpX;
	iOriginY = ptRegion->iLeftUpY + (ptRegion->iHeigh - tRegionCar.iHeigh)/2 + tRegionCar.iLeftUpY;


	/* 逐个绘制 */
	while (name[i])
	{
		/* get bitmap */
		tFontBitMap.iCurOriginX = iOriginX;
		tFontBitMap.iCurOriginY = iOriginY;
		error = GetFontBitMap(name[i], &tFontBitMap);
		if (error)
		{
			printf("SelectAndInitFont err\n");
			return;
		}

		/* draw on buffer */		
		DrawFontBitMap(&tFontBitMap, dwColor);		

		iOriginX = tFontBitMap.iNextOriginX;
		iOriginY = tFontBitMap.iNextOriginY;	
		i++;
	}
	
}

4.2、支持配置文件的command

最重要的一句system(command);
这句函数可以让设备在console上执行command字符串中包含的命令

led.sh

#!/bin/sh
status=$1                   #status=argv[1]
if [ "$status" = "ok" ]
then
echo "led has been tested, it is ok"
fi

if [ "$status" = "err" ]
then
echo "led has been tested, it is fail"
fi

参考资料

通俗易懂详解typedef函数指针

Logo

更多推荐