当前位置: 首页 > article >正文

嵌入式应用实例→电子产品量产工具→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;

  1. 为指针定义类型
    前向声明允许你在结构体定义之前声明指向该结构体的指针类型。这在需要定义互相引用的结构体或函数时很有用。
    例如:

    struct Button; // 前向声明
    typedef struct Button *PButton; // 定义指向该结构体的指针类型
    
  2. 避免完整定义的开销
    如果某些地方只需要使用 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);
  1. typedef

    • typedef 用于为一个已存在的类型定义一个新的类型别名。
    • 在这里,它为一个特定类型的函数指针定义了别名 ONDRAW_FUNC
  2. 函数指针

    • (*ONDRAW_FUNC) 定义了一个函数指针,表示 ONDRAW_FUNC 是指向某种函数的指针。
    • 这个函数的原型是:
      int Function(struct Button *ptButton, PDispBuff ptDispBuff);
      
      也就是说,它接收两个参数,返回一个 int 类型的值。
  3. 参数类型

    • struct Button *ptButton
      指向一个 struct Button 类型的指针。通过它,函数可以操作一个 Button 对象, Button 对象的定义在后面。
    • PDispBuff ptDispBuff
      假设 PDispBuff 是一个类型别名(可能定义为 typedef DispBuff *PDispBuff),表示 DispBuff 类型的指针。这是FrameBuffer的显示缓冲区的指针。
  4. 返回类型

    • int
      表示函数执行的结果是一个整数,通常用来表示状态码(如 0 表示成功,非 0 表示失败)。

使用方式

  1. 声明函数指针变量

    ONDRAW_FUNC myDrawFunc;
    

    这里 myDrawFunc 是一个指向函数的指针,它的函数原型符合 ONDRAW_FUNC 定义。

  2. 定义符合原型的函数

    int MyButtonDraw(struct Button *ptButton, PDispBuff ptDispBuff) {
        // 绘制按钮的逻辑
        return 0; // 成功
    }
    
  3. 赋值并调用

    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


http://www.kler.cn/a/458956.html

相关文章:

  • unity学习5:创建一个自己的3D项目
  • 电脑找不到mfc110.dll文件要如何解决?Windows缺失mfc110.dll文件快速解决方法
  • Servlet解析
  • el-table 实现纵向多级表头
  • 计算机网络复习(练习题)
  • linux文件类型和根目录结构
  • 走方格(蓝桥杯2020年试题H)
  • TDengine 新功能 VARBINARY 数据类型
  • VScode 只能运行c,运行不了c++的解决问题
  • HTML——21. 文件下载
  • 什么是出海投资安全评估报告?如何写出海投资安全评估报告?
  • 基于 InternLM 和 LangChain 搭建你的知识库
  • YUM与开源项目(Web运维)
  • 微服务SpringCloud分布式事务之Seata
  • 基于Pytorch和yolov8n手搓安全帽目标检测的全过程
  • 闭包的理解
  • 协程原理 函数栈 有栈协程
  • SpringCloudAlibaba 技术栈—Sentinel
  • union的实际使用
  • html+css网页设计 美食 美食4个页面
  • HTML——13.超链接
  • 纯血鸿蒙ArkUI选项卡布局详解
  • 【Spring Boot 实现 PDF 导出】
  • win10、win11-鼠标右键还原、暂停更新
  • Docker运行hello-world镜像失败或超时:Unable to find image ‘hello-world:latest‘ locally Trying to pull reposi
  • Hbase的特点、特性