dvui

repository·main·Indexed 23 days ago

https://github.com/david-vanderson/dvui

An immediate-mode GUI toolkit implemented in Zig, designed for high performance and flexibility. It features a single-pass layout system and supports the creation of full applications or debugging windows. The library includes AccessKit integration for accessibility across Linux, Windows, and MacOS, and provides support for various backends including SDL2, SDL3, and Raylib.

Tokens
9.3K
Snippets
14
Records
48
Agent score
76%

What's inside dvui

  1. OpenDyslexic Font Overview and Styles

    main

    OpenDyslexic is an open-source typeface (licensed under SIL-OFL) designed to assist with dyslexia symptoms by using unique letter shapes, bolder letter bottoms to communicate orientation, and wider spacing for easier tracking.

    Available styles include:

    • OpenDyslexic: Regular, Bold, Italic, and BoldItalic.
    • OpenDyslexic Mono
    • OpenDyslexic Rounded: Designed with intentionally lower contrast to assist with glare or blindness.
  2. How parent-child communication and layout work

    main

    DVUI uses a parent-child hierarchy where each widget maintains a pointer to its parent, eventually leading back to a dvui.Window.

    Layout Mechanism

    Layout is a single-pass process. A widget asks for a rectangle from its parent during init(), but it can only know its own minSize during deinit(). To bridge this, widgets use the minSize calculated from the previous frame.

    Communication Methods

    • Child to Parent (Initialization):
      • parentGet(): Get the parent's interface.
      • parent.extendId(): Create a unique ID.
      • parent.rectFor(): Request a Rect from the parent based on the child's last known minSize.
      • parentSet(): Set the current widget as the new parent.
    • Child to Parent (Deinitialization):
      • parent.minSizeForChild(): Report the current frame's minSize to the parent so it can calculate its own size for the next frame.
      • parentReset(): Restore the previous parent.

    Opting Out of Layout

    If you set Options.rect, the widget specifies its own position and size in parent coordinates. In this case, the widget does not call parent.rectFor() or parent.minSizeForChild(), making it invisible to the parent's automatic layout calculations.

  3. Manage widget layout and sizing

    main

    Widgets receive their position and size from their parent. To influence layout, a widget communicates the following properties to its parent:

    • min_size: The minimum size requested (includes content, padding, border, and margin). This is typically the maximum of Options.min_size_content (plus padding/border/margin) and the min_size calculated from the previous frame, capped by Options.max_size_content.
    • expand: A flag indicating if the widget should take up all available space (can be horizontal, vertical, or both).
    • gravity_x, gravity_y: Used to position a non-expanded widget within a larger rectangle.
    • rect: Directly specifies the position in the parent. This is rarely used but useful for optimizing long scrollable lists by skipping non-visible widgets.
  4. AccessKit Multithreading Requirements

    main

    AccessKit callbacks (such as initialTreeUpdate, frameTreeUpdate, and actionHandler) may be called on a non-GUI thread. You must assume these callbacks arrive on a separate thread depending on the operating system.

    To prevent race conditions:

    • Protect any direct access to currentWindow().accesskit variables using currentWindow().accesskit.mutex.
    • accesskit.nodes is safe to access via .get only when called from the GUI thread.
  5. Understand DVUI Event Handling

    main

    DVUI provides a time-ordered array of all events since the last frame via events(). Instead of the system routing events to specific widgets, widgets are responsible for inspecting the event array and deciding which events to process using eventMatch().

    Mouse Events

    • Positioning: Mouse events include a screen position. A widget typically processes a mouse event if the event's screen position is within the widget's screen rectangle.
    • Overlapping Widgets: Multiple widgets may overlap (e.g., a label inside a button).
      • Process Before Children: A widget can intercept events before its children. This is useful for elements like a FloatingWindowWidget resize handle, where the handle should capture the event even if a button is underneath it.
      • Process After Children: A widget can wait to see if children process the event first. For example, a ScrollContainerWidget only processes mouse-wheel events if no child widget consumed them.
    • Capture: Widgets can capture the mouse to receive all subsequent mouse events until the capture is released.

    Keyboard Events

    • Keyboard events are associated with the id of the last focused widget and the id of the last focused subwindow.

    Special Events

    • .focus: A mouse event created by DVUI immediately before a user action (like left-mouse-down). This allows widgets to separate the logic of focusing a widget from the logic of interacting with it.
    • .position: A mouse event created every frame after all other events. It represents the final mouse position for the current frame and is used for updating cursors or hover states.
  6. Inject a custom raylib-zig version into DVUI

    main

    To use a specific version of raylib-zig instead of the one bundled with DVUI, you must configure the dvui dependency in your build.zig and manually inject your raylib and raygui modules into the DVUI backend module. This prevents dependency conflicts between your project and the library.

    Follow these steps in your build.zig:

    1. Load the dvui dependency with the .raylib_zig backend.
    2. Access the raylib_zig module from the dependency.
    3. Use addImport to map your project's raylib and raygui dependencies to the backend module.
    4. Import the dvui_raylib module and the modified backend module into your executable.
    // In build.zig while loading the dvui dependency
    const dvui_dep = b.dependency("dvui", .{
        .target = target,
        .optimize = optimize,
        .backend = .raylib_zig,
    });
    
    // Inject your own raylib dependencies to prevent conflicts
    const backend_mod = dvui_dep.module("raylib_zig");
    backend_mod.addImport("raylib", raylib); // from your raylib dependency
    backend_mod.addImport("raygui", raygui);
    
    exe.root_module.addImport("dvui", dvui_dep.module("dvui_raylib"));
    exe.root_module.addImport("backend", backend_mod);
  7. Add accessibility labels to widgets

    main

    To make your UI accessible to screen readers, you must provide semantic details that DVUI cannot infer automatically.

    Key tasks include:

    • Images and Icons: Always provide a descriptive label using the .label option.
    • Entry Widgets: Add labels to textboxes and groups of widgets.
    • Grouping: When using radio buttons in a group, supply a label to the group.
    • Role Management: Use .role = .none to prevent screen readers from reading decorative elements (like dropdown arrows) and set appropriate roles for custom widgets to ensure they are recognized.
        const image_source: dvui.ImageSource = .{ .imageFile = .{ .bytes = zig_favicon, .name = "zig favicon" } };
        const imgsize = dvui.imageSize(image_source) catch dvui.Size.all(50);
        _ = dvui.image(@src(), .{ .source = image_source }, .{
            .gravity_y = 0.5,
            .min_size_content = .{ .w = imgsize.w + icon_image_size_extra, .h = imgsize.h + icon_image_size_extra },
            .rotation = icon_image_rotation,
            .label = .{ .text = image_source.imageFile.name },
        });
  8. Install and use Aleo Variable Fonts

    main

    Aleo is available as both variable fonts and static fonts.

    Variable Fonts

    If your application supports variable fonts, use the following files to access all styles (including intermediate weights) via the wght axis:

    • Aleo/Aleo-VariableFont_wght.ttf
    • Aleo/Aleo-Italic-VariableFont_wght.ttf

    Static Fonts

    If your application does not support variable fonts, use the individual static files located in the Aleo/static/ directory:

    • Aleo/Aleo-Thin.ttf
    • Aleo/Aleo-ExtraLight.ttf
    • Aleo/Aleo-Light.ttf
    • Aleo/Aleo-Regular.ttf
    • Aleo/Aleo-Medium.ttf
    • Aleo/Aleo-SemiBold.ttf
    • Aleo/Aleo-Bold.ttf
    • Aleo/Aleo-ExtraBold.ttf
    • Aleo/Aleo-Black.ttf
    • Aleo/static/Aleo-ThinItalic.ttf
    • Aleo/static/Aleo-ExtraLightItalic.ttf
    • Aleo/static/Aleo-LightItalic.ttf
    • Aleo/static/Aleo-Italic.ttf
    • Aleo/static/Aleo-MediumItalic.ttf
    • Aleo/static/Aleo-SemiBoldItalic.ttf
    • Aleo/static/Aleo-BoldItalic.ttf
    • Aleo/static/Aleo-ExtraBoldItalic.ttf
    • Aleo/static/Aleo-BlackItalic.ttf

    Setup Steps

    1. Install the desired font files onto your system.
    2. Use your application's font picker to select the Aleo font family and choose from the available styles.
  9. Access raylib in your project when using a custom version

    main

    When you have injected a custom raylib-zig version using the pattern described in the setup guide, you can access the raylib API in two ways:

    1. Directly: Use @import("raylib") if you provided your own dependency.
    2. Via the DVUI backend: Import the backend module and access backend.raylib to ensure you are using the same instance the library is using.
    // if custom raylib dependency is supplied
    const raylib_direct = @import("raylib");
    
    // via the backend module
    const backend = @import("backend");
    const raylib_dvui = backend.raylib;
  10. Test accessibility on Windows

    main

    Use the built-in Windows Narrator (ctrl-win-enter) to perform manual testing:

    1. Initial Focus: Ensure a widget is highlighted when the window first opens. If the whole window stays highlighted, use dvui.focusWidget() to set a default focus.
    2. Screen Reading: Press caps-r to read the screen. Verify that controls are read correctly and that decorative elements are skipped (use .role = .none if necessary).
    3. Label Association: Check that fields are labeled correctly. Use .label = .for or .label = .by in widget options to associate labels with their target widgets.
    4. Keyboard Navigation: Tab through widgets to ensure .tab_index is sensible and that the reader highlights each focusable widget.

    For automated action testing, use Accessibility Insights and inspect the values and actions available for each widget in the bottom right pane.