前言
韦东山项目实战之七步从零编写带GUI的应用(项目开发|论文参考|C|GUI)学习笔记 文章中大多内容来自韦东山老师的文档,还有部分个人根据自己需求补充的内容
视频教程地址: https://www.bilibili.com/video/BV1it4y1Q75z
1、UI系统
▲程序分层
1.1、数据结构抽象
ui.h
#ifndef _UI_H
#define _UI_H
#include <common.h>
#include <disp_manager.h>
#include <input_manager.h>
typedef struct Button {
char *name;
int iFontSize;
int status;
Region tRegion;
ONDRAW_FUNC OnDraw;
ONPRESSED_FUNC OnPressed;
}Button, *PButton;
#endif
1.2、代码编写
1.2.1、typedef函数指针
为了代码的整洁,这里要用到typedef函数指针的概念 什么是函数指针? 函数指针的本质是一个指针类型变量 指针类型变量里保存的是什么?是地址 是什么的地址?是函数的地址 举个栗子🌰:
char (*p)(int, int);
上面的语句就定义了一个函数指针 这个指针变量p 可以保存一个返回值类型为char ,入口参数数量为2,入口参数类型为int 所以可以说函数指针的定义方式为
函数返回值类型 (* 指针变量名) (函数参数列表);
函数指针是一个指针变量,那么它的变量类型是什么?它的变量类型如下
char (*)(int,int);
函数指针怎么使用?
void Func(int x)
{
printf("%d",x);
}
void (*p) (int)
p = Func;
(*p)(a, b);
什么是typedef函数指针?
typedef char (*p)(int);
一般typedef的用法是 typedef 源类型 字面类型名 的表达方式,但是在这里的用法不一样 看下面的栗子🌰:
typedef char (*p)(int);
p pFun;
char glFun(int a){ return;}
void main()
{
pFun = glFun;
(*pFun)(2);
}
和不使用typedef相对比,使用typedef之后定义一个函数指针可以不用输入返回值类型和入口参数个数和类型,看会起来清爽整洁许多
void (*p) (int)
p pFun;
现在不难理解typedef char (*p)(int); 的作用就是可以方便使用p pFun 创建一个返回值、入口参数个数和类型已经固定的函数指针
1.2.2、程序分析
disp_manage.c 添加函数
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);
}
}
▲字符居中显示解决方法
disp_manage.c 添加函数
void DrawTextInRegionCentral(char *name, PRegion ptRegion, unsigned int dwColor)
{
FontBitMap tFontBitMap;
RegionCartesian tRegionCar;
int iOriginX, iOriginY;
int i = 0;
int error;
GetStringRegionCar(name, &tRegionCar);
iOriginX = ptRegion->iLeftUpX + (ptRegion->iWidth - tRegionCar.iWidth)/2 - tRegionCar.iLeftUpX;
iOriginY = ptRegion->iLeftUpY + (ptRegion->iHeigh - tRegionCar.iHeigh)/2 + tRegionCar.iLeftUpY;
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++;
}
}
button.c :创建button设备,编写默认绘制按钮函数和默认按下反馈函数
#include <ui.h>
static int DefaultOnDraw(struct Button *ptButton, PDispBuff ptDispBuff)
{
DrawRegion(&ptButton->tRegion, BUTTON_DEFAULT_COLOR);
SetFontSize(ptButton->iFontSize);
DrawTextInRegionCentral(ptButton->name, &ptButton->tRegion, BUTTON_TEXT_COLOR);
FlushDisplayRegion(&ptButton->tRegion, ptDispBuff);
return 0;
}
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);
FlushDisplayRegion(&ptButton->tRegion, ptDispBuff);
return 0;
}
void InitButton(PButton ptButton, char *name, PRegion ptRegion, ONDRAW_FUNC OnDraw, ONPRESSED_FUNC OnPressed)
{
ptButton->status = 0;
ptButton->name = name;
if (ptRegion)
ptButton->tRegion = *ptRegion;
ptButton->OnDraw = OnDraw ? OnDraw : DefaultOnDraw;
ptButton->OnPressed = OnPressed ? OnPressed : DefaultOnPressed;
}
1.3、单元测试
ui_test.c :2秒切换一次点按状态
#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();
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, "test", &tRegion, NULL, NULL);
tButton.OnDraw(&tButton, ptBuffer);
while (1)
{
tButton.OnPressed(&tButton, ptBuffer, NULL);
sleep(2);
}
return 0;
}
2、页面系统
▲程序分层
2.1、数据结构抽象
page_manage.h
#ifndef _PAGE_MANAGER_H
#define _PAGE_MANAGER_H
typedef struct PageAction {
char *name;
void (*Run)(void *pParams);
struct PageAction *ptNext;
}PageAction, *PPageAction;
void PageRegister(PPageAction ptPageAction);
void PageSystemRegister(void);
PPageAction Page(char *name);
#endif
2.2、页面管理器
▲代码结构
page_manage.c
#include <common.h>
#include <page_manager.h>
#include <string.h>
static PPageAction g_ptPages = NULL;
void PageRegister(PPageAction ptPageAction)
{
ptPageAction->ptNext = g_ptPages;
g_ptPages = ptPageAction;
}
PPageAction Page(char *name)
{
PPageAction ptTmp = g_ptPages;
while (ptTmp)
{
if (strcmp(name, ptTmp->name) == 0)
return ptTmp;
ptTmp = ptTmp->ptNext;
}
return NULL;
}
void PageSystemRegister(void)
{
extern void MainPageRegister(void);
MainPageRegister();
}
2.3、单元测试
main_page.c
#include <page_manager.h>
#include <stdio.h>
static void MainPageRun(void *pParams)
{
printf("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}
static PageAction g_tMainPage = {
.name = "main",
.Run = MainPageRun,
};
void MainPageRegister(void)
{
PageRegister(&g_tMainPage);
}
page_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 <page_manager.h>
int main(int argc, char **argv)
{
PagesRegister();
Page("main")->Run(NULL);
return 0;
}
3、业务系统
3.1、流程及代码框架
▲业务系统程序流程图
main.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 <input_manager.h>
#include <page_manager.h>
int main(int argc, char **argv)
{
int error;
if (argc != 2)
{
printf("Usage: %s <font_file>\n", argv[0]);
return -1;
}
DisplaySystemRegister();
SelectDefaultDisplay("fb");
InitDefaultDisplay();
InputSystemRegister();
IntpuDeviceInit();
FontSystemRegister();
error = SelectAndInitFont("freetype", argv[1]);
if (error)
{
printf("SelectAndInitFont err\n");
return -1;
}
PageSystemRegister();
Page("main")->Run(NULL);
return 0;
}
▲业务系统主页面流程图
main_page.c
...
static void MainPageRun(void *pParams)
{
int error;
InputEvent tInputEvent;
PButton ptButton;
PDispBuff ptDispBuff = GetDisplayBuffer();
error = ParseConfigFile();
if (error)
return ;
GenerateButtons();
while (1)
{
error = GetInputEvent(&tInputEvent);
if (error)
continue;
ptButton = GetButtonByInputEvent(&tInputEvent);
if (!ptButton)
continue;
ptButton->OnPressed(ptButton, ptDispBuff, &tInputEvent);
}
}
...
其中的ParseConfigFile(); GenerateButtons(); GetButtonByInputEvent(); 函数将在后面编写
3.2、处理配置文件
▲GUI主界面
??为什么需要配置文件:在配置文件中可以保存一些绘制主界面需要的信息,比如每个button的name以及button是否能被touch和button被touch后需要的一些command等,这样可以增添系统的灵活性
3.2.1、数据结构抽象
config.h
#ifndef _CONFIG_H
#define _CONFIG_H
#include <common.h>
#define ITEMCFG_MAX_NUM 30
#define CFG_FILE "/etc/test_gui/gui.conf"
typedef struct ItemCfg {
int index;
char name[100];
int bCanBeTouched;
char command[100];
}ItemCfg, *PItemCfg;
int ParseConfigFile(void);
int GetItemCfgCount(void);
PItemCfg GetItemCfgByIndex(int index);
PItemCfg GetItemCfgByName(char *name);
#endif
3.2.2、程序分析
config.c :处理cfg文件
#include <config.h>
#include <stdio.h>
#include <string.h>
static ItemCfg g_tItemCfgs[ITEMCFG_MAX_NUM];
static int g_iItemCfgCount = 0;
int ParseConfigFile(void)
{
FILE *fp;
char buf[100];
char *p = buf;
fp = fopen(CFG_FILE, "r");
if (!fp)
{
printf("can not open cfg file %s\n", CFG_FILE);
return -1;
}
while (fgets(buf, 100, fp))
{
buf[99] = '\0';
p = buf;
while (*p == ' ' || *p =='\t')
p++;
if (*p == '#')
continue;
g_tItemCfgs[g_iItemCfgCount].command[0] = '\0';
g_tItemCfgs[g_iItemCfgCount].index = g_iItemCfgCount;
sscanf(p, "%s %d %s", g_tItemCfgs[g_iItemCfgCount].name, &g_tItemCfgs[g_iItemCfgCount].bCanBeTouched, \
g_tItemCfgs[g_iItemCfgCount].command);
g_iItemCfgCount++;
}
return 0;
}
int GetItemCfgCount(void)
{
return g_iItemCfgCount;
}
PItemCfg GetItemCfgByIndex(int index)
{
if (index < g_iItemCfgCount)
return &g_tItemCfgs[index];
else
return NULL;
}
PItemCfg GetItemCfgByName(char *name)
{
int i;
for (i = 0; i < g_iItemCfgCount; i++)
{
if (strcmp(name, g_tItemCfgs[i].name) == 0)
return &g_tItemCfgs[i];
}
return NULL;
}
3.3、生成界面
▲计算每个按钮的Region
main_page.c :添加在主界面绘制按钮函数
...
#define X_GAP 5
#define Y_GAP 5
static void GenerateButtons(void)
{
int width, height;
int n_per_line;
int row, rows;
int col;
int n;
PDispBuff pDispBuff;
int xres, yres;
int start_x, start_y;
int pre_start_x, pre_start_y;
PButton pButton;
int i = 0;
int iFontSize;
g_tButtonCnt = n = GetItemCfgCount();
pDispBuff = GetDisplayBuffer();
xres = pDispBuff->iXres;
yres = pDispBuff->iYres;
width = sqrt(1.0/0.618 *xres * yres / n);
n_per_line = xres / width + 1;
width = xres / n_per_line;
height = 0.618 * width;
start_x = (xres - width * n_per_line) / 2;
rows = n / n_per_line;
if (rows * n_per_line < n)
rows++;
start_y = (yres - rows*height)/2;
for (row = 0; (row < rows) && (i < n); row++)
{
pre_start_y = start_y + row * height;
pre_start_x = start_x - width;
for (col = 0; (col < n_per_line) && (i < n); col++)
{
pButton = &g_tButtons[i];
pButton->tRegion.iLeftUpX = pre_start_x + width;
pButton->tRegion.iLeftUpY = pre_start_y;
pButton->tRegion.iWidth = width - X_GAP;
pButton->tRegion.iHeigh = height - Y_GAP;
pre_start_x = pButton->tRegion.iLeftUpX;
InitButton(pButton, GetItemCfgByIndex(i)->name, NULL, NULL, MainPageOnPressed);
i++;
}
}
iFontSize = GetFontSizeForAllButton();
for (i = 0; i < n; i++)
{
g_tButtons[i].iFontSize = iFontSize;
g_tButtons[i].OnDraw(&g_tButtons[i], pDispBuff);
}
}
...
其中GetFontSizeForAllButton(); 函数将在后面实现
3.4、处理输入事件
- 我们得到的输入事件,可能来自触摸屏,也可能是其他APP发来的网络数据
▲struct InputEvent
- 对于触摸屏事件,根据iX、iY找到按钮
- 对于网络数据,我们限定为这样的格式:
??“name ok”、“name err”、“name 70%” ??根据name找到按钮 - 对于按钮,我们要提供自己的OnPressed函数,不适用UI系统默认的函数
main_page.c :添加input事件处理相关函数
static int isTouchPointInRegion(int iX, int iY, PRegion ptRegion)
{
if (iX < ptRegion->iLeftUpX || iX >= ptRegion->iLeftUpX + ptRegion->iWidth)
return 0;
if (iY < ptRegion->iLeftUpY || iY >= ptRegion->iLeftUpY + ptRegion->iHeigh)
return 0;
return 1;
}
static PButton GetButtonByName(char *name)
{
int i;
for (i = 0; i < g_tButtonCnt; i++)
{
if (strcmp(name, g_tButtons[i].name) == 0)
return &g_tButtons[i];
}
return NULL;
}
static PButton GetButtonByInputEvent(PInputEvent ptInputEvent)
{
int i;
char name[100];
if (ptInputEvent->iType == INPUT_TYPE_TOUCH)
{
for (i = 0; i < g_tButtonCnt; i++)
{
if (isTouchPointInRegion(ptInputEvent->iX, ptInputEvent->iY, &g_tButtons[i].tRegion))
return &g_tButtons[i];
}
}
else if (ptInputEvent->iType == INPUT_TYPE_NET)
{
sscanf(ptInputEvent->str, "%s", name);
return GetButtonByName(name);
}
else
{
return NULL;
}
return NULL;
}
main_page.c :添加主界面按下反馈处理函数MainPageOnPressed();
...
static int MainPageOnPressed(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent)
{
unsigned int dwColor = BUTTON_DEFAULT_COLOR;
char name[100];
char status[100];
char *strButton;
char *command_status[3] = {"err", "ok", "percent"};
int command_status_index = 0;
char command[1000];
PItemCfg ptItemCfg;
strButton = ptButton->name;
if ((ptInputEvent->iType == INPUT_TYPE_TOUCH) && (ptInputEvent->iPressure))
{
if (GetItemCfgByName(ptButton->name)->bCanBeTouched == 0)
return -1;
ptButton->status = !ptButton->status;
if (ptButton->status)
{
dwColor = BUTTON_PRESSED_COLOR;
command_status_index = 1;
}
}
else if (ptInputEvent->iType == INPUT_TYPE_NET)
{
sscanf(ptInputEvent->str, "%s %s", name, status);
if (strcmp(status, "ok") == 0)
{
command_status_index = 1;
dwColor = BUTTON_PRESSED_COLOR;
}
else if (strcmp(status, "err") == 0)
{
command_status_index = 0;
dwColor = BUTTON_DEFAULT_COLOR;
}
else if (status[0] >= '0' && status[0] <= '9')
{
command_status_index = 2;
dwColor = BUTTON_PERCENT_COLOR;
strButton = status;
}
else
return -1;
}
else
{
return -1;
}
DrawRegion(&ptButton->tRegion, dwColor);
DrawTextInRegionCentral(strButton, &ptButton->tRegion, BUTTON_TEXT_COLOR);
FlushDisplayRegion(&ptButton->tRegion, ptDispBuff);
ptItemCfg = GetItemCfgByName(ptButton->name);
if (ptItemCfg->command[0] != '\0')
{
sprintf(command, "%s %s", ptItemCfg->command, command_status[command_status_index]);
system(command);
}
return 0;
}
...
4、系统改进
4.1、按钮文字
common.h :添加数据结构RegionCartesian用来表示笛卡尔坐标系下的区域信息
typedef struct RegionCartesian {
int iLeftUpX;
int iLeftUpY;
int iWidth;
int iHeigh;
}RegionCartesian, *PRegionCartesian;
freetype.c :添加函数FreetypeGetStringRegionCar();
static int FreetypeGetStringRegionCar(char *str, PRegionCartesian ptRegionCar)
{
int i;
int error;
FT_BBox bbox;
FT_BBox glyph_bbox;
FT_Vector pen;
FT_Glyph glyph;
FT_GlyphSlot slot = g_tFace->glyph;
bbox.xMin = bbox.yMin = 32000;
bbox.xMax = bbox.yMax = -32000;
pen.x = 0;
pen.y = 0;
for (i = 0; i < strlen(str); i++)
{
FT_Set_Transform(g_tFace, 0, &pen);
error = FT_Load_Char(g_tFace, str[i], FT_LOAD_RENDER);
if (error)
{
printf("FT_Load_Char error\n");
return -1;
}
error = FT_Get_Glyph(g_tFace->glyph, &glyph);
if (error)
{
printf("FT_Get_Glyph error!\n");
return -1;
}
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_TRUNCATE, &glyph_bbox);
if ( glyph_bbox.xMin < bbox.xMin )
bbox.xMin = glyph_bbox.xMin;
if ( glyph_bbox.yMin < bbox.yMin )
bbox.yMin = glyph_bbox.yMin;
if ( glyph_bbox.xMax > bbox.xMax )
bbox.xMax = glyph_bbox.xMax;
if ( glyph_bbox.yMax > bbox.yMax )
bbox.yMax = glyph_bbox.yMax;
pen.x += slot->advance.x;
pen.y += slot->advance.y;
}
ptRegionCar->iLeftUpX = bbox.xMin;
ptRegionCar->iLeftUpY = bbox.yMax;
ptRegionCar->iWidth = bbox.xMax - bbox.xMin + 1;
ptRegionCar->iHeigh = bbox.yMax - bbox.yMin + 1;
return 0;
}
font_manage.c :添加函数GetStringRegionCar();
int GetStringRegionCar(char *str, PRegionCartesian ptRegionCar)
{
return g_ptDefaulFontOpr->GetStringRegionCar(str, ptRegionCar);
}
main_page.c :添加函数GetFontSizeForAllButton();
static int GetFontSizeForAllButton(void)
{
int i;
int max_len = -1;
int max_index = 0;
int len;
RegionCartesian tRegionCar;
float k, kx, ky;
for (i = 0; i < g_tButtonCnt; i++)
{
len = strlen(g_tButtons[i].name);
if (len > max_len)
{
max_len = len;
max_index = i;
}
}
SetFontSize(100);
GetStringRegionCar(g_tButtons[max_index].name, &tRegionCar);
kx = (float)g_tButtons[max_index].tRegion.iWidth / tRegionCar.iWidth;
ky = (float)g_tButtons[max_index].tRegion.iHeigh / tRegionCar.iHeigh;
if (kx < ky)
k = kx;
else
k = ky;
return k * 100 * 0.8;
}
▲字符居中显示方案
disp_manage.c :修改DrawTextInRegionCentral(); 函数
void DrawTextInRegionCentral(char *name, PRegion ptRegion, unsigned int dwColor)
{
FontBitMap tFontBitMap;
RegionCartesian tRegionCar;
int iOriginX, iOriginY;
int i = 0;
int error;
GetStringRegionCar(name, &tRegionCar);
iOriginX = ptRegion->iLeftUpX + (ptRegion->iWidth - tRegionCar.iWidth)/2 - tRegionCar.iLeftUpX;
iOriginY = ptRegion->iLeftUpY + (ptRegion->iHeigh - tRegionCar.iHeigh)/2 + tRegionCar.iLeftUpY;
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++;
}
}
4.2、支持配置文件的command
最重要的一句system(command); 这句函数可以让设备在console上执行command 字符串中包含的命令
led.sh
#!/bin/sh
status=$1
if [ "$status" = "ok" ]
then
echo "led has been tested, it is ok"
fi
if [ "$status" = "err" ]
then
echo "led has been tested, it is fail"
fi
参考资料
通俗易懂详解typedef函数指针
|