libui是一个 C 中简单且可移植(但并非不灵活)的 GUI 库,它使用每个平台原生的GUI技术进行绘制。
官网地址:链接
相关文件:链接
一、配置说明
1. 所需链接的库
在使用libui的过程中至少需要链接以下库
user32.lib
kernel32.lib
gdi32.lib
comctl32.lib
uxtheme.lib
msimg32.lib
comdlg32.lib
d2d1.lib
dwrite.lib
ole32.lib
oleaut32.lib
oleacc.lib
uuid.lib
windowscodecs.lib
libui.a
2. 静态库的使用
静态库在使用时还需要在项目中加入资源文件,下面的 文件 选择其中之一即可
- static.manifest
- resources.res
如果使用resources.res出现问题,请见 解决办法
二、简单使用
1. 空白窗口
#include <stdio.h>
#include "ui.h"
int onClosing(uiWindow* w, void* data)
{
uiQuit();
return 1;
}
int main(void)
{
uiInitOptions o = { 0 };
const char* err;
uiWindow* window;
// 初始化ui
err = uiInit(&o);
if (err != NULL) {
fprintf(stderr, u8"初始化错误: %s\n", err);
uiFreeInitError(err);
return 1;
}
// 创建一个新窗口
window = uiNewWindow("Hello World!", 300, 300, 0);
uiWindowOnClosing(window, onClosing, NULL);
// 显示窗口
uiControlShow(uiControl(window));
// 启动主循环
uiMain();
// 释放内存空间
uiUninit();
return 0;
}
2. 绘制文本
#include <stdio.h>
#include "ui.h"
int onClosing(uiWindow* w, void* data)
{
uiQuit();
return 1;
}
int main(void)
{
uiInitOptions o = { 0 };
const char* err;
uiWindow* window;
uiLabel* label;
// 初始化ui
err = uiInit(&o);
if (err != NULL) {
fprintf(stderr, u8"初始化错误: %s\n", err);
uiFreeInitError(err);
return 1;
}
// 创建一个新窗口
window = uiNewWindow("Hello World!", 300, 300, 0);
uiWindowOnClosing(window, onClosing, NULL);
// 添加标签
label = uiNewLabel("Hello, World!");
uiWindowSetChild(window, uiControl(label));
// 显示窗口
uiControlShow(uiControl(window));
// 启动主循环
uiMain();
uiUninit();
return 0;
}
三、控件介绍
1. 容器控件
uiWindow
一个代表顶层窗口的控件,可包含其他控件。
uiBox
一个容纳一组控件的盒状容器。
uiTab
一个多页面控制界面容器,一次显示一个页面。
uiGroup
向所包含的子控件添加标签的控件容器。
uiForm
将包含的控件组织为带标签的字段的控件容器。
uiGrid
要在网格中排列的包含控件的控件容器。
2. 数据输入
uiCheckbox
带有用户可选框并带有文本标签的控件。
uiEntry
具有单行文本输入字段的控件。
uiSpinbox
通过文本字段或 +/- 按钮显示和修改整数值的控件。
uiSlider
通过用户可拖动的滑块显示和修改整数值的控件。
uiCombobox
通过下拉菜单从预定义项目列表中选择一项的控件。
uiEditableCombobox
用于从预定义的项目列表中选择一个项目或输入自己的项目的控件。
uiRadioButtons
复选按钮的多选控件,一次只能从中选择一个。
uiDateTimePicker
输入日期和/或时间的控件。
uiMultilineEntry
具有多行文本输入字段的控件。
uiFontButton
单击时打开字体选择器的类似按钮的控件。
uiColorButton
带有颜色指示器的控件,单击时会打开颜色选择器。
uiTable
以表格方式显示数据的控件。
3. 静态控件
uiLabel
显示非交互式文本的控件。
uiProgressBar
通过水平条的填充级别可视化任务进度的控件。
uiSeparator
用于在视觉上水平或垂直分隔控件的控件。
uiMenuItem
与uiMenu结合使用的菜单项。
uiMenu
应用程序级菜单栏。
uiImage
要在屏幕上显示的图像的容器。
4. 按钮控件
uiButton
uiFontButton
uiColorButton
5. 对话框窗口
6. 菜单
7. 表格
四、综合案例
1. 直方图
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "ui.h"
uiWindow* mainwin;
uiArea* histogram;
uiAreaHandler handler;
uiSpinbox* datapoints[10];
uiColorButton* colorButton;
int currentPoint = -1;
// some metrics
#define xoffLeft 20 /* 直方图的左边缘 */
#define yoffTop 20 /* 直方图的上边缘 */
#define xoffRight 20 /* 直方图的右边缘 */
#define yoffBottom 20 /* 直方图的下边缘 */
#define pointRadius 10 /* 点的大小 */
// 命名颜色
#define colorWhite 0xFFFFFF
#define colorBlack 0x000000
#define colorDodgerBlue 0x1E90FF
// 设置画刷的颜色
static void setSolidBrush(uiDrawBrush* brush, uint32_t color, double alpha)
{
uint8_t component;
brush->Type = uiDrawBrushTypeSolid;
component = (uint8_t)((color >> 16) & 0xFF);
brush->R = ((double)component) / 255;
component = (uint8_t)((color >> 8) & 0xFF);
brush->G = ((double)component) / 255;
component = (uint8_t)(color & 0xFF);
brush->B = ((double)component) / 255;
brush->A = alpha;
}
// 根据给定的宽度和高度,计算出每个数据点在绘图区域中的位置坐标
static void pointLocations(double width, double height, double* xs, double* ys)
{
double xincr, yincr;
int i, n;
xincr = width / 9; // 10 - 1 to make the last point be at the end
yincr = height / 100;
for (i = 0; i < 10; i++) {
// get the value of the point
n = uiSpinboxValue(datapoints[i]);
// because y=0 is the top but n=0 is the bottom, we need to flip
n = 100 - n;
xs[i] = xincr * i;
ys[i] = yincr * n;
}
}
// 据给定的宽度和高度以及当前界面上的数据点,构建一个绘制折线图的路径
static uiDrawPath* constructGraph(double width, double height, int extend)
{
uiDrawPath* path;
double xs[10], ys[10];
int i;
pointLocations(width, height, xs, ys);
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathNewFigure(path, xs[0], ys[0]);
for (i = 1; i < 10; i++)
uiDrawPathLineTo(path, xs[i], ys[i]);
if (extend) {
uiDrawPathLineTo(path, width, height);
uiDrawPathLineTo(path, 0, height);
uiDrawPathCloseFigure(path);
}
uiDrawPathEnd(path);
return path;
}
// 根据介绍的客户区域计算图形大小
static void graphSize(double clientWidth, double clientHeight, double* graphWidth, double* graphHeight)
{
*graphWidth = clientWidth - xoffLeft - xoffRight;
*graphHeight = clientHeight - yoffTop - yoffBottom;
}
static void handlerDraw(uiAreaHandler* a, uiArea* area, uiAreaDrawParams* p)
{
uiDrawPath* path;
uiDrawBrush brush;
uiDrawStrokeParams sp;
uiDrawMatrix m;
double graphWidth, graphHeight;
double graphR, graphG, graphB, graphA;
// fill the area with white
setSolidBrush(&brush, colorWhite, 1.0);
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathAddRectangle(path, 0, 0, p->AreaWidth, p->AreaHeight);
uiDrawPathEnd(path);
uiDrawFill(p->Context, path, &brush);
uiDrawFreePath(path);
// figure out dimensions
graphSize(p->AreaWidth, p->AreaHeight, &graphWidth, &graphHeight);
// clear sp to avoid passing garbage to uiDrawStroke()
// for example, we don't use dashing
memset(&sp, 0, sizeof(uiDrawStrokeParams));
// make a stroke for both the axes and the histogram line
sp.Cap = uiDrawLineCapFlat;
sp.Join = uiDrawLineJoinMiter;
sp.Thickness = 2;
sp.MiterLimit = uiDrawDefaultMiterLimit;
// draw the axes
setSolidBrush(&brush, colorBlack, 1.0);
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathNewFigure(path,
xoffLeft, yoffTop);
uiDrawPathLineTo(path,
xoffLeft, yoffTop + graphHeight);
uiDrawPathLineTo(path,
xoffLeft + graphWidth, yoffTop + graphHeight);
uiDrawPathEnd(path);
uiDrawStroke(p->Context, path, &brush, &sp);
uiDrawFreePath(path);
// now transform the coordinate space so (0, 0) is the top-left corner of the graph
uiDrawMatrixSetIdentity(&m);
uiDrawMatrixTranslate(&m, xoffLeft, yoffTop);
uiDrawTransform(p->Context, &m);
// now get the color for the graph itself and set up the brush
uiColorButtonColor(colorButton, &graphR, &graphG, &graphB, &graphA);
brush.Type = uiDrawBrushTypeSolid;
brush.R = graphR;
brush.G = graphG;
brush.B = graphB;
// we set brush->A below to different values for the fill and stroke
// now create the fill for the graph below the graph line
path = constructGraph(graphWidth, graphHeight, 1);
brush.A = graphA / 2;
uiDrawFill(p->Context, path, &brush);
uiDrawFreePath(path);
// now draw the histogram line
path = constructGraph(graphWidth, graphHeight, 0);
brush.A = graphA;
uiDrawStroke(p->Context, path, &brush, &sp);
uiDrawFreePath(path);
// now draw the point being hovered over
if (currentPoint != -1) {
double xs[10], ys[10];
pointLocations(graphWidth, graphHeight, xs, ys);
path = uiDrawNewPath(uiDrawFillModeWinding);
uiDrawPathNewFigureWithArc(path,
xs[currentPoint], ys[currentPoint],
pointRadius,
0, 6.23, // TODO pi
0);
uiDrawPathEnd(path);
// use the same brush as for the histogram lines
uiDrawFill(p->Context, path, &brush);
uiDrawFreePath(path);
}
}
static int inPoint(double x, double y, double xtest, double ytest)
{
// TODO switch to using a matrix
x -= xoffLeft;
y -= yoffTop;
return (x >= xtest - pointRadius) &&
(x <= xtest + pointRadius) &&
(y >= ytest - pointRadius) &&
(y <= ytest + pointRadius);
}
static void handlerMouseEvent(uiAreaHandler* a, uiArea* area, uiAreaMouseEvent* e)
{
double graphWidth, graphHeight;
double xs[10], ys[10];
int i;
graphSize(e->AreaWidth, e->AreaHeight, &graphWidth, &graphHeight);
pointLocations(graphWidth, graphHeight, xs, ys);
for (i = 0; i < 10; i++)
if (inPoint(e->X, e->Y, xs[i], ys[i]))
break;
if (i == 10) // not in a point
i = -1;
currentPoint = i;
// TODO only redraw the relevant area
uiAreaQueueRedrawAll(histogram);
}
static void handlerMouseCrossed(uiAreaHandler* ah, uiArea* a, int left)
{
// do nothing
}
static void handlerDragBroken(uiAreaHandler* ah, uiArea* a)
{
// do nothing
}
static int handlerKeyEvent(uiAreaHandler* ah, uiArea* a, uiAreaKeyEvent* e)
{
// reject all keys
return 0;
}
static void onDatapointChanged(uiSpinbox* s, void* data)
{
uiAreaQueueRedrawAll(histogram);
}
// 回调函数,当颜色按钮改变时调用
static void onColorChanged(uiColorButton* b, void* data)
{
uiAreaQueueRedrawAll(histogram);
}
// 回调函数,用户关闭窗口时,销毁主窗口,再执行释放内存
static int onClosing(uiWindow* w, void* data)
{
uiControlDestroy(uiControl(mainwin));
uiQuit(); //关闭整个 UI 程序并释放所有分配的内存
return 0;
}
static int shouldQuit(void* data)
{
uiControlDestroy(uiControl(mainwin)); // 销毁窗口对象
return 1;
}
int main(void)
{
uiInitOptions o;
const char* err;
uiBox* hbox, * vbox;
int i;
uiDrawBrush brush;
handler.Draw = handlerDraw;
handler.MouseEvent = handlerMouseEvent;
handler.MouseCrossed = handlerMouseCrossed;
handler.DragBroken = handlerDragBroken;
handler.KeyEvent = handlerKeyEvent;
memset(&o, 0, sizeof(uiInitOptions));
err = uiInit(&o);
if (err != NULL) {
fprintf(stderr, "error initializing ui: %s\n", err);
uiFreeInitError(err);
return 1;
}
// 通过回调函数判断程序是否退出
uiOnShouldQuit(shouldQuit, NULL);
mainwin = uiNewWindow("libui Histogram Example", 640, 480, 1);
uiWindowSetMargined(mainwin, 1);
uiWindowOnClosing(mainwin, onClosing, NULL);
hbox = uiNewHorizontalBox();
uiBoxSetPadded(hbox, 1);
uiWindowSetChild(mainwin, uiControl(hbox));
vbox = uiNewVerticalBox();
uiBoxSetPadded(vbox, 1);
uiBoxAppend(hbox, uiControl(vbox), 0);
// 设置10组数据框,添加到vbox
srand(time(NULL));
for (i = 0; i < 10; i++) {
datapoints[i] = uiNewSpinbox(0, 100);
uiSpinboxSetValue(datapoints[i], rand() % 101);
uiSpinboxOnChanged(datapoints[i], onDatapointChanged, NULL); // 设置数据区域变化时发生的操作
uiBoxAppend(vbox, uiControl(datapoints[i]), 0);
}
// 设置颜色选择按钮,添加到vbox
colorButton = uiNewColorButton();
setSolidBrush(&brush, colorDodgerBlue, 1.0);
uiColorButtonSetColor(colorButton, brush.R, brush.G, brush.B, brush.A);
uiColorButtonOnChanged(colorButton, onColorChanged, NULL); // 设置颜色按钮变化时执行的操作
uiBoxAppend(vbox, uiControl(colorButton), 0);
// 设置绘图区域,添加到hbox
histogram = uiNewArea(&handler);
uiBoxAppend(hbox, uiControl(histogram), 1);
// 显示窗口
uiControlShow(uiControl(mainwin));
// 执行循环
uiMain();
// 释放内存空间
uiUninit();
return 0;
}
附录
其他分支:
- https://github.com/neroist/uing
- https://github.com/libui-ng/libui-ng
参考文档:https://libui-ng.github.io/libui-ng/annotated.html