当前位置: 首页 > 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

相关文章:

嵌入式应用实例→电子产品量产工具→UI界面的绘制和测试

前言 之前已经在博文https://blog.csdn.net/wenhao_ir/article/details/144747714中实现了用Freetype在LCD屏上绘制字符&#xff0c;本篇博文我们利用Freetype实现UI界面的绘制。 头文件include\ui.h的分析 头文件内的代码 #ifndef _UI_H #define _UI_H#include <common…...

如何删除 Docker 中的悬虚镜像?

在 Docker 中&#xff0c;悬虚镜像&#xff08;Dangling Images&#xff09;是指那些没有 标签 且没有被任何容器使用的镜像。这些镜像通常是由于构建过程中生成的中间层镜像或未正确清理的镜像残留。删除悬虚镜像可以释放磁盘空间并保持 Docker 环境的整洁。 1. 列出悬虚镜像…...

el-table树形懒加载展开改为点击行展开

思路&#xff1a;获取el-table中小箭头&#xff0c;然后调它的click事件&#xff01; <el-tablerow-click"getOpenDetail":row-class-name"tableRowClassName">// 点击当前行展开节点getOpenDetail(row, column, event) {// 如果是叶子节点或点击的是…...

【Ubuntu】Ubuntu server 18.04 搭建Slurm并行计算环境(包含NFS)

Ubuntu server 18.04 搭建Slurm并行计算环境&#xff08;包含NFS&#xff09; 一、Munge 认证模块 1.1、安装 munge 主节点和子节点都安装munge #安装 sudo apt update && sudo apt install munge libmunge-dev#设置开机启动 sudo systemctl enable munge sudo syste…...

高并发场景下的秒杀系统架构设计与实现

引言 秒杀系统是一种高并发场景的典型应用&#xff0c;广泛存在于电商平台、抢票系统和促销活动中。秒杀活动的特点是短时间内吸引大量用户同时访问并尝试抢购商品&#xff0c;这对系统的高并发处理能力、稳定性和用户体验提出了极高的要求。 在秒杀系统中&#xff0c;常见的…...

搭建开源版Ceph分布式存储

系统&#xff1a;Rocky8.6 三台2H4G 三块10G的硬盘的虚拟机 node1 192.168.2.101 node2 192.168.2.102 node3 192.168.2.103 三台虚拟机环境准备 1、配置主机名和IP的映射关系 2、关闭selinux和firewalld防火墙 3、配置时间同步且所有节点chronyd服务开机自启 1、配置主机名和…...

QT----------多媒体

实现思路 多媒体模块功能概述&#xff1a; QT 的多媒体模块提供了丰富的功能&#xff0c;包括音频播放、录制、视频播放和摄像头操作等。 播放音频&#xff1a; 使用 QMediaPlayer 播放完整的音频文件。使用 QSoundEffect 播放简短的音效文件。 录制音频&#xff1a; 使用 QMe…...

选择器(结构伪类选择器,伪元素选择器),PxCook软件,盒子模型

