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

嵌入式应用实例→电子产品量产工具→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    0x000000struct 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.lryc.cn/news/514561.html

相关文章:

  • 如何删除 Docker 中的悬虚镜像?
  • el-table树形懒加载展开改为点击行展开
  • 【Ubuntu】Ubuntu server 18.04 搭建Slurm并行计算环境(包含NFS)
  • 高并发场景下的秒杀系统架构设计与实现
  • 搭建开源版Ceph分布式存储
  • QT----------多媒体
  • 选择器(结构伪类选择器,伪元素选择器),PxCook软件,盒子模型
  • Vue2/Vue3 响应式原理对比指南
  • FastExcel:超越EasyExcel的新一代Excel处理工具
  • 大模型系列17-RAGFlow搭建本地知识库
  • 常用的mac软件下载地址
  • 基于51单片机和16X16LED点阵屏(74HC138和74HC595驱动)的小游戏《贪吃蛇》
  • python中常用的内置函数介绍
  • 【微服务】Spring Cloud Config解决的问题和案例
  • 华为OD机试E卷 --最小的调整次数--24年OD统一考试(Java JS Python C C++)
  • Oracle Dataguard(主库为 Oracle 11g 单节点)配置详解(2):配置主数据库
  • 慧集通iPaaS集成平台低代码训练-实践篇
  • TDengine 如何进行高效数据建模
  • HarmonyOS NEXT应用开发实战:一分钟写一个网络接口,JsonFormat插件推荐
  • 基于动力学的MPC控制器设计盲点解析
  • Java重要面试名词整理(十六):SpringBoot
  • 在K8S中,如何部署kubesphere?
  • 算法-查找缺失的数字
  • antd-vue - - - - - a-date-picker限制选择范围
  • 计算机网络练习题
  • redis的集群模式与ELK基础
  • STM32-笔记18-呼吸灯
  • Vue3 + ElementPlus动态合并数据相同的单元格(超级详细版)
  • 【JavaWeb后端学习笔记】MySQL的数据控制语言(Data Control Language,DCL)
  • libvirt学习