嵌入式应用实例→电子产品量产工具→UI界面的绘制和测试
前言
之前已经在博文https://blog.csdn.net/wenhao_ir/article/details/144747714中实现了用Freetype在LCD屏上绘制字符,本篇博文我们利用Freetype实现UI界面的绘制。
头文件include\ui.h
的分析
头文件内的代码
#ifndef _UI_H
#define _UI_H
#include <common.h>
#include <disp_manager.h>
#include <input_manager.h>
#define BUTTON_DEFAULT_COLOR 0xff0000
#define BUTTON_PRESSED_COLOR 0x00ff00
#define BUTTON_TEXT_COLOR 0x000000
struct Button;
typedef int (*ONDRAW_FUNC)(struct Button *ptButton, PDispBuff ptDispBuff);
typedef int (*ONPRESSED_FUNC)(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent);
typedef struct Button {
char *name;
int status;
Region tRegion;
ONDRAW_FUNC OnDraw;
ONPRESSED_FUNC OnPressed;
}Button, *PButton;
void InitButton(PButton ptButton, char *name, PRegion ptRegion, ONDRAW_FUNC OnDraw, ONPRESSED_FUNC OnPressed);
#endif
代码struct Button;
在 C 语言中,struct Button;
是一种 前向声明,其作用是告诉编译器“存在一个名为 struct Button
的结构体,但我现在不打算定义它的具体内容”。具体用途如下:
为什么使用 struct Button;
?
-
为指针定义类型
前向声明允许你在结构体定义之前声明指向该结构体的指针类型。这在需要定义互相引用的结构体或函数时很有用。
例如:struct Button; // 前向声明 typedef struct Button *PButton; // 定义指向该结构体的指针类型
-
避免完整定义的开销
如果某些地方只需要使用struct Button
的指针而不需要了解其完整内容,前向声明可以减少编译依赖,从而加快编译速度。
为什么这里需要 struct Button;
?
在后成的代码中:
typedef int (*ONDRAW_FUNC)(struct Button *ptButton, PDispBuff ptDispBuff);
typedef int (*ONPRESSED_FUNC)(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent);
用到了结构体 struct Button
。
代码typedef int (*ONDRAW_FUNC)(struct Button *ptButton, PDispBuff ptDispBuff);
这段代码是 C 语言中 函数指针类型 的定义。我们逐步分析:
定义拆解
typedef int (*ONDRAW_FUNC)(struct Button *ptButton, PDispBuff ptDispBuff);
-
typedef
typedef
用于为一个已存在的类型定义一个新的类型别名。- 在这里,它为一个特定类型的函数指针定义了别名
ONDRAW_FUNC
。
-
函数指针
(*ONDRAW_FUNC)
定义了一个函数指针,表示ONDRAW_FUNC
是指向某种函数的指针。- 这个函数的原型是:
也就是说,它接收两个参数,返回一个int Function(struct Button *ptButton, PDispBuff ptDispBuff);
int
类型的值。
-
参数类型
struct Button *ptButton
指向一个struct Button
类型的指针。通过它,函数可以操作一个Button
对象,Button
对象的定义在后面。PDispBuff ptDispBuff
假设PDispBuff
是一个类型别名(可能定义为typedef DispBuff *PDispBuff
),表示DispBuff
类型的指针。这是FrameBuffer的显示缓冲区的指针。
-
返回类型
int
表示函数执行的结果是一个整数,通常用来表示状态码(如 0 表示成功,非 0 表示失败)。
使用方式
-
声明函数指针变量
ONDRAW_FUNC myDrawFunc;
这里
myDrawFunc
是一个指向函数的指针,它的函数原型符合ONDRAW_FUNC
定义。 -
定义符合原型的函数
int MyButtonDraw(struct Button *ptButton, PDispBuff ptDispBuff) { // 绘制按钮的逻辑 return 0; // 成功 }
-
赋值并调用
myDrawFunc = MyButtonDraw; // 将函数指针指向具体实现 myDrawFunc(ptButton, ptDispBuff); // 通过指针调用函数
具体用途
ONDRAW_FUNC
的设计通常用于 回调机制,允许将函数指针作为参数传递,或在结构体中保存,提供灵活的扩展能力。在你的代码中,ONDRAW_FUNC
是一个绘制按钮的回调函数,它的用途包括:
- 在按钮需要绘制时,调用该函数实现具体的绘制逻辑。
- 提供不同的绘制方法(比如改变样式或颜色),而无需修改其他代码。
代码typedef int (*ONPRESSED_FUNC)(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent);
这里面涉及到的将函数指针定义为一个类型的语法知识这里不再赘述,只说下几个参数的意义。
ptButton:这是一个Button结构体类似的指针,Button的定义见后面
ptDispBuff:这是FrameBuffer的显示缓冲区的指针。
ptInputEvent:这里面存储着来自触摸屏的输入数据:
typedef struct InputEvent {
struct timeval tTime;
int iType;
int iX;
int iY;
int iPressure;
char str[1024];
}InputEvent, *PInputEvent;
结构体Button
typedef struct Button {
char *name;
int status;
Region tRegion;
ONDRAW_FUNC OnDraw;
ONPRESSED_FUNC OnPressed;
}Button, *PButton;
这个结构体就代表屏幕上的一个一个按钮(下图中,一个框就是一个按钮):
name代表一个按钮的名字;
status代表按钮的状态;
tRegion代表按钮的显示区域;
函数指针OnDraw用于区域的绘制;
函数指针OnPressed用于对点击按钮的处理。
文件ui\button.c
的分析
按钮初始化函数InitButton()
void InitButton(PButton ptButton, char *name, PRegion ptRegion, ONDRAW_FUNC OnDraw, ONPRESSED_FUNC OnPressed)
{
ptButton->status = 0;
ptButton->name = name;
ptButton->tRegion = *ptRegion;
ptButton->OnDraw = OnDraw ? OnDraw : DefaultOnDraw;
ptButton->OnPressed = OnPressed ? OnPressed : DefaultOnPressed;
}
这个没啥好说的,就是对结构体PButton的实例ptButton进行实始化赋值处理。
绘制按钮和文字的函数DefaultOnDraw()
static int DefaultOnDraw(struct Button *ptButton, PDispBuff ptDispBuff)
{
/* 绘制底色 */
DrawRegion(&ptButton->tRegion, BUTTON_DEFAULT_COLOR);
/* 居中写文字 */
DrawTextInRegionCentral(ptButton->name, &ptButton->tRegion, BUTTON_TEXT_COLOR);
/* flush to lcd/web */
FlushDisplayRegion(&ptButton->tRegion, ptDispBuff);
return 0;
}
这个没啥好讲的,注释已经写得很清楚了,需要注意的就是函数FlushDisplayRegion()对于咱们这进而的LCD屏其实是没必要的,因为咱们这里的LCD屏,其存储区的值变了,对应的屏幕上的颜色也就变了。
点击处理函数DefaultOnPressed()
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;
}
文件display\disp_manager.c
分析
以某种颜色填充区域的函数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(i, j, dwColor);
}
}
这个函数用来把某个矩形区域绘制成一种颜色,实际上就是以某种颜色填充某块矩形区域。
在区域中居中书写文字的函数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;
iOriginX = (ptRegion->iWidth - n * iFontSize)/2 + ptRegion->iLeftUpX;
iOriginY = (ptRegion->iHeigh - iFontSize)/2 + iFontSize + ptRegion->iLeftUpY;
SetFontSize(iFontSize);
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++;
}
}
这个没啥好说的,关键是确定字符串的起始位置,不过这个的算法也不难。
测试单元main函数分析
int main(int argc, char **argv)
{
PDispBuff ptBuffer;
int error;
Button tButton;
Region tRegion;
if (argc != 2)
{
printf("Usage: %s <font_file>\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, "UI_test", &tRegion, NULL, NULL);
tButton.OnDraw(&tButton, ptBuffer);
while (1)
{
tButton.OnPressed(&tButton, ptBuffer, NULL);
sleep(2);
}
return 0;
}
这个流程就很简单了,首先对FramBuffer设备(LCD设备)进行初始化,然后对Freetype库进行初始化,接着就可以在LCD屏上进行UI界面的绘制了。由于我们只是测试UI界面的绘制,还没有进行UI界面的交互功能的开发,所以这里就没有进行tslib库的设置和初始化。
交叉编译
首先把freetype的文件复制到工程的include文件中,然后记着把Makefile文件修改好。
接着代码复制到Ubuntu中。
make
把生成的可执行文件test重命名为:UI_test,并在NFS文件中准备好下面三个文件备用:
板上测试
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
cd /mnt/UI_test
先把屏幕刷黑:
./draw_lcd_black
然后运行咱们这里的测试程序(由于程序是处于while循环中,所以这里让它后台运行):
./UI_test ./simsun.ttc &
就得到了我们想要的结果(每隔2秒重新绘制一次,绿色和红色交替进行):
附完整源代码
https://pan.baidu.com/s/1-HGuKQj4lpFn6xPagaBSEA?pwd=n19t