ITADN
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

DVUI — 面向应用与游戏的即时模式 Zig GUI

Zig GUI 工具包,适用于完整应用程序或现有应用/游戏中的调试窗口。

已在 Zig v0.16.0 上测试(对于 Zig v0.15.2,请使用 DVUI 分支 zig15 或 标签 v0.4.0)。

主页 · 演示 · 文档 · 开发日志

Screenshot of DVUI Standalone Example (Application Window)

示例

zig build sdl3-app

  • sdl3 后端,dvui 处理主循环
  • 适合入门,尝试修改 frame() 中的 ./examples/app.zig
  • 参见 Getting Started
后端 作为应用
dvui 处理主循环
app.zig
独立
你控制主循环
*-standalone.zig
叠加
在现有应用/游戏上显示调试 HUD
*-ontop.zig
SDL3sdl3-appsdl3-standalonesdl3-ontop
SDL3GPU
通过 SDL GPU 进行渲染
待办sdl3gpu-standalonesdl3gpu-ontop
SDL2sdl2-appsdl2-standalonesdl2-ontop
Raylib
C API
raylib-appraylib-standaloneraylib-ontop
Raylib
绑定 raylib-zig
raylib-zig-appraylib-zig-standaloneraylib-zig-ontop
DX11dx11-appdx11-standalonedx11-ontop
GLFWglfw-apptodoglfw-opengl-ontop
wio
基于 wio 的 OpenGL
wio-appwio-standalonewio-ontop
基于 wio 的 Vulkan wio-app -Drenderer=vulkanwio-standalone -Drenderer=vulkanwio-ontop -Drenderer=vulkan
pugl
基于 pugl 的 OpenGL
pugl-apppugl-standalonenone
Webweb-appnonenone

dvui-demo 是一个模板仓库,其中也包含这些示例。 参见 Getting Started

Docs

  • zig build docs -Dgenerate-images
  • 加载 ./zig-out/docs/index.html
  • Online Docs

以下项目使用了 DVUI:

在以下平台讨论你的项目:

Feature Overview

延伸阅读:

入门指南

dvui-demo 是一个模板仓库

  • build.zigbuild.zig.zon 将 dvui 作为 zig 依赖项引用
  • 包含所有示例

Alternatively:

  1. Add DVUI as a dependency:
    zig fetch --save git+https://github.com/david-vanderson/dvui#main
    
  2. Add build.zig logic (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"));
    

延伸阅读:

排查 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-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 的后端上正常工作,你必须直接导入后者:
  1. 在 `build.zig` 中(此处使用 SDL3 后端):
    exe.root_module.addImport("sdl-backend", dvui_dep.module("sdl3"));
  2. 然后在你的代码中:
    const SDLBackend = @import("sdl-backend");
如何调试 DVUI? 使用调试窗口 dvui.toggleDebugWindow()。其预览可作为在线演示主页上的 Debug Window 按钮使用。
在哪里获取 DVUI 新功能的更新? 阅读 DVUI Devlog,其中还涵盖了诸如 DVUI 中的单位 等主题。可以订阅其 RSS 源。

内置组件

目前已实现的组件:

  • 文本输入:
    • 单行和多行
    • 包含触摸支持(可选择拖拽项和菜单)
  • 数字输入:
    • 支持所有整数和浮点类型
  • 文本布局:
    • 部分可点击
    • 部分可单独设置样式
  • 浮动窗口
  • 菜单
  • 弹出/上下文窗口
  • 滚动区域
  • 按钮
  • 多行标签:
    • 可点击以作为链接
  • 工具提示
  • 滑块
  • 滑块输入:
    • 组合滑块和文本输入
  • 复选框
  • 单选按钮
  • 吐司提示
  • 带有可拖动分隔条的面板
  • 下拉列表
  • 组合框
  • 可重新排序的列表:
    • 拖拽以重新排序/删除/添加
  • 数据网格
  • 分组框(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);
      }
      

缺点

  • 难以实现“发射后不管”(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。

小部件 initdeinit

使用小部件最简单的方法是通过创建它们的高级函数:

{
    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 条信息:

  • 最小尺寸
  • 当空间大于最小尺寸时的提示(expandgravity_xgravity_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_shadow
  • style(使用主题的颜色)
  • colors(直接指定):
    • color_fill
    • color_fill_hover
    • color_fill_press
    • color_text
    • color_text_hover
    • color_text_press
    • color_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.roleOptions.label
    • AccessKit 集成
    • -Daccesskit 添加到 zig build

延伸阅读: