Linux应用项目之量产工具(四)——UI系统
前言
前面我们完成了量产工具的显示、输入和文字系统,如下:
量产工具(一)——显示系统
量产工具(二)——输入系统
量产工具(三)——文字系统
项目框架
本节我们来实现量产工具的第四个子系统——UI系统。
数据结构抽象
所谓 UI ,就是 User Interface(用户界面),有图像界面(GUI)等;我们的UI系统,就是构造各类GUI元素,比如按钮(目前只实现按钮)
怎么描述一个按钮呢?它的位置、大小怎么表示?怎么绘制它?用户点击它之后,如何处理?
根据上面这些参数需求,我们可以构造如下结构体:
ui.h
#ifndef _UI_H
#define _UI_H
#include <common.h>//通用头文件,定义了Region结构体
#include <disp_manager.h>
#include <input_manager.h>
#define BUTTON_DEFAULT_COLOR 0xff0000 //默认的按键颜色为红色
#define BUTTON_PRESSED_COLOR 0x00ff00 //按键按下后的颜色为绿色
#define BUTTON_TEXT_COLOR 0x000000 //文本的颜色为黑色
typedef struct Button Button;
typedef struct Button * PButton;
/*用typedef将函数指针更名为第一个括号里名字,更加简洁*/
typedef int (*ONDRAW_FUNC)(PButton ptButton, PDispBuff ptDispBuff);
typedef int (*ONPRESSED_FUNC)(PButton 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
编写按钮的代码
点击按钮后怎么处理,是业务系统的事情(顶层APP),所以应该提供一个 InitButton 函数,让用户可以提供 OnPressed 等底层函数,上层代码通过下面 3 个函数使用按钮。
button.c
#include <ui.h>
/*默认的绘图函数*/
static int DefaultOnDraw(PButton 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;
}
/*默认的按键状态变换时的处理函数*/
static int DefaultOnPressed(PButton 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;
}
/*上层代码调用Init函数来进行初始化操作*/
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;
}
在 button.c 里,DrawRegion 函数和 DrawTextInRegionCentral 函数我们是在 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;
// 计算文本在区域内的起始X坐标,确保文本居中
iOriginX = (ptRegion->iWidth - n * iFontSize)/2 + ptRegion->iLeftUpX;
// 计算文本在区域内的起始Y坐标,确保文本居中
iOriginY = (ptRegion->iHeigh - iFontSize)/2 + iFontSize + ptRegion->iLeftUpY;
// 设置字体大小
SetFontSize(iFontSize);
// 遍历文本字符串中的每个字符
while (name[i])
{
// 获取当前字符的字体位图
tFontBitMap.iCurOriginX = iOriginX;
tFontBitMap.iCurOriginY = iOriginY;
error = GetFontBitMap(name[i], &tFontBitMap);
if (error)
{
printf("SelectAndInitFont err\n");
return;
}
// 在缓冲区上绘制当前字符的字体位图
DrawFontBitMap(&tFontBitMap, dwColor);
// 更新下一个字符的起始坐标
iOriginX = tFontBitMap.iNextOriginX;
iOriginY = tFontBitMap.iNextOriginY;
i++;
}
}
单元测试
编写单元测试代码 ui_test.c
ui_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 <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();
// 选择默认的显示操作接口,这里假设"fb"是一个帧缓冲设备
SelectDefaultDisplay("fb");
// 初始化默认的显示操作接口
InitDefaultDisplay();
// 获取显示缓冲区
ptBuffer = GetDisplayBuffer();
// 注册字体
FontsRegister();
// 选择并初始化字体,这里使用的是"freetype"字体,字体大小由命令行参数指定
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);
//用软件模拟按键按下,周期为2s
while (1)
{
tButton.OnPressed(&tButton, ptBuffer, NULL);
// 暂停2秒
sleep(2);
}
// 程序正常结束
return 0;
}
unittest下的Makefile
文件结构如下:
EXTRA_CFLAGS :=
CFLAGS_file.o :=
#obj-y += disp_test.o
#obj-y += input_test.o
#obj-y += font_test.o
obj-y += ui_test.o
最后一行表示要编译 ui_test.c
ui目录下的Makefile
EXTRA_CFLAGS :=
CFLAGS_file.o :=
obj-y += button.o
编译 button.c
顶层目录下的Makefile
# 定义交叉编译工具链的前缀,这里留空,意味着使用系统默认的编译器
CROSS_COMPILE ?=
# 定义汇编器、链接器、C编译器、预处理器、归档器和符号表工具
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
# 定义二进制文件处理工具
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
# 导出定义的编译工具
export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP
# 定义C编译器的标志
CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include # 添加头文件搜索路径
# 定义链接器的标志
LDFLAGS := -lts -lpthread -lfreetype # 链接时需要的库
# 导出编译和链接标志
export CFLAGS LDFLAGS
# 定义项目顶层目录
TOPDIR := $(shell pwd)
export TOPDIR
# 定义目标程序名
TARGET := test
# 定义源代码目录结构,每个目录下的文件将被编译成对应的.o文件
obj-y += display/
obj-y += input/
obj-y += font/
obj-y += ui/
obj-y += unittest/
# 默认目标,编译所有内容
all : start_recursive_build $(TARGET)
@echo $(TARGET) has been built!
# 开始递归构建,使用当前目录下的Makefile.build文件
start_recursive_build:
make -C ./ -f $(TOPDIR)/Makefile.build
# 定义目标程序的构建规则
$(TARGET) : built-in.o
$(CC) -o $(TARGET) built-in.o $(LDFLAGS)
# 清理目标,删除所有生成的.o文件和目标程序
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
# 深度清理目标,删除所有生成的.o文件、依赖文件和目标程序
distclean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(TARGET)
加粗部分表示要进入这些目录下编译 .c 文件
运行结果
板子挂载 Ubuntu 的 NFS 目录,编译运行:
运行结果如下 :
至此UI系统就完成了,下节我会更新量产工具的第五篇——页面系统,距离完成这个项目越来越近了,希望大家多多点赞支持。