前言
前面我们完成了量产工具的显示、输入和文字系统,如下:
量产工具(一)——显示系统
量产工具(二)——输入系统
量产工具(三)——文字系统
项目框架
本节我们来实现量产工具的第四个子系统——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);//用软件模拟按键按下,周期为2swhile (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系统就完成了,下节我会更新量产工具的第五篇——页面系统,距离完成这个项目越来越近了,希望大家多多点赞支持。