结构为类选择器 伪元素选择器 PxCook 盒子模型 (内外边距&#xff0c;边框&#xff09; 内外边距合并&#xff0c;塌陷问题 元素溢出 圆角 阴影: 模糊半径&#xff1a;越大越模糊&#xff0c;也就是越柔和 案例一&#xff1a;产品卡片 <!DOCTYPE html> <html lang&q…...

Vue2/Vue3 响应式原理对比指南

Vue2/Vue3 响应式原理对比指南 1. 基本实现原理 1.1 Vue2 响应式实现 (Object.defineProperty) // Vue2 响应式核心实现 function defineReactive(obj, key, val) {// 递归处理嵌套对象observe(val);const dep new Dep();Object.defineProperty(obj, key, {get() {// 依赖收…...

FastExcel:超越EasyExcel的新一代Excel处理工具

简介 FastExcel是由原EasyExcel作者在阿里巴巴宣布停止维护EasyExcel之后推出的升级版框架。它继承了EasyExcel的所有优点&#xff0c;并且在性能和功能上进行了显著的提升和创新。 FastExcel的特点 高性能读写&#xff1a;FastExcel专注于性能优化&#xff0c;能够高效处理…...

大模型系列17-RAGFlow搭建本地知识库

大模型系列17-RAGFlow搭建本地知识库 安装ollama安装open-wehui安装并运行ragflowRAG&#xff08;检索、增强、生成&#xff09;RAG是什么RAG三过程RAG问答系统构建步骤向量库构建检索模块生成模块 RAG解决LLM的痛点 使用ragflow访问ragflow配置ollama模型添加Embedding模型添加…...

常用的mac软件下载地址

目录 iRightMouse Pro&#xff08;超级右键&#xff09; xmind&#xff08;思维导图&#xff09; Parallels Desktop&#xff08;虚拟机工具&#xff09; Paste&#xff08;跨平台复制粘贴&#xff09; AutoSwitchInput Pro&#xff08;自动切换输入法&#xff09; Snipa…...

基于51单片机和16X16LED点阵屏(74HC138和74HC595驱动)的小游戏《贪吃蛇》

目录 系列文章目录前言一、效果展示二、原理分析三、各模块代码1、定时器02、自制八位独立按键3、点阵屏模块 四、主函数总结 系列文章目录 前言 《贪吃蛇》&#xff0c;一款经典的、怀旧的小游戏&#xff0c;单片机入门必写程序。 以《贪吃蛇》为载体&#xff0c;熟悉各种屏…...

python中常用的内置函数介绍

python中常用的内置函数介绍 1. print()2. len()3. type()4. str(), int(), float()5. list(), tuple(), set(), dict()6. range()7. sum()8. max(), min()9. sorted()10. zip()11. enumerate()12. map()13. filter()14. any(), all()15. abs()16. pow()17. round()18. ord(), …...

【微服务】Spring Cloud Config解决的问题和案例

文章目录 强烈推荐引言解决问题1. 配置管理的集中化2. 配置的版本控制3. 环境特定配置4. 配置的动态刷新5. 安全管理敏感数据6. 配置的一致性 组件1. **配置服务器&#xff08;Config Server&#xff09;**2. **配置客户端&#xff08;Config Client&#xff09;** 配置示例配置…...

华为OD机试E卷 --最小的调整次数--24年OD统一考试(Java JS Python C C++)

文章目录 题目描述输入描述输出描述用例题目解析JS算法源码Java算法源码python算法源码c算法源码c++算法源码题目描述 有一个特异性的双端队列一,该队列可以从头部或尾部添加数据,但是只能从头部移出数据。 小A依次执行2n个指令往队列中添加数据和移出数据。其中n个指令是添…...

Oracle Dataguard(主库为 Oracle 11g 单节点)配置详解(2):配置主数据库

Oracle Dataguard&#xff08;主库为 Oracle 11g 单节点&#xff09;配置详解&#xff08;2&#xff09;&#xff1a;配置主数据库 目录 Oracle Dataguard&#xff08;主库为 Oracle 11g 单节点&#xff09;配置详解&#xff08;2&#xff09;&#xff1a;配置主数据库一、配置…...

慧集通iPaaS集成平台低代码训练-实践篇

练习使用帐号信息&#xff1a; 1.致远A8平台&#xff08;请自行准备测试环境&#xff09; 慧集通连接器配置相关信息 访问地址&#xff1a; rest账号&#xff1a;rest rest密码&#xff1a; OA账号&#xff1a; 2.云星空&#xff08;请自行准备测试环境&#xff09; 连接…...

TDengine 如何进行高效数据建模

1.背景 数据建模对于数据库建立后整体高效运行非常关键&#xff0c;不同建模方式&#xff0c;可能会产生相差几倍的性能差别 2. 建库 建模在建库阶段应考虑几下几点&#xff1a; 建多少库 根据业务情况确定建库个数&#xff0c;TDengine 不支持跨库查询&#xff0c;如果业…...

HarmonyOS NEXT应用开发实战:一分钟写一个网络接口,JsonFormat插件推荐

在开发鸿蒙操作系统应用时&#xff0c;网络接口的实现往往是一个繁琐且重复的过程。为了提高开发效率&#xff0c;坚果派(nutpi.net)特别推出了一个非常实用的插件——JsonFormat。这款插件的主要功能是将JSON格式的数据直接转换为arkts的结构定义&#xff0c;让我们在编写接口…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合

在汽车智能化的汹涌浪潮中&#xff0c;车辆不再仅仅是传统的交通工具&#xff0c;而是逐步演变为高度智能的移动终端。这一转变的核心支撑&#xff0c;来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒&#xff08;T-Box&#xff09;方案&#xff1a;NXP S32K146 与…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

C# 表达式和运算符(求值顺序)

求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如&#xff0c;已知表达式3*52&#xff0c;依照子表达式的求值顺序&#xff0c;有两种可能的结果&#xff0c;如图9-3所示。 如果乘法先执行&#xff0c;结果是17。如果5…...

GraphQL 实战篇:Apollo Client 配置与缓存

GraphQL 实战篇&#xff1a;Apollo Client 配置与缓存 上一篇&#xff1a;GraphQL 入门篇&#xff1a;基础查询语法 依旧和上一篇的笔记一样&#xff0c;主实操&#xff0c;没啥过多的细节讲解&#xff0c;代码具体在&#xff1a; https://github.com/GoldenaArcher/graphql…...