DVUI — 面向应用与游戏的即时模式 Zig GUI
Zig GUI 工具包,适用于完整应用程序或现有应用/游戏中的调试窗口。
已在 Zig v0.16.0 上测试(对于 Zig v0.15.2,请使用 DVUI 分支 zig15 或 标签 v0.4.0)。

示例
zig build sdl3-app
- sdl3 后端,dvui 处理主循环
- 适合入门,尝试修改
frame()中的./examples/app.zig - 参见 Getting Started
| 后端 |
作为应用
dvui 处理主循环 app.zig
|
独立
你控制主循环 *-standalone.zig
|
叠加
在现有应用/游戏上显示调试 HUD *-ontop.zig
|
|---|---|---|---|
| SDL3 | sdl3-app | sdl3-standalone | sdl3-ontop |
|
SDL3GPU
通过 SDL GPU 进行渲染 | 待办 | sdl3gpu-standalone | sdl3gpu-ontop |
| SDL2 | sdl2-app | sdl2-standalone | sdl2-ontop |
|
Raylib
C API | raylib-app | raylib-standalone | raylib-ontop |
|
Raylib
绑定 raylib-zig
| raylib-zig-app | raylib-zig-standalone | raylib-zig-ontop |
| DX11 | dx11-app | dx11-standalone | dx11-ontop |
| GLFW | glfw-app | todo | glfw-opengl-ontop |
|
wio
基于 wio 的 OpenGL
| wio-app | wio-standalone | wio-ontop |
基于 wio 的 Vulkan
| wio-app -Drenderer=vulkan | wio-standalone -Drenderer=vulkan | wio-ontop -Drenderer=vulkan |
|
pugl
基于 pugl 的 OpenGL
| pugl-app | pugl-standalone | none |
| Web | web-app | none | none |
dvui-demo 是一个模板仓库,其中也包含这些示例。 参见 Getting Started。
Docs
zig build docs -Dgenerate-images- 加载
./zig-out/docs/index.html - Online Docs
Featured Projects
以下项目使用了 DVUI:
- Fizzy - Pixel art editor
- Graphl Visual Programming Language Demo
- recastnavigation - Recast/Detour Tooling
- Podcast Player
- Graphical Janet REPL
- FIDO2/ Passkey compatible authenticator implementation for Linux
- QEMU frontend
- Static site generator GUI
- File explorer for Altair 8800 disk images
- Kanji flashcard app
- Azem - WIP micro-mouse simulator / maze solver - Demo
在以下平台讨论你的项目:
- Zig Discord
#dvui或#dvui-help - Zig Libera IRC
#dvui - DVUI GitHub Discussions
Feature Overview
- 即时模式 API:
- 参见 设计
- 处理每个输入事件
- 适用于低帧率场景
- 适用于
- 整个 UI(
*-app示例) - 在现有应用之上进行调试
- 参见 Ontop-Floating-Windows
- 整个 UI(
- 后端
- TinyVG 图标
zig-lib-svg2tvg- 更多图标见
zig-lib-icons
- 位图图像
- 字体
- 触摸
- 文本输入框中的选择拖拽
- 捏合缩放
- 无障碍性:
- AccessKit,通过向
zig build添加标志-Daccesskit启用 - 参见 Accessibility
- AccessKit,通过向
- 原生文件对话框
- 动画
- 主题
- FPS 限制
延伸阅读:
- 如何编写和修改容器小部件的实现细节:
入门指南
dvui-demo 是一个模板仓库
build.zig和build.zig.zon将 dvui 作为 zig 依赖项引用- 包含所有示例
Alternatively:
- Add DVUI as a dependency:
zig fetch --save git+https://github.com/david-vanderson/dvui#main - Add
build.ziglogic (here using SDL3 backend):const dvui_dep = b.dependency("dvui", .{ .target = target, .optimize = optimize, .backend = .sdl3 }); exe.root_module.addImport("dvui", dvui_dep.module("dvui_sdl3"));
延伸阅读:
- 使用未随 DVUI 捆绑的
raylib-zig版本:
排查 Raylib 问题
- 如果遇到错误
No Wayland,则还需添加标志-Dlinux_display_backend=X11
网络故障排除
- 要加载此后端的示例,必须首先通过(本地)Web 服务器使用以下方式进行服务:
- Zig
zig build serve-web-app -Dbackend=web - Python
python -m http.server -d ./zig-out/bin/web-app - Caddy
caddy file-server --root ./zig-out/bin/web-app --listen :8000 - 任何其他 Web 服务器
- Zig
- 输出存储在
./zig-out/bin/web-app/
Nixos 故障排除
如果你在运行示例时遇到问题,例如:
error=No available video device
这可能意味着你需要向 LD_LIBRARY_PATH 添加软件包
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath ([
pkgs.libGLX
pkgs.libx11
pkgs.libxcursor
pkgs.libxext
pkgs.libxfixes
pkgs.libxi
pkgs.libxinerama
pkgs.libxrandr
pkgs.libxrender
pkgs.udev
pkgs.wayland
pkgs.vulkan-loader
pkgs.alsa-lib
pkgs.libusb1
pkgs.libdecor
pkgs.libxkbcommon
pkgs.dbus
]);
常见问题
如何为 DVUI 启用 LSP 自动补全?
要使 ZLS 自动补全 在 DVUI 的后端上正常工作,你必须直接导入后者:-
在 `build.zig` 中(此处使用 SDL3 后端):
exe.root_module.addImport("sdl-backend", dvui_dep.module("sdl3")); -
然后在你的代码中:
const SDLBackend = @import("sdl-backend");
如何调试 DVUI?
使用调试窗口dvui.toggleDebugWindow()。其预览可作为在线演示主页上的 Debug Window 按钮使用。
内置组件
目前已实现的组件:
- 文本输入:
- 单行和多行
- 包含触摸支持(可选择拖拽项和菜单)
- 数字输入:
- 支持所有整数和浮点类型
- 文本布局:
- 部分可点击
- 部分可单独设置样式
- 浮动窗口
- 菜单
- 弹出/上下文窗口
- 滚动区域
- 按钮
- 多行标签:
- 可点击以作为链接
- 工具提示
- 滑块
- 滑块输入:
- 组合滑块和文本输入
- 复选框
- 单选按钮
- 吐司提示
- 带有可拖动分隔条的面板
- 下拉列表
- 组合框
- 可重新排序的列表:
- 拖拽以重新排序/删除/添加
- 数据网格
- 分组框(fieldset)
设计
立即模式
与传统 GUI 工具包(GTK、Win32、Cocoa)不同,控件不会在帧之间存储。在下面的示例中,dvui.button() 处理输入事件,在屏幕上绘制按钮,如果本帧发生了按钮点击则返回 true:
if (dvui.button(@src(), "Ok", .{}, .{})) {
dialog.close();
}
关于即时模式 GUI(IMGUI)的介绍,请参阅 Dear ImGui 中的相应章节。
优势
- 减少 widget 状态
- 例如,一个直接使用你应用中的 bool 的 checkbox
- 减少 GUI 状态
- 每帧显示的 widgets 直接反映每帧运行的代码
- 更难出现 GUI 显示一种内容但应用认为显示另一种内容的状态
- 无需清理不再需要的 widgets
- 函数是 GUI 的可组合构建块
- 由于运行一个 widget 是一个函数,你可以轻松封装一个 widget:
// Let's wrap the sliderEntry widget so we have 3 that represent a Color pub fn colorSliders(src: std.builtin.SourceLocation, color: *dvui.Color, opts: Options) void { var hbox = dvui.box(src, .{ .dir = .horizontal }, opts); defer hbox.deinit(); var red: f32 = color.r; var green: f32 = color.g; var blue: f32 = color.b; _ = dvui.sliderEntry(@src(), "R: {d:0.0}", .{ .value = &red, .min = 0, .max = 255, .interval = 1 }, .{ .gravity_y = 0.5 }); _ = dvui.sliderEntry(@src(), "G: {d:0.0}", .{ .value = &green, .min = 0, .max = 255, .interval = 1 }, .{ .gravity_y = 0.5 }); _ = dvui.sliderEntry(@src(), "B: {d:0.0}", .{ .value = &blue, .min = 0, .max = 255, .interval = 1 }, .{ .gravity_y = 0.5 }); color.r = @trunc(red); color.g = @trunc(green); color.b = @trunc(blue); }
- 由于运行一个 widget 是一个函数,你可以轻松封装一个 widget:
缺点
- 难以实现“发射后不管”(fire-and-forget)
- 例如,从下一帧不会运行的代码中显示一个带有错误消息的对话框
- DVUI 为此包含了一个用于对话框和 toast 的保留模式空间
- 难以实现对话框序列
- 保留模式 GUI 可以递归地运行模态对话框,使得对话框代码可以只存在于单个函数中
- DVUI 的保留对话框可以为此串联在一起
处理所有事件
DVUI 处理每个输入事件,使其在低帧率情况下可用。一个按钮可以在同一帧内接收鼠标按下事件和鼠标释放事件,并正确报告一次点击。一个自定义按钮甚至可以在每帧报告多次点击(更高级的 dvui.button() 函数每帧只报告 1 次点击)。
在同一帧中,以下情况都可能发生:
- 文本输入框 A 接收文本事件
- 文本输入框 A 接收一个制表符,将键盘焦点移动到框 B
- 文本输入框 B 接收更多文本事件
由于所有操作都在单次遍历中完成,这在组件 A 在组件 B 之前运行的正常情况下是有效的。 在相反的顺序下(组件 B 接收一个制表符,将焦点移动到 A)它不起作用,因为 A 在获得焦点之前就已经运行了。
Ontop-Floating-Windows
此库可以以 2 种方式使用:
- 作为整个应用程序的 GUI,绘制在整个操作系统窗口之上
- 作为现有应用程序顶部的浮动窗口,只需进行最少的更改:
- 仅在
dvui.floatingWindow()调用内部使用组件 - 如果事件不会被 DVUI 处理,
dvui.Window.addEvent...函数将返回false(主应用程序应处理它) - 将
dvui.Window.cursorRequested()更改为dvui.Window.cursorRequestedFloating(),如果应由主应用程序设置鼠标光标,则返回null
- 仅在
浮动窗口和弹出窗口通过延迟其渲染来处理,以便它们能正确渲染在下方窗口之上。 所有浮动窗口和弹出窗口的渲染都在 dvui.Window.end() 期间发生。
FPS-Throttling
如果你的应用程序以固定帧率运行,请使用 dvui.Window.begin() 和 dvui.Window.end(),它们负责簿记和渲染。
如果你希望 dvui 为你处理主循环,请使用 dvui.App。
如果你只想在需要时渲染帧,请在开头添加 dvui.Window.beginWait(),在结尾添加 dvui.Window.waitTime()。它们协同工作,在以下情况下休眠适当的时间并渲染帧:
- 有事件传入
- 动画正在进行
- 计时器已到期
- GUI 代码调用
dvui.refresh(null, ...)(如果你的代码知道在当前帧之后需要一帧) - 后台线程调用
dvui.refresh(window, ...),进而调用backend.refresh()
dvui.Window.waitTime() 还接受一个最大 FPS 参数,以确保帧率保持在给定值以下。
dvui.Window.beginWait() 和 dvui.Window.waitTime() 维护一个内部估计值,用于衡量在渲染代码之外花费了多少时间。这用于计算下一帧应休眠多长时间。
该估计值可在演示窗口 Animations > Clock > Estimate of frame overhead 中查看。该估计值仅在由计时器到期引起的帧上更新(如时钟示例),初始值为 1 ms。
小部件 init 和 deinit
使用小部件最简单的方法是通过创建它们的高级函数:
{
var box = dvui.box(@src(), .{}, .{.expand = .both});
defer box.deinit();
// Widgets run here will be children of box
}
这些函数将小部件的内存分配到一个内部竞技场分配器中,该分配器每帧刷新一次。
你也可以使用低级函数将小部件分配在栈上:
{
var box: BoxWidget = undefined;
box.init(@src(), .{}, .{.expand = .both});
// Box is now parent widget
box.drawBackground();
// Might draw the background in a different way
defer box.deinit();
// Widgets run here will be children of box
}
低级函数提供了更多的自定义选项,包括动画、拦截事件和不同的绘制方式。
从高级函数开始,在需要时,复制高级函数的函数体并在此基础上进行自定义。
父级、子级和布局
主要的布局机制是嵌套控件。DVUI 会跟踪当前的父控件。当一个控件运行时,它是当前父控件的子控件。该控件随后可以将自己设为当前父控件,并在运行结束时重置回之前的父控件 deinit()。
父控件决定为每个子控件分配屏幕上的哪个矩形区域,除非子控件在其 dvui.Options 中传递了 .rect = 。
通常,你希望 GUI 的每个部分要么紧密打包(仅占用最小尺寸),要么扩展以占据可用空间。垂直方向和水平方向的选择可能不同。
当子控件被布局(确定尺寸和位置)时,它会向父控件发送 2 条信息:
- 最小尺寸
- 当空间大于最小尺寸时的提示(
expand、gravity_x和gravity_y)
如果父控件未被 expand,意图是尽可能紧密打包,因此它只会给所有子控件分配其最小尺寸。
如果父控件拥有的空间多于子控件所需,它将使用提示来布局它们:
expand— 此子控件是否应占据更多空间gravity— 如果未被expand,在更大的空间中如何定位子控件
有关更多信息,请参阅 readme-implementation。
外观
每个小部件在创建时都可以通过 Options 结构体更改以下选项:
margin(边框外的空间)border(每一侧)padding(边框内的空间)min_size_content(为获得最小尺寸而添加的 margin/border/padding)max_size_content(为获得最大最小尺寸而添加的 margin/border/padding)background(用背景色填充边框内的空间)corner_radius(针对每个角)box_shadowstyle(使用主题的颜色)colors(直接指定):color_fillcolor_fill_hovercolor_fill_presscolor_textcolor_text_hovercolor_text_presscolor_border
font(直接指定):- 可通过
Font.theme(.body)引用主题字体(或.heading、.title、.mono)
- 可通过
theme(完全使用另一个主题)ninepatch_fill(以及_hover和_press):- 在背景上绘制一张图像
每个小部件都有自己的默认选项。这些可以直接更改:
dvui.ButtonWidget.defaults.background = false;
主题可以在帧之间甚至帧内更改。主题控制 font_style 引用的字体和命名颜色:
if (theme_dark) {
dvui.themeSet(dvui.Theme.builtin.adwaita_dark);
} else {
dvui.themeSet(dvui.Theme.builtin.adwaita_light);
}
主题的 focus 颜色用于显示键盘焦点。
如果未向 Window.init() 传递主题,默认主题将尝试跟随系统的深色或浅色模式。
可访问性
DVUI 对不同种类的可访问性基础设施的支持程度各不相同。当前状态,包括通常与可访问性相关的领域,如下:
- 键盘导航:
- 大多数控件支持键盘导航
- 语言支持:
- 文本渲染为简单的从左到右,每个 unicode 码点渲染为单个字形
- 目前不支持字素簇
- 不支持从右到左或混合文本方向
- 语言输入:
- IME(输入法编辑器)在 SDL 和 web 后端中可用
- 高对比度主题:
- DVUI 的主题可以支持此功能
- 目前没有操作系统集成
- 屏幕阅读和替代输入:
- 使用来自 AccessKit 集成的
Options.role和Options.label - AccessKit 集成
- 将
-Daccesskit添加到zig build
- 使用来自 AccessKit 集成的
延伸阅读:
- 跟踪可访问性进度: