david-vanderson/dvui
No description provided.
d29847ebcb6da34dec466a06163431982500a09281ea86c805f69edc32795c6c83f5be020335ca96153f45bf550d54c902c2e6233c1c0554f067028c0e73e0ea640d969762bc961106df9538817595e433f1659.tar.gz5587b16fa040573846a6bf531301f6206d31a6bf78797970be3d676a9087696ec36d6f29a522b580Zig GUI toolkit for whole applications or debugging windows in existing applications.
Tested with Zig 0.15.1 (use tag v0.3.0 for zig 0.14.1)
How to run the built-in examples:
zig build sdl3-standalonezig build sdl3-ontop zig build sdl3-appzig build sdl2-standalonezig build sdl2-ontopzig build sdl2-appNo Wayland also add flag -Dlinux_display_backend=X11zig build raylib-standalonezig build raylib-ontopzig build raylib-appzig build dx11-standalonezig build dx11-ontopzig build dx11-apppython -m http.server -d zig-out/bin/EXAMPLE_NAMEcaddy file-server --root zig-out/bin/EXAMPLE_NAME --listen :8000zig build web-testzig-out/bin/web-test/index.htmlzig build web-appzig-out/bin/web-app/index.htmlzig build docszig-out/docs/index.htmlzig build docs -Dgenerate-imagesThis document is a broad overview. See implementation details for how to write and modify container widgets.
Online discussion happens in #gui-dev on the zig discord server: https://discord.gg/eJgXXTtVzA or in IRC (Libera) channel #dvui
Below is a screenshot of the demo window, whose source code can be found at src/Examples.zig.
-Daccesskit to zig buildAdd the dvui dependency:
zig fetch --save git+https://github.com/david-vanderson/dvui#main
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"));
If you are starting a new project, copy the app example.
DVUI Demo is a template project you can use as a starting point.
Important Tips:
For zls autocomplete to work on dvui's backend, you must import it directly. In build.zig:
mod.addImport("sdl-backend", dvui_dep.module("sdl3"));
Then in your code:
const SDLBackend = @import("sdl-backend");
if (dvui.button(@src(), "Ok", .{}, .{})) {
dialog.close();
}
Widgets are not stored between frames like in traditional gui toolkits (gtk, win32, cocoa). dvui.button() processes input events, draws the button on the screen, and returns true if a button click happened this frame.
For an intro to immediate mode guis, see: https://github.com/ocornut/imgui/wiki#about-the-imgui-paradigm
// 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 = @floatFromInt(color.r);
var green: f32 = @floatFromInt(color.g);
var blue: f32 = @floatFromInt(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 = @intFromFloat(red);
color.g = @intFromFloat(green);
color.b = @intFromFloat(blue);
}
DVUI processes every input event, making it useable in low framerate situations. A button can receive a mouse-down event and a mouse-up event in the same frame and correctly report a click. A custom button could even report multiple clicks per frame. (the higher level dvui.button() function only reports 1 click per frame)
In the same frame these can all happen:
Because everything is in a single pass, this works in the normal case where widget A is run before widget B. It doesn't work in the opposite order (widget B receives a tab that moves focus to A) because A ran before it got focus.
This library can be used in 2 ways:
dvui.floatingWindow() callsdvui.Window.addEvent... functions return false if event won't be handled by dvui (main application should handle it)dvui.Window.cursorRequested() to dvui.Window.cursorRequestedFloating() which returns null if the mouse cursor should be set by the main applicationFloating windows and popups are handled by deferring their rendering so that they render properly on top of windows below them. Rendering of all floating windows and popups happens during dvui.Window.end().
If your app is running at a fixed framerate, use dvui.Window.begin() and dvui.Window.end() which handle bookkeeping and rendering.
If you want dvui to handle the mainloop for you, use dvui.App.
If you want to only render frames when needed, add dvui.Window.beginWait() at the start and dvui.Window.waitTime() at the end. These cooperate to sleep the right amount and render frames when:
dvui.refresh(null, ...) (if your code knows you need a frame after the current one)dvui.refresh(window, ...) which in turn calls backend.refresh()dvui.Window.waitTime() also accepts a max fps parameter which will ensure the framerate stays below the given value.
dvui.Window.beginWait() and dvui.Window.waitTime() maintain an internal estimate of how much time is spent outside of the rendering code. This is used in the calculation for how long to sleep for the next frame.
The estimate is visible in the demo window Animations > Clock > "Estimate of frame overhead". The estimate is only updated on frames caused by a timer expiring (like the clock example), and it starts at 1ms.
The easiest way to use widgets is through the high-level functions that create and install them:
{
var box = dvui.box(@src(), .{}, .{.expand = .both});
defer box.deinit();
// widgets run here will be children of box
}
These functions allocate memory for the widget onto an internal arena allocator that is flushed each frame.
Instead you can allocate the widget on the stack using the lower-level functions:
{
var box = BoxWidget.init(@src(), .{}, .{.expand = .both});
// box now has an id, can look up animations/timers
box.install();
// 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
}
The lower-level functions give a lot more customization options including animations, intercepting events, and drawing differently.
Start with the high-level functions, and when needed, copy the body of the high-level function and customize from there.
The primary layout mechanism is nesting widgets. DVUI keeps track of the current parent widget. When a widget runs, it is a child of the current parent. A widget may then make itself the current parent, and reset back to the previous parent when it runs deinit().
The parent widget decides what rectangle of the screen to assign to each child, unless the child passes .rect = in their dvui.Options.
Usually you want each part of a gui to either be packed tightly (take up only min size), or expand to take the available space. The choice might be different for vertical vs. horizontal.
When a child widget is laid out (sized and positioned), it sends 2 pieces of info to the parent:
If parent is not expanded, the intent is to pack as tightly as possible, so it will give all children only their min size.
If parent has more space than the children need, it will lay them out using the hints:
See implementation details for more information.
Each widget has the following options that can be changed through the Options struct when creating the widget:
Each widget has its own default options. These can be changed directly:
dvui.ButtonWidget.defaults.background = false;
Themes can be changed between frames or even within a frame. The theme controls the fonts and colors referenced by font_style and named colors.
if (theme_dark) {
win.theme = dvui.Theme.builtin.adwaita_dark;
} else {
win.theme = dvui.Theme.builtin.adwaita_light;
}
The theme's color_accent is also used to show keyboard focus.
The default theme will attempt to follow the system dark or light mode, or it can be set in the Window init options or by setting the Window.theme field directly. See the app and standalone examples for how to set the default theme.
DVUI has varying support for different kinds of accessibility infrastructure. Currently we have:
Keyboard Navigation
Language Support
Language Input
High Contrast Themes
Screen Reading and Alternate Input
-Daccesskit to zig buildOptions.role and Options.label