Skip to main content

Crate layuit

Crate layuit 

Source
Expand description

§Layuit

A renderer-agnostic UI layout system. Layuit handles computing the size and position of various UiNodes in a UiTree. Layuit does not handle rendering, but provides tools for doing so.

Layuit provides several organizational nodes such as HStack and Margin, but allows users to create their own nodes.

§Core concepts

  • UiTree: Owns the UiNodes and layout information in an arena and handles computation and access.
  • UiNode: A trait implemented by all UI nodes, containing alignment and any number of children.
  • NodeCache: The cached layout information for a node, produced by UiTree::calculate_layout.
  • Rect: A rectangle in space, represented with f32 coordinates.
  • Alignment: An alignment primarily used for determining node placement.

§Layout process

Layout runs in two passes, when UiTree::calculate_layout is called:

  1. Bottom-up: minimum size. Children are visited before their parent. Each node computes its minimum size based on its children through calculate_min_size and stores it in its NodeCache::min_size.

  2. Top-down: rectangles. Starting from the root, each node computes the position and size of its immediate children through calculate_rects. Each child then uses its restricted Rect to do the same for its children. The NodeCache::rect field is populated with the resulting Rects.

The majority of the layout process can be thought of as drawing boxes on a sheet of paper. Boxes cannot normally cross, but are allowed to touch. Several nodes change that behavior:

  • Overlap intentionally allows the boxes of children to overlap each other, as long as they stay inside.

  • Clip allows a box to be bigger than that of its parent, but requires the box’s viewable area to be constrained to that of the Clip by the renderer.

  • Hider allows a child to be completely excluded from the layout process, but requires the renderer to ignore it.

§Caveats

The cache is stale before UiTree::calculate_layout is called, and becomes stale if children are added, removed, moved, or otherwise changed. The cache always produces valid results, but they may be out of date or set to 0.

Minimum size is a practice, not a requirement. When implementing custom nodes, be wary of ensuring each node’s minimum size is enforced. This can easily become a problem if the space required by the entire tree is smaller than the one provided to UiTree::calculate_layout.

§Implementing custom nodes

Custom nodes are essential to using Layuit. Without them, no meaningful UI can be rendered. However, it is important to ensure you follow the rules:

  1. Children must be accurately reported. Failure to report children will result in them not being updated during UiTree::calculate_layout or removed during UiTree::remove_node.

  2. Minimum size must be correctly calculated. Under-representing the minimum size can and often will result in nodes overflowing into each other.

  3. Rectangles must be properly assigned. Similar to #2, it is the responsibility of the parent node to ensure that each node get both enough space and not too much. Failing to do so will result in nodes overlapping.

Probably the most important custom node is the Label:

use layuit::{Alignment, NodeCache, Rect, UiTree, UiNode};

pub struct Label {
    text: String,
    cached_size: (f32, f32),

    align: (Alignment, Alignment),
}

/* Label methods and constructors... */

impl UiNode for Label {
    fn get_align(&self) -> (Alignment, Alignment) { self.align }
    fn get_align_mut(&mut self) -> (&mut Alignment, &mut Alignment) {
        (&mut self.align.0, &mut self.align.1)
    }

    fn calculate_min_size(&self, _tree: &UiTree) -> (f32, f32) {
        self.cached_size
    }

    // calculate_rects and get_children are omitted for leaf nodes
}

However, you are not restricted to just leaf nodes. You can create containers.

§Using the ui! macro

The ui! macro is a convenience for creating trees with a simple syntax, avoiding rewriting .with_align((...)) and .with_child(...) in every node, for every child. It does come with the limitation that you cannot create your entire tree this way; your root node must be created manually.

Additionally, you can create variables outside the macro and assign the indices of nodes created by the macro to them.

use layuit::UiTree;
use layuit::padding::{Spacer, Minimum};
use layuit::stacks::HStack;
use layuit::proportion::HSplit;
use thunderdome::Index;

let spacer3;
let (node_index, mut tree) = layuit::ui!(
    %%,
    +|+ HStack::new() => [
        -|< Spacer::sized((10.0, 10.0)),
        -|- Minimum::new().with_min((20.0, 20.0)) => [
            -|- Spacer::sized((10.0, 10.0))
        ],
        spacer3 = -|> Spacer::sized((10.0, 10.0))
    ]
);

tree
    .get_node_mut(spacer3)
    .unwrap()
    .downcast_mut::<Spacer>()
    .unwrap()
    .set_size((20.0, 20.0));


// HStack (Full, Full) = node_index / tree root
// ├─ Spacer (N/A, Begin)
// ├─ Minimum (N/A, Center)
// │  └─ Spacer (Center, Center)
// └─ Spacer (N/A, End) = spacer3

§Provided nodes

  • HStack - Horizontal arrangement
  • VStack - Vertical arrangement
  • Overlap - Independent arrangement of children
  • Margin - Adds padding to a child
  • Minimum - Creates a minimum size constraint for precise control
  • Spacer - A leaf node with configurable empty space
  • Clip - Allows a child to outgrow the node with the assumption that the renderer will clip it, and enables a scroll offset to be applied if the child is larger.
  • Hider - Allows a child’s visibility to be controlled. An invisible node has no minimum size and should not be attempted to be rendered.
  • Selector - Selects a single child node to be visible at a time.
  • AspectRatio - Maintains a horizontal:vertical ratio.
  • HSplit - Horizontal split between two children.
  • VSplit - Vertical split between two children.
  • Percent - Maintains a percentage of space for a child.
  • HEqual - Horizontal arrangement with each child getting equal space.
  • VEqual - Vertical arrangement with each child getting equal space.
  • Grid - 2D grid of children.
  • Clamp - Constrains a child to a maximum size.
  • HSplit3 - Horizontal split between two children, with a third child separating the two.
  • VSplit3 - Vertical split between two children, with a third child separating the two.

Modules§

clip
Container nodes that mark their children as visually bound to their parent.
grid
Containers that distribute equal space to children.
limit
Containers that create upper bounds for nodes.
overlap
Containers of independent children
padding
Size constraint nodes.
prelude
Re-exports the most common and uncommonly-named types.
proportion
Containers that use ratios, but maintain minimum size requirements.
split3
3-way splits
stacks
Horizontal and vertical stacks of UI nodes.
visibility
Container nodes that control the visibility of their child.

Macros§

ui
A macro for making the process of creating a UI subtree easier.

Structs§

CalculateLayoutConfig
NodeCache
Cached layout information for a node.
NodeIndex
Index type for Arena that has a generation attached to it.
PartialTree
A UiTree that does not have a full tree structure assigned.
Rect
A rectangle in space, represented with f32 coordinates.
UiTree
A tree of UI nodes, stored as an arena.

Enums§

Alignment
An alignment of any sort, for example determining node placement.
Anchor
Horizontal or vertical anchoring. While very similar to Alignment, Anchor represents shrinking only, and has no Full variant.

Traits§

UiNode
Basic functionality for a UI node.
UiWalker
A walker for a UI tree.