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 theUiNodes 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 byUiTree::calculate_layout.Rect: A rectangle in space, represented withf32coordinates.Alignment: An alignment primarily used for determining node placement.
§Layout process
Layout runs in two passes, when UiTree::calculate_layout is called:
-
Bottom-up: minimum size. Children are visited before their parent. Each node computes its minimum size based on its children through
calculate_min_sizeand stores it in itsNodeCache::min_size. -
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 restrictedRectto do the same for its children. TheNodeCache::rectfield is populated with the resultingRects.
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:
-
Overlapintentionally allows the boxes of children to overlap each other, as long as they stay inside. -
Clipallows a box to be bigger than that of its parent, but requires the box’s viewable area to be constrained to that of theClipby the renderer. -
Hiderallows 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:
-
Children must be accurately reported. Failure to report children will result in them not being updated during
UiTree::calculate_layoutor removed duringUiTree::remove_node. -
Minimum size must be correctly calculated. Under-representing the minimum size can and often will result in nodes overflowing into each other.
-
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 layuit::overlap::Overlap;
use thunderdome::Index;
let mut tree = UiTree::new(Overlap::new());
let mut spacer3 = Index::DANGLING;
let node_index = layuit::ui!(
&mut tree,
+|+ 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_root_mut()
.downcast_mut::<Overlap>()
.unwrap()
.add_child(node_index);
tree
.get_node_mut(spacer3)
.unwrap()
.downcast_mut::<Spacer>()
.unwrap()
.set_size((20.0, 20.0));
// Overlap (N/A, N/A) <- Tree root
// └─ HStack (Full, Full) = node_index
// ├─ Spacer (N/A, Begin)
// ├─ Minimum (N/A, Center)
// │ └─ Spacer (Center, Center)
// └─ Spacer (N/A, End) = spacer3§Provided nodes
HStack- Horizontal arrangementVStack- Vertical arrangementOverlap- Independent arrangement of childrenMargin- Adds padding to a childMinimum- Creates a minimum size constraint for precise controlSpacer- A leaf node with configurable empty spaceClip- 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.
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.
- 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§
- Node
Cache - Cached layout information for a node.
- Rect
- A rectangle in space, represented with
f32coordinates. - 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,Anchorrepresents shrinking only, and has noFullvariant.