diff --git a/src/flexbox.rs b/src/flexbox.rs
index 0f7a5a4fb..14d196b93 100644
--- a/src/flexbox.rs
+++ b/src/flexbox.rs
@@ -1,3 +1,6 @@
+//! Computes the [flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) layout algorithm on a [`Forest`](crate::forest::Forest) according to the [spec](https://www.w3.org/TR/css-flexbox-1/)
+//!
+//! Note that some minor steps appear to be missing: see https://github.com/DioxusLabs/sprawl/issues for more information.
use core::f32;
use crate::flexbox::Number::{Defined, Undefined};
@@ -11,65 +14,104 @@ use crate::style::{AlignContent, AlignSelf, Dimension, Display, FlexWrap, Justif
use crate::style::{FlexDirection, Style};
use crate::sys::{abs, round, ChildrenVec, Vec};
+/// The computed results of a [`flexbox`](crate::flexbox) calculation
#[derive(Debug, Clone)]
pub struct ComputeResult {
+ /// The computed size
pub size: Size<f32>,
}
+/// The intermediate results of a flexbox calculation for a single item
struct FlexItem {
+ /// The identifier for the associated [`Node`](crate::node::Node)
node: NodeId,
+ /// The base size of this item
size: Size<Number>,
+ /// The minimum allowable size of this item
min_size: Size<Number>,
+ /// The maximum allowable size of this item
max_size: Size<Number>,
+ /// The final offset of this item
position: Rect<Number>,
+ /// The margin of this item
margin: Rect<f32>,
+ /// The padding of this item
padding: Rect<f32>,
+ /// The border of this item
border: Rect<f32>,
+ /// The default size of this item
flex_basis: f32,
+ /// The default size of this item, minus padding and border
inner_flex_basis: f32,
+ /// The amount by which this item has deviated from its target size
violation: f32,
+ /// Is the size of this item locked
frozen: bool,
+ /// The proposed inner size of this item
hypothetical_inner_size: Size<f32>,
+ /// The proposed outer size of this item
hypothetical_outer_size: Size<f32>,
+ /// The size that this item wants to be
target_size: Size<f32>,
+ /// The size that this item wants to be, plus any padding and border
outer_target_size: Size<f32>,
+ /// The position of the bottom edge of this item
baseline: f32,
- // temporary values for holding offset in the main / cross direction.
- // offset is the relative position from the item's natural flow position based on
- // relative position values, alignment, and justification. Does not include margin/padding/border.
+ /// A temporary values for holding offset in the main / cross direction.
+
+ /// A temporary value for the main offset
+ ///
+ /// Offset is the relative position from the item's natural flow position based on
+ /// relative position values, alignment, and justification. Does not include margin/padding/border.
offset_main: f32,
+ /// A temporary value for the cross offset
offset_cross: f32,
}
+/// A line of [`FlexItem`] used for intermediate computation
struct FlexLine<'a> {
+ /// The slice of items to
items: &'a mut [FlexItem],
+ /// The dimensions of the cross-axis
cross_size: f32,
+ /// The relative offset of the cross-axis
offset_cross: f32,
}
-/// Constant values that can be reused for the flexbox algorithm.
+/// Values that can be cached during the flexbox algorithm
struct AlgoConstants {
+ /// The direction of the current segment being layed out
dir: FlexDirection,
+ /// Is this segment a row
is_row: bool,
+ /// Is this segment a column
is_column: bool,
+ /// Is the wrap direction inverted
is_wrap_reverse: bool,
+ /// The margin of this section
margin: Rect<f32>,
+ /// The border of this section
border: Rect<f32>,
+ /// The padding of this section
padding_border: Rect<f32>,
+ /// The size of the internal node
node_inner_size: Size<Number>,
+ /// The size of the surrounding container
container_size: Size<f32>,
+ /// The size of the internal container
inner_container_size: Size<f32>,
}
impl Forest {
+ /// Computes the layout of this [`Forest`] according to the flexbox algorithm
pub(crate) fn compute(&mut self, root: NodeId, size: Size<Number>) {
let style = self.nodes[root].style;
let has_root_min_max = style.min_size.width.is_defined()
@@ -78,9 +120,9 @@ impl Forest {
|| style.max_size.height.is_defined();
let result = if has_root_min_max {
- let first_pass = self.compute_internal(root, style.size.resolve(size), size, false, true);
+ let first_pass = self.compute_preliminary(root, style.size.resolve(size), size, false, true);
- self.compute_internal(
+ self.compute_preliminary(
root,
Size {
width: first_pass
@@ -101,7 +143,7 @@ impl Forest {
true,
)
} else {
- self.compute_internal(root, style.size.resolve(size), size, true, true)
+ self.compute_preliminary(root, style.size.resolve(size), size, true, true)
};
self.nodes[root].layout = Layout { order: 0, size: result.size, location: Point::zero() };
@@ -109,6 +151,7 @@ impl Forest {
Self::round_layout(&mut self.nodes, &self.children, root, 0.0, 0.0);
}
+ /// Rounds the calculated [`NodeData`] according to the spec
fn round_layout(nodes: &mut [NodeData], children: &[ChildrenVec<NodeId>], root: NodeId, abs_x: f32, abs_y: f32) {
let layout = &mut nodes[root].layout;
let abs_x = abs_x + layout.location.x;
@@ -125,6 +168,7 @@ impl Forest {
}
}
+ /// Saves intermediate results to a [`Cache`]
fn cache(&mut self, node: NodeId, main_size: bool) -> &mut Option<Cache> {
if main_size {
&mut self.nodes[node].main_size_layout_cache
@@ -190,8 +234,8 @@ impl Forest {
};
let node_inner_size = Size {
- width: node_size.width - padding_border.horizontal(),
- height: node_size.height - padding_border.vertical(),
+ width: node_size.width - padding_border.horizontal_axis_sum(),
+ height: node_size.height - padding_border.vertical_axis_sum(),
};
let container_size = Size::zero();
@@ -269,10 +313,10 @@ impl Forest {
constants: &AlgoConstants,
) -> Size<Number> {
Size {
- width: node_size.width.or_else(parent_size.width - constants.margin.horizontal())
- - constants.padding_border.horizontal(),
- height: node_size.height.or_else(parent_size.height - constants.margin.vertical())
- - constants.padding_border.vertical(),
+ width: node_size.width.or_else(parent_size.width - constants.margin.horizontal_axis_sum())
+ - constants.padding_border.horizontal_axis_sum(),
+ height: node_size.height.or_else(parent_size.height - constants.margin.vertical_axis_sum())
+ - constants.padding_border.vertical_axis_sum(),
}
}
@@ -380,7 +424,7 @@ impl Forest {
};
child.flex_basis = self
- .compute_internal(
+ .compute_preliminary(
child.node,
Size {
width: width.maybe_max(child.min_size.width).maybe_min(child.max_size.width),
@@ -400,15 +444,16 @@ impl Forest {
// used min and max main sizes (and flooring the content box size at zero).
for child in flex_items {
- child.inner_flex_basis =
- child.flex_basis - child.padding.main(constants.dir) - child.border.main(constants.dir);
+ child.inner_flex_basis = child.flex_basis
+ - child.padding.main_axis_sum(constants.dir)
+ - child.border.main_axis_sum(constants.dir);
// TODO - not really spec abiding but needs to be done somewhere. probably somewhere else though.
// The following logic was developed not from the spec but by trail and error looking into how
// webkit handled various scenarios. Can probably be solved better by passing in
// min-content max-content constraints from the top
let min_main = self
- .compute_internal(child.node, Size::undefined(), available_space, false, false)
+ .compute_preliminary(child.node, Size::undefined(), available_space, false, false)
.size
.main(constants.dir)
.maybe_max(child.min_size.main(constants.dir))
@@ -422,7 +467,7 @@ impl Forest {
child.hypothetical_outer_size.set_main(
constants.dir,
- child.hypothetical_inner_size.main(constants.dir) + child.margin.main(constants.dir),
+ child.hypothetical_inner_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
);
}
}
@@ -517,7 +562,7 @@ impl Forest {
if constants.node_inner_size.main(constants.dir).is_undefined() && constants.is_row {
child.target_size.set_main(
constants.dir,
- self.compute_internal(
+ self.compute_preliminary(
child.node,
Size {
width: child.size.width.maybe_max(child.min_size.width).maybe_min(child.max_size.width),
@@ -539,9 +584,10 @@ impl Forest {
// TODO this should really only be set inside the if-statement below but
// that causes the target_main_size to never be set for some items
- child
- .outer_target_size
- .set_main(constants.dir, child.target_size.main(constants.dir) + child.margin.main(constants.dir));
+ child.outer_target_size.set_main(
+ constants.dir,
+ child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
+ );
let child_style = &self.nodes[child.node].style;
if (child_style.flex_grow == 0.0 && child_style.flex_shrink == 0.0)
@@ -560,7 +606,7 @@ impl Forest {
.items
.iter()
.map(|child| {
- child.margin.main(constants.dir)
+ child.margin.main_axis_sum(constants.dir)
+ if child.frozen { child.target_size.main(constants.dir) } else { child.flex_basis }
})
.sum();
@@ -587,7 +633,7 @@ impl Forest {
.items
.iter()
.map(|child| {
- child.margin.main(constants.dir)
+ child.margin.main_axis_sum(constants.dir)
+ if child.frozen { child.target_size.main(constants.dir) } else { child.flex_basis }
})
.sum();
@@ -668,7 +714,7 @@ impl Forest {
// min-content max-content constraints from the top. Need to figure out correct thing to do here as
// just piling on more conditionals.
let min_main = if constants.is_row && self.nodes[child.node].measure.is_none() {
- self.compute_internal(child.node, Size::undefined(), available_space, false, false)
+ self.compute_preliminary(child.node, Size::undefined(), available_space, false, false)
.size
.width
.maybe_min(child.size.width)
@@ -682,9 +728,10 @@ impl Forest {
let clamped = child.target_size.main(constants.dir).maybe_min(max_main).maybe_max(min_main).max(0.0);
child.violation = clamped - child.target_size.main(constants.dir);
child.target_size.set_main(constants.dir, clamped);
- child
- .outer_target_size
- .set_main(constants.dir, child.target_size.main(constants.dir) + child.margin.main(constants.dir));
+ child.outer_target_size.set_main(
+ constants.dir,
+ child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
+ );
acc + child.violation
});
@@ -732,7 +779,7 @@ impl Forest {
child.hypothetical_inner_size.set_cross(
constants.dir,
- self.compute_internal(
+ self.compute_preliminary(
child.node,
Size {
width: if constants.is_row { child.target_size.width.into() } else { child_cross },
@@ -761,7 +808,7 @@ impl Forest {
child.hypothetical_outer_size.set_cross(
constants.dir,
- child.hypothetical_inner_size.cross(constants.dir) + child.margin.cross(constants.dir),
+ child.hypothetical_inner_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
);
}
}
@@ -775,6 +822,7 @@ impl Forest {
flex_lines: &mut [FlexLine],
constants: &AlgoConstants,
) {
+ /// Recursively caluculates the baseline for children
fn calc_baseline(db: &Forest, node: NodeId, layout: &Layout) -> f32 {
if db.children[node].is_empty() {
layout.size.height
@@ -786,7 +834,7 @@ impl Forest {
for line in flex_lines {
for child in line.items.iter_mut() {
- let result = self.compute_internal(
+ let result = self.compute_preliminary(
child.node,
Size {
width: if constants.is_row {
@@ -855,7 +903,7 @@ impl Forest {
) {
if flex_lines.len() == 1 && node_size.cross(constants.dir).is_defined() {
flex_lines[0].cross_size =
- (node_size.cross(constants.dir) - constants.padding_border.cross(constants.dir)).or_else(0.0);
+ (node_size.cross(constants.dir) - constants.padding_border.cross_axis_sum(constants.dir)).or_else(0.0);
} else {
for line in flex_lines.iter_mut() {
// 1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose
@@ -910,7 +958,7 @@ impl Forest {
{
let total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum();
let inner_cross =
- (node_size.cross(constants.dir) - constants.padding_border.cross(constants.dir)).or_else(0.0);
+ (node_size.cross(constants.dir) - constants.padding_border.cross_axis_sum(constants.dir)).or_else(0.0);
if total_cross < inner_cross {
let remaining = inner_cross - total_cross;
@@ -945,7 +993,7 @@ impl Forest {
&& child_style.cross_margin_end(constants.dir) != Dimension::Auto
&& child_style.cross_size(constants.dir) == Dimension::Auto
{
- (line_cross_size - child.margin.cross(constants.dir))
+ (line_cross_size - child.margin.cross_axis_sum(constants.dir))
.maybe_max(child.min_size.cross(constants.dir))
.maybe_min(child.max_size.cross(constants.dir))
} else {
@@ -955,7 +1003,7 @@ impl Forest {
child.outer_target_size.set_cross(
constants.dir,
- child.target_size.cross(constants.dir) + child.margin.cross(constants.dir),
+ child.target_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
);
}
}
@@ -1209,12 +1257,14 @@ impl Forest {
constants.container_size.set_cross(
constants.dir,
- node_size.cross(constants.dir).or_else(total_cross_size + constants.padding_border.cross(constants.dir)),
+ node_size
+ .cross(constants.dir)
+ .or_else(total_cross_size + constants.padding_border.cross_axis_sum(constants.dir)),
);
constants.inner_container_size.set_cross(
constants.dir,
- constants.container_size.cross(constants.dir) - constants.padding_border.cross(constants.dir),
+ constants.container_size.cross(constants.dir) - constants.padding_border.cross_axis_sum(constants.dir),
);
total_cross_size
@@ -1296,7 +1346,7 @@ impl Forest {
let line_offset_cross = line.offset_cross;
let layout_item = |child: &mut FlexItem| {
- let result = self.compute_internal(
+ let result = self.compute_preliminary(
child.node,
child.target_size.map(|s| s.into()),
constants.container_size.map(|s| s.into()),
@@ -1327,7 +1377,7 @@ impl Forest {
};
total_offset_main +=
- child.offset_main + child.margin.main(constants.dir) + result.size.main(constants.dir);
+ child.offset_main + child.margin.main_axis_sum(constants.dir) + result.size.main(constants.dir);
};
if constants.dir.is_reverse() {
@@ -1399,7 +1449,7 @@ impl Forest {
Undefined
});
- let result = self.compute_internal(
+ let result = self.compute_preliminary(
child,
Size { width, height },
Size { width: container_width, height: container_height },
@@ -1493,7 +1543,8 @@ impl Forest {
}
}
- fn compute_internal(
+ /// Compute a preliminary size for an item
+ fn compute_preliminary(
&mut self,
node: NodeId,
node_size: Size<Number>,
@@ -1530,8 +1581,8 @@ impl Forest {
return ComputeResult {
size: Size {
- width: node_size.width.or_else(0.0) + constants.padding_border.horizontal(),
- height: node_size.height.or_else(0.0) + constants.padding_border.vertical(),
+ width: node_size.width.or_else(0.0) + constants.padding_border.horizontal_axis_sum(),
+ height: node_size.height.or_else(0.0) + constants.padding_border.vertical_axis_sum(),
},
};
}
@@ -1576,7 +1627,7 @@ impl Forest {
acc.max(length)
});
- let size = longest_line + constants.padding_border.main(constants.dir);
+ let size = longest_line + constants.padding_border.main_axis_sum(constants.dir);
match available_space.main(constants.dir) {
Defined(val) if flex_lines.len() > 1 && size < val => val,
_ => size,
@@ -1586,7 +1637,7 @@ impl Forest {
constants.inner_container_size.set_main(
constants.dir,
- constants.container_size.main(constants.dir) - constants.padding_border.main(constants.dir),
+ constants.container_size.main(constants.dir) - constants.padding_border.main_axis_sum(constants.dir),
);
// 9.4. Cross Size Determination
@@ -1657,6 +1708,9 @@ impl Forest {
// Before returning we perform absolute layout on all absolutely positioned children
self.perform_absolute_layout_on_absolute_children(node, &constants);
+ /// Lay out all hidden nodes recursively
+ ///
+ /// Each hidden node has zero size and is placed at the origin
fn hidden_layout(nodes: &mut [NodeData], children: &[ChildrenVec<NodeId>], node: NodeId, order: u32) {
nodes[node].layout = Layout { order, size: Size::zero(), location: Point::zero() };
diff --git a/src/forest.rs b/src/forest.rs
index f2aa6ebe3..6d983cf08 100644
--- a/src/forest.rs
+++ b/src/forest.rs
@@ -1,4 +1,4 @@
-//! Forest - ECS like datastructure for storing node trees.
+//! Forest - a struct-of-arrays data structure for storing node trees.
//!
//! Backing datastructure for `Sprawl` structs.
use crate::geometry::Size;
@@ -8,16 +8,26 @@ use crate::number::Number;
use crate::style::Style;
use crate::sys::{new_vec_with_capacity, ChildrenVec, ParentsVec, Vec};
+/// Layout information for a given [`Node`](crate::node::Node)
+///
+/// Stored in a [`Forest`].
pub(crate) struct NodeData {
+ /// The layout strategy used by this node
pub(crate) style: Style,
+ /// The mapping from the Size<Number> (in units) to Size<f32> (in points) for this node
pub(crate) measure: Option<MeasureFunc>,
+ /// The results of the layout computation
pub(crate) layout: Layout,
+ /// The primary cached results of the layout computation
pub(crate) main_size_layout_cache: Option<Cache>,
+ /// Secondary cached results of the layout computation
pub(crate) other_layout_cache: Option<Cache>,
+ /// Does this node's layout need to be recomputed?
pub(crate) is_dirty: bool,
}
impl NodeData {
+ /// Create the data for a new leaf node
#[must_use]
fn new_leaf(style: Style, measure: MeasureFunc) -> Self {
Self {
@@ -30,6 +40,8 @@ impl NodeData {
}
}
+ /// Create the data for a new node
+ // TODO: why is this different from new_leaf?
#[must_use]
fn new(style: Style) -> Self {
Self {
@@ -53,15 +65,24 @@ impl NodeData {
}
}
+/// A collection of UI layout trees used to store [`NodeData`] associated with specific [`Nodes`](crate::node::Node)
pub(crate) struct Forest {
+ /// The [`NodeData`] for each node stored in this forest
pub(crate) nodes: Vec<NodeData>,
+ /// The children of each node
+ ///
+ /// The indexes in the outer vector correspond to the position of the parent [`NodeData`]
pub(crate) children: Vec<ChildrenVec<NodeId>>,
+ /// The parents of each node
+ ///
+ /// The indexes in the outer vector correspond to the position of the child [`NodeData`]
pub(crate) parents: Vec<ParentsVec<NodeId>>,
}
impl Forest {
+ /// Creates a new [`Forest`] that can store up to `capacity` [`Nodes`](crate::node::Node) before needing to be expanded
#[must_use]
- pub fn with_capacity(capacity: usize) -> Self {
+ pub(crate) fn with_capacity(capacity: usize) -> Self {
Self {
nodes: new_vec_with_capacity(capacity),
children: new_vec_with_capacity(capacity),
@@ -69,7 +90,8 @@ impl Forest {
}
}
- pub fn new_leaf(&mut self, style: Style, measure: MeasureFunc) -> NodeId {
+ /// Adds a new unattached leaf node to the forest, and returns the [`NodeId`] of the new node
+ pub(crate) fn new_leaf(&mut self, style: Style, measure: MeasureFunc) -> NodeId {
let id = self.nodes.len();
self.nodes.push(NodeData::new_leaf(style, measure));
self.children.push(new_vec_with_capacity(0));
@@ -77,7 +99,8 @@ impl Forest {
id
}
- pub fn new_node(&mut self, style: Style, children: ChildrenVec<NodeId>) -> NodeId {
+ /// Adds a new unparented node to the forest with the associated children attached, and returns the [`NodeId`] of the new node
+ pub(crate) fn new_node(&mut self, style: Style, children: ChildrenVec<NodeId>) -> NodeId {
let id = self.nodes.len();
for child in &children {
self.parents[*child].push(id);
@@ -88,20 +111,27 @@ impl Forest {
id
}
- pub fn add_child(&mut self, node: NodeId, child: NodeId) {
- self.parents[child].push(node);
- self.children[node].push(child);
- self.mark_dirty(node)
+ /// Adds a `child` node to the `parent` node
+ pub(crate) fn add_child(&mut self, parent: NodeId, child: NodeId) {
+ self.parents[child].push(parent);
+ self.children[parent].push(child);
+ self.mark_dirty(parent)
}
- pub fn clear(&mut self) {
+ /// Removes all nodes and resets the data structure
+ ///
+ /// The capacity is retained.
+ pub(crate) fn clear(&mut self) {
self.nodes.clear();
self.children.clear();
self.parents.clear();
}
- /// Removes a node and swaps with the last node.
- pub fn swap_remove(&mut self, node: NodeId) -> Option<NodeId> {
+ /// Removes the specified `node`
+ ///
+ /// The last existing node is moved to its previous position, in order to ensure compactness.
+ /// Returns the previous [`NodeId`] of the moved node, if one was moved.
+ pub(crate) fn swap_remove(&mut self, node: NodeId) -> Option<NodeId> {
self.nodes.swap_remove(node);
// Now the last element is swapped in at index `node`.
@@ -169,21 +199,31 @@ impl Forest {
}
}
- pub fn remove_child(&mut self, node: NodeId, child: NodeId) -> NodeId {
- let index = self.children[node].iter().position(|n| *n == child).unwrap();
- self.remove_child_at_index(node, index)
+ /// Breaks the link between the `parent` node and the `child` node
+ ///
+ /// The `child`'s data is not removed.
+ pub(crate) fn remove_child(&mut self, parent: NodeId, child: NodeId) -> NodeId {
+ let index = self.children[parent].iter().position(|n| *n == child).unwrap();
+ self.remove_child_at_index(parent, index)
}
- pub fn remove_child_at_index(&mut self, node: NodeId, index: usize) -> NodeId {
- let child = self.children[node].remove(index);
- self.parents[child].retain(|p| *p != node);
- self.mark_dirty(node);
+ /// Breaks the link between the `parent` node and the n-th child node
+ ///
+ /// The child's data is not removed.
+ pub(crate) fn remove_child_at_index(&mut self, parent: NodeId, child_index: usize) -> NodeId {
+ let child = self.children[parent].remove(child_index);
+ self.parents[child].retain(|p| *p != parent);
+ self.mark_dirty(parent);
child
}
- pub fn mark_dirty(&mut self, node: NodeId) {
- // Performs a recursive depth-first search up the tree until the root node is reached
- // WARNING: this will stack-overflow if the tree contains a cycle
+ /// Marks the `node` as needing layout recalculation
+ ///
+ /// Any cached layout information is cleared.
+ pub(crate) fn mark_dirty(&mut self, node: NodeId) {
+ /// Performs a recursive depth-first search up the tree until the root node is reached
+ ///
+ /// WARNING: this will stack-overflow if the tree contains a cycle
fn mark_dirty_recursive(nodes: &mut Vec<NodeData>, parents: &[ParentsVec<NodeId>], node_id: NodeId) {
nodes[node_id].mark_dirty();
@@ -195,7 +235,9 @@ impl Forest {
mark_dirty_recursive(&mut self.nodes, &self.parents, node);
}
- pub fn compute_layout(&mut self, node: NodeId, size: Size<Number>) {
+ /// Computes the layout of the `node` and its children
+ pub(crate) fn compute_layout(&mut self, node: NodeId, size: Size<Number>) {
+ // TODO: It's not clear why this method is distinct
self.compute(node, size)
}
}
diff --git a/src/geometry.rs b/src/geometry.rs
index 8f54463a0..55555948c 100644
--- a/src/geometry.rs
+++ b/src/geometry.rs
@@ -1,25 +1,51 @@
+//! Geometric primitives useful for layout
+
use core::ops::Add;
use crate::number::Number;
use crate::style::{Dimension, FlexDirection};
+/// An axis-aligned UI rectangle
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Rect<T> {
+ /// This can represent either the x-coordinate of the starting edge,
+ /// or the amount of padding on the starting side.
+ ///
+ /// The starting edge is the left edge when working with LTR text,
+ /// and the right edge when working with RTL text.
pub start: T,
+ /// This can represent either the x-coordinate of the ending edge,
+ /// or the amount of padding on the ending side.
+ ///
+ /// The ending edge is the right edge when working with LTR text,
+ /// and the left edge when working with RTL text.
pub end: T,
+ /// This can represent either the y-coordinate of the top edge,
+ /// or the amount of padding on the top side.
pub top: T,
+ /// This can represent either the y-coordinate of the bottom edge,
+ /// or the amount of padding on the bottom side.
pub bottom: T,
}
impl<T> Rect<T> {
+ /// Applies the function `f` to all four sides of the [`Rect`]
+ ///
+ /// This is used to transform a `Rect<T>` into a `Rect<R>`.
pub(crate) fn map<R, F>(self, f: F) -> Rect<R>
where
F: Fn(T) -> R,
{
Rect { start: f(self.start), end: f(self.end), top: f(self.top), bottom: f(self.bottom) }
}
+
+ /// Applies the function `f` to all four sides of the rect
+ ///
+ /// When applied to the left and right sides, the width is used
+ /// as the second parameter of `f`.
+ /// When applied to the top or bottom sides, the height is used instead.
pub(crate) fn zip_size<R, F, U>(self, size: Size<U>, f: F) -> Rect<R>
where
F: Fn(T, U) -> R,
@@ -38,27 +64,47 @@ impl<T> Rect<T>
where
T: Add<Output = T> + Copy + Clone,
{
- pub(crate) fn horizontal(&self) -> T {
+ /// The sum of [`Rect.start`](Rect) and [`Rect.end`](Rect)
+ ///
+ /// This is typically used when computing total padding.
+ ///
+ /// **NOTE:** this is *not* the width of the rectangle.
+ pub(crate) fn horizontal_axis_sum(&self) -> T {
self.start + self.end
}
- pub(crate) fn vertical(&self) -> T {
+ /// The sum of [`Rect.top`](Rect) and [`Rect.bottom`](Rect)
+ ///
+ /// This is typically used when computing total padding.
+ ///
+ /// **NOTE:** this is *not* the height of the rectangle.
+ pub(crate) fn vertical_axis_sum(&self) -> T {
self.top + self.bottom
}
- pub(crate) fn main(&self, direction: FlexDirection) -> T {
+ /// The sum of the two fields of the [`Rect`] representing the main axis.
+ ///
+ /// This is typically used when computing total padding.
+ ///
+ /// If the [`FlexDirection`] is [`FlexDirection::Row`] or [`FlexDirection::RowReverse`], this is [`Rect::horizontal`].
+ /// Otherwise, this is [`Rect::vertical`].
+ pub(crate) fn main_axis_sum(&self, direction: FlexDirection) -> T {
if direction.is_row() {
- self.horizontal()
+ self.horizontal_axis_sum()
} else {
- self.vertical()
+ self.vertical_axis_sum()
}
}
- pub(crate) fn cross(&self, direction: FlexDirection) -> T {
+ /// The sum of the two fields of the [`Rect`] representing the cross axis.
+ ///
+ /// If the [`FlexDirection`] is [`FlexDirection::Row`] or [`FlexDirection::RowReverse`], this is [`Rect::vertical`].
+ /// Otherwise, this is [`Rect::horizontal`].
+ pub(crate) fn cross_axis_sum(&self, direction: FlexDirection) -> T {
if direction.is_row() {
- self.vertical()
+ self.vertical_axis_sum()
} else {
- self.horizontal()
+ self.horizontal_axis_sum()
}
}
}
@@ -67,6 +113,7 @@ impl<T> Rect<T>
where
T: Copy + Clone,
{
+ /// The `start` or `top` value of the [`Rect`], from the perspective of the main layout axis
pub(crate) fn main_start(&self, direction: FlexDirection) -> T {
if direction.is_row() {
self.start
@@ -75,6 +122,7 @@ where
}
}
+ /// The `end` or `bottom` value of the [`Rect`], from the perspective of the main layout axis
pub(crate) fn main_end(&self, direction: FlexDirection) -> T {
if direction.is_row() {
self.end
@@ -83,6 +131,7 @@ where
}
}
+ /// The `start` or `top` value of the [`Rect`], from the perspective of the cross layout axis
pub(crate) fn cross_start(&self, direction: FlexDirection) -> T {
if direction.is_row() {
self.top
@@ -91,6 +140,7 @@ where
}
}
+ /// The `end` or `bottom` value of the [`Rect`], from the perspective of the main layout axis
pub(crate) fn cross_end(&self, direction: FlexDirection) -> T {
if direction.is_row() {
self.bottom
@@ -100,15 +150,19 @@ where
}
}
+/// The width and height of a [`Rect`]
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Size<T> {
+ /// The x extent of the rectangle
pub width: T,
+ /// The y extent of the rectangle
pub height: T,
}
impl Size<()> {
+ /// Generates a `Size<Number>` with undefined width and height
#[must_use]
pub fn undefined() -> Size<Number> {
Size { width: Number::Undefined, height: Number::Undefined }
@@ -116,6 +170,9 @@ impl Size<()> {
}
impl<T> Size<T> {
+ /// Applies the function `f` to both the width and height
+ ///
+ /// This is used to transform a `Rect<T>` into a `Rect<R>`.
pub fn map<R, F>(self, f: F) -> Size<R>
where
F: Fn(T) -> R,
@@ -123,6 +180,9 @@ impl<T> Size<T> {
Size { width: f(self.width), height: f(self.height) }
}
+ /// Sets the extent of the main layout axis
+ ///
+ /// Whether this is the width or height depends on the `direction` provided
pub(crate) fn set_main(&mut self, direction: FlexDirection, value: T) {
if direction.is_row() {
self.width = value
@@ -131,6 +191,9 @@ impl<T> Size<T> {
}
}
+ /// Sets the extent of the cross layout axis
+ ///
+ /// Whether this is the width or height depends on the `direction` provided
pub(crate) fn set_cross(&mut self, direction: FlexDirection, value: T) {
if direction.is_row() {
self.height = value
@@ -139,6 +202,9 @@ impl<T> Size<T> {
}
}
+ /// Gets the extent of the main layout axis
+ ///
+ /// Whether this is the width or height depends on the `direction` provided
pub(crate) fn main(self, direction: FlexDirection) -> T {
if direction.is_row() {
self.width
@@ -147,6 +213,9 @@ impl<T> Size<T> {
}
}
+ /// Gets the extent of the cross layout axis
+ ///
+ /// Whether this is the width or height depends on the `direction` provided
pub(crate) fn cross(self, direction: FlexDirection) -> T {
if direction.is_row() {
self.height
@@ -157,6 +226,7 @@ impl<T> Size<T> {
}
impl Size<f32> {
+ /// A [`Size`] with zero width and height
#[must_use]
pub fn zero() -> Self {
Self { width: 0.0, height: 0.0 }
@@ -164,18 +234,25 @@ impl Size<f32> {
}
impl Size<Dimension> {
+ /// Converts any `parent`-relative values for size into an absolute size
pub(crate) fn resolve(&self, parent: Size<Number>) -> Size<Number> {
Size { width: self.width.resolve(parent.width), height: self.height.resolve(parent.height) }
}
}
+/// A 2-dimensional coordinate.
+///
+/// When used in association with a [`Rect`], represents the bottom-left corner.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Point<T> {
+ /// The x-coordinate
pub x: T,
+ /// The y-coordinate
pub y: T,
}
impl Point<f32> {
+ /// A [`Point`] with values (0,0), representing the origin
#[must_use]
pub fn zero() -> Self {
Self { x: 0.0, y: 0.0 }
diff --git a/src/layout.rs b/src/layout.rs
index 0f77546ff..b09b859f4 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -1,3 +1,5 @@
+//! Final and cached data structures that represent the high-level UI layout
+
use crate::flexbox::ComputeResult;
use crate::geometry::{Point, Size};
use crate::number::Number;
@@ -10,24 +12,30 @@ pub struct Layout {
/// Nodes with a higher order should be rendered on top of those with a lower order.
/// This is effectively a topological sort of each tree.
pub order: u32,
- // The width and height of the node
+ /// The width and height of the node
pub size: Size<f32>,
- // The bottom-left corner of the node
+ /// The bottom-left corner of the node
pub location: Point<f32>,
}
impl Layout {
+ /// Creates a new [`Layout`] struct with zero size positioned at the origin
#[must_use]
pub(crate) fn new() -> Self {
Self { order: 0, size: Size::zero(), location: Point::zero() }
}
}
+/// Cached intermediate layout results
#[derive(Debug, Clone)]
pub(crate) struct Cache {
+ /// The initial cached size of the node itself
pub(crate) node_size: Size<Number>,
+ /// The initial cached size of the parent's node
pub(crate) parent_size: Size<Number>,
+ /// Whether or not layout should be recomputed
pub(crate) perform_layout: bool,
+ /// The stored result of the layout calculations
pub(crate) result: ComputeResult,
}
diff --git a/src/lib.rs b/src/lib.rs
index 0b3335851..ed5ec0143 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,8 @@
+#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(unsafe_code)]
+#![warn(missing_docs)]
+#![warn(clippy::missing_docs_in_private_items)]
#[cfg(all(not(feature = "std"), feature = "alloc"))]
extern crate alloc;
@@ -17,18 +20,19 @@ pub mod style;
mod flexbox;
mod forest;
-mod sys;
-
#[cfg(all(not(feature = "alloc"), not(feature = "std")))]
mod indexmap;
+mod sys;
+
+pub use crate::node::Sprawl;
#[cfg(feature = "std")]
use core::fmt::{Display, Formatter, Result};
-pub use crate::node::Sprawl;
-
+/// An error that can occur when performing layout
#[derive(Debug)]
pub enum Error {
+ /// The [`Node`](node::Node) was invalid
InvalidNode(node::Node),
}
@@ -45,7 +49,7 @@ impl Display for Error {
impl std::error::Error for Error {
fn description(&self) -> &str {
match *self {
- Error::InvalidNode(_) => "The node is not part of the sprawl instance",
+ Error::InvalidNode(_) => "The node is not part of the Sprawl instance",
}
}
}
diff --git a/src/node.rs b/src/node.rs
index 299ca8c64..477ea4878 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -1,3 +1,6 @@
+//! UI [`Node`] types and related data structures.
+//!
+//! Layouts are composed of multiple nodes, which live in a forest-like data structure.
use crate::forest::Forest;
use crate::geometry::Size;
use crate::layout::Layout;
@@ -9,8 +12,11 @@ use crate::sys::{new_map_with_capacity, ChildrenVec, Map, Vec};
use crate::Error;
use core::sync::atomic::{AtomicUsize, Ordering};
+/// A function that can be applied to a `Size<Number>` to obtain a `Size<f32>`
pub enum MeasureFunc {
+ /// Stores an unboxed function
Raw(fn(Size<Number>) -> Size<f32>),
+ /// Stores a boxed function
#[cfg(any(feature = "std", feature = "alloc"))]
Boxed(Box<dyn Fn(Size<Number>) -> Size<f32>>),
}
@@ -18,18 +24,27 @@ pub enum MeasureFunc {
/// Global sprawl instance id allocator.
static INSTANCE_ALLOCATOR: Allocator = Allocator::new();
+/// An [`Id`]-containing identifier
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(not(any(feature = "std", feature = "alloc")), derive(hash32_derive::Hash32))]
pub struct Node {
+ /// The id of the forest that this node lives with
instance: Id,
+ /// The identifier of this particular node within the tree
local: Id,
}
+/// A forest of UI [`Nodes`](`Node`), suitable for UI layout
pub struct Sprawl {
+ /// The ID of the root node
id: Id,
- nodes: Allocator,
+ /// A monotonically-increasing index that tracks the [`Id`] of the next node
+ allocator: Allocator,
+ /// A map from Node -> NodeId
nodes_to_ids: Map<Node, NodeId>,
+ /// A map from NodeId -> Node
ids_to_nodes: Map<NodeId, Node>,
+ /// An efficient data structure that stores the node trees
forest: Forest,
}
@@ -40,32 +55,38 @@ impl Default for Sprawl {
}
impl Sprawl {
+ /// Creates a new [`Sprawl`]
+ ///
+ /// The default capacity of a [`Sprawl`] is 16 nodes.
#[must_use]
pub fn new() -> Self {
Default::default()
}
+ /// Creates a new [`Sprawl`] that can store `capacity` nodes before reallocation
pub fn with_capacity(capacity: usize) -> Self {
Self {
id: INSTANCE_ALLOCATOR.allocate(),
- nodes: Allocator::new(),
+ allocator: Allocator::new(),
nodes_to_ids: new_map_with_capacity(capacity),
ids_to_nodes: new_map_with_capacity(capacity),
forest: Forest::with_capacity(capacity),
}
}
+ /// Allocates memory for a new node, and returns a matching generated [`Node`]
fn allocate_node(&mut self) -> Node {
- let local = self.nodes.allocate();
+ let local = self.allocator.allocate();
Node { instance: self.id, local }
}
+ /// Stores a new node in the tree
fn add_node(&mut self, node: Node, id: NodeId) {
let _ = self.nodes_to_ids.insert(node, id);
let _ = self.ids_to_nodes.insert(id, node);
}
- // Find node in the forest.
+ /// Returns the `NodeId` of the provided node within the forest
fn find_node(&self, node: Node) -> Result<NodeId, Error> {
match self.nodes_to_ids.get(&node) {
Some(id) => Ok(*id),
@@ -73,6 +94,7 @@ impl Sprawl {
}
}
+ /// Adds a new leaf node, which does not have any children
pub fn new_leaf(&mut self, style: Style, measure: MeasureFunc) -> Result<Node, Error> {
let node = self.allocate_node();
let id = self.forest.new_leaf(style, measure);
@@ -80,6 +102,7 @@ impl Sprawl {
Ok(node)
}
+ /// Adds a new node, which may have any number of `children`
pub fn new_node(&mut self, style: Style, children: &[Node]) -> Result<Node, Error> {
let node = self.allocate_node();
let children =
@@ -89,16 +112,18 @@ impl Sprawl {
Ok(node)
}
- /// Removes all nodes.
+ /// Removes all nodes
///
- /// All associated nodes will be invalid.
+ /// All associated [`Id`] will be rendered invalid.
pub fn clear(&mut self) {
self.nodes_to_ids.clear();
self.ids_to_nodes.clear();
self.forest.clear();
}
- /// Remove nodes.
+ /// Remove a specific [`Node`] from the tree
+ ///
+ /// Its [`Id`] is marked as invalid.
pub fn remove(&mut self, node: Node) {
let id = if let Ok(id) = self.find_node(node) { id } else { return };
@@ -112,6 +137,7 @@ impl Sprawl {
}
}
+ /// Sets the [`MeasureFunc`] of the associated node
pub fn set_measure(&mut self, node: Node, measure: Option<MeasureFunc>) -> Result<(), Error> {
let id = self.find_node(node)?;
self.forest.nodes[id].measure = measure;
@@ -119,6 +145,7 @@ impl Sprawl {
Ok(())
}
+ /// Adds a `child` [`Node`] under the supplied `parent`
pub fn add_child(&mut self, parent: Node, child: Node) -> Result<(), Error> {
let node_id = self.find_node(parent)?;
let child_id = self.find_node(child)?;
@@ -127,6 +154,7 @@ impl Sprawl {
Ok(())
}
+ /// Directly sets the `children` of the supplied `parent`
pub fn set_children(&mut self, parent: Node, children: &[Node]) -> Result<(), Error> {
let node_id = self.find_node(parent)?;
let children_id = children.iter().map(|child| self.find_node(*child)).collect::<Result<ChildrenVec<_>, _>>()?;
@@ -146,6 +174,9 @@ impl Sprawl {
Ok(())
}
+ /// Removes the `child` of the parent `node`
+ ///
+ /// The child is not removed from the forest entirely, it is simply no longer attached to its previous parent.
pub fn remove_child(&mut self, parent: Node, child: Node) -> Result<Node, Error> {
let node_id = self.find_node(parent)?;
let child_id = self.find_node(child)?;
@@ -154,6 +185,9 @@ impl Sprawl {
Ok(self.ids_to_nodes[&prev_id])
}
+ /// Removes the child at the given `index` from the `parent`
+ ///
+ /// The child is not removed from the forest entirely, it is simply no longer attached to its previous parent.
pub fn remove_child_at_index(&mut self, parent: Node, child_index: usize) -> Result<Node, Error> {
let node_id = self.find_node(parent)?;
// TODO: index check
@@ -162,6 +196,9 @@ impl Sprawl {
Ok(self.ids_to_nodes[&prev_id])
}
+ /// Replaces the child at the given `child_index` from the `parent` node with the new `child` node
+ ///
+ /// The child is not removed from the forest entirely, it is simply no longer attached to its previous parent.
pub fn replace_child_at_index(&mut self, parent: Node, child_index: usize, new_child: Node) -> Result<Node, Error> {
let node_id = self.find_node(parent)?;
let child_id = self.find_node(new_child)?;
@@ -176,21 +213,25 @@ impl Sprawl {
Ok(self.ids_to_nodes[&old_child])
}
- pub fn children(&self, parent: Node) -> Result<Vec<Node>, Error> {
- let id = self.find_node(parent)?;
- Ok(self.forest.children[id].iter().map(|child| self.ids_to_nodes[child]).collect())
- }
-
+ /// Returns the child [`Node`] of the parent `node` at the provided `child_index`
pub fn child_at_index(&self, parent: Node, child_index: usize) -> Result<Node, Error> {
let id = self.find_node(parent)?;
Ok(self.ids_to_nodes[&self.forest.children[id][child_index]])
}
+ /// Returns the number of children of the `parent` [`Node`]
pub fn child_count(&self, parent: Node) -> Result<usize, Error> {
let id = self.find_node(parent)?;
Ok(self.forest.children[id].len())
}
+ /// Returns a list of children that belong to the [`Parent`]
+ pub fn children(&self, parent: Node) -> Result<Vec<Node>, Error> {
+ let id = self.find_node(parent)?;
+ Ok(self.forest.children[id].iter().map(|child| self.ids_to_nodes[child]).collect())
+ }
+
+ /// Sets the [`Style`] of the provided `node`
pub fn set_style(&mut self, node: Node, style: Style) -> Result<(), Error> {
let id = self.find_node(node)?;
self.forest.nodes[id].style = style;
@@ -198,6 +239,7 @@ impl Sprawl {
Ok(())
}
+ /// Gets the [`Style`] of the provided `node`
pub fn style(&self, node: Node) -> Result<&Style, Error> {
let id = self.find_node(node)?;
Ok(&self.forest.nodes[id].style)
@@ -209,17 +251,20 @@ impl Sprawl {
Ok(&self.forest.nodes[id].layout)
}
+ /// Marks the layout computation of this node and its children as outdated
pub fn mark_dirty(&mut self, node: Node) -> Result<(), Error> {
let id = self.find_node(node)?;
self.forest.mark_dirty(id);
Ok(())
}
+ /// Indicates whether the layout of this node (and its children) need to be recomputed
pub fn dirty(&self, node: Node) -> Result<bool, Error> {
let id = self.find_node(node)?;
Ok(self.forest.nodes[id].is_dirty)
}
+ /// Updates the stored layout of the provided `node` and its children
pub fn compute_layout(&mut self, node: Node, size: Size<Number>) -> Result<(), Error> {
let id = self.find_node(node)?;
self.forest.compute_layout(id, size);
@@ -230,21 +275,26 @@ impl Sprawl {
/// Internal node id.
pub(crate) type NodeId = usize;
+/// The identifier of a [`Node`]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(not(any(feature = "std", feature = "alloc")), derive(hash32_derive::Hash32))]
pub(crate) struct Id(usize);
+/// An bump-allocator index that tracks how many [`Nodes`](Node) have been allocated in a [`Sprawl`].
pub(crate) struct Allocator {
- new_id: AtomicUsize,
+ /// The last reserved [`NodeId`]
+ last_id: AtomicUsize,
}
impl Allocator {
+ /// Creates a fresh [`Allocator`]
#[must_use]
pub const fn new() -> Self {
- Self { new_id: AtomicUsize::new(0) }
+ Self { last_id: AtomicUsize::new(0) }
}
+ /// Allocates space for one more [`Node`]
pub fn allocate(&self) -> Id {
- Id(self.new_id.fetch_add(1, Ordering::Relaxed))
+ Id(self.last_id.fetch_add(1, Ordering::Relaxed))
}
}
diff --git a/src/number.rs b/src/number.rs
index 744e8c13d..48dd11ea7 100644
--- a/src/number.rs
+++ b/src/number.rs
@@ -1,13 +1,25 @@
+//! Contains the [`Number`] type and associated methods
+
use core::ops::{Add, Div, Mul, Sub};
+/// A [`f32`] that may be undefined
+///
+/// NOTE: this should *really* be an [`Option<f32>`],
+/// but we're optimizing for backwards compatibility for now.
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Number {
+ /// Pretend this is [`Some(f32)`]
+ ///
+ /// WARNING: the contained value may be NaN
Defined(f32),
+ /// Pretend this is [`None`]
Undefined,
}
+/// An extension method used for [`Number`]
pub trait OrElse<T> {
+ /// Returns the contained value if it is defined, otherwise returns `other`
fn or_else(self, other: T) -> T;
}
@@ -36,6 +48,7 @@ impl OrElse<Self> for Number {
}
impl Number {
+ /// Is the number defined?
pub fn is_defined(self) -> bool {
match self {
Number::Defined(_) => true,
@@ -43,6 +56,7 @@ impl Number {
}
}
+ /// Is the number undefined?
pub fn is_undefined(self) -> bool {
match self {
Number::Defined(_) => false,
@@ -51,8 +65,15 @@ impl Number {
}
}
+/// An extension trait for [`Number`]
pub trait MinMax<In, Out> {
+ /// Returns the minimum of self and rhs
+ ///
+ /// If either either value is invalid, returns the other value.
fn maybe_min(self, rhs: In) -> Out;
+ /// Returns the maximum of self and rhs
+ ///
+ /// If either either value is invalid, returns the other value.
fn maybe_max(self, rhs: In) -> Out;
}
diff --git a/src/prelude.rs b/src/prelude.rs
index abc9c55ee..153f727c7 100644
--- a/src/prelude.rs
+++ b/src/prelude.rs
@@ -1,3 +1,5 @@
+//! Commonly used types
+
pub use crate::{
geometry::{Rect, Size},
layout::Layout,
diff --git a/src/style.rs b/src/style.rs
index 436173793..ff95ab96d 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -1,13 +1,25 @@
+//! A representation of [CSS layout properties](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) in Rust, used for flexbox layout
+
use crate::geometry::{Rect, Size};
use crate::number::Number;
+/// How [`Nodes`](crate::node::Node) are aligned relative to the cross axis
+///
+/// The default behavior is [`AlignItems::Stretch`].
+///
+/// [Specification](https://www.w3.org/TR/css-flexbox-1/#align-items-property)
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum AlignItems {
+ /// Items are packed toward the start of the cross axis
FlexStart,
+ /// Items are packed toward the end of the cross axis
FlexEnd,
+ /// Items are packed along the center of the cross axis
Center,
+ /// Items are aligned such as their baselines align
Baseline,
+ /// Stretch to fill the container
Stretch,
}
@@ -17,14 +29,27 @@ impl Default for AlignItems {
}
}
+/// Overrides the inherited [`AlignItems`] behavior for this node.
+///
+/// The behavior of any child nodes will be controlled by this node's [`AlignItems`] value.
+///
+/// The default behavior is [`AlignSelf::Auto`].
+///
+/// [Specification](https://www.w3.org/TR/css-flexbox-1/#align-items-property)
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum AlignSelf {
+ /// Inherits the [`AlignItems`] behavior of the parent
Auto,
+ /// Items are packed toward the start of the cross axis
FlexStart,
+ /// Items are packed toward the end of the cross axis
FlexEnd,
+ /// Items are packed along the center of the cross axis
Center,
+ /// Items are aligned such as their baselines align
Baseline,
+ /// Distribute items evenly, but stretch them to fill the container
Stretch,
}
@@ -34,14 +59,26 @@ impl Default for AlignSelf {
}
}
+/// Sets the distribution of space between and around content items along the cross-axis
+///
+/// The default value is [`AlignContent::Stretch`].
+///
+/// [Specification](https://www.w3.org/TR/css-flexbox-1/#align-content-property)
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum AlignContent {
+ /// Items are packed toward the start of the cross axis
FlexStart,
+ /// Items are packed toward the end of the cross axis
FlexEnd,
+ /// Items are packed along the center of the cross axis
Center,
+ /// Distribute items evenly, but stretch them to fill the container
Stretch,
+ /// Distribute items evenly, such that the first and last item are aligned with the edges
SpaceBetween,
+ /// Distribute items evenly,
+ /// such that the space between items is the same as the space between the first and last item and the edges
SpaceAround,
}
@@ -51,11 +88,16 @@ impl Default for AlignContent {
}
}
+/// Sets the layout used for the children of this node
+///
+/// [`Display::Flex`] is the default value.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Display {
+ /// The children will follow the flexbox layout algorithm
#[cfg_attr(feature = "serde", serde(rename = "flex"))]
Flex,
+ /// The children will not be laid out, and will follow absolute positioning
#[cfg_attr(feature = "serde", serde(rename = "none"))]
None,
}
@@ -66,12 +108,35 @@ impl Default for Display {
}
}
+/// The direction of the flexbox layout main axis.
+///
+/// There are always two perpendicular layout axes: main (or primary) and cross (or secondary).
+/// Adding items will cause them to be positioned adjacent to each other along the main axis.
+/// By varying this value throughout your tree, you can create complex axis-aligned layouts.
+///
+/// Items are always aligned relative to the cross axis, and justified relative to the main axis.
+///
+/// The default behavior is [`FlexDirection::Row`].
+///
+/// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-direction-property)
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum FlexDirection {
+ /// Defines +x as the main axis
+ ///
+ /// Items will be added from left to right in a row.
Row,
+ /// Defines +y as the main axis
+ ///
+ /// Items will be added from top to bottom in a column.
Column,
+ /// Defines -x as the main axis
+ ///
+ /// Items will be added from right to left in a row.
RowReverse,
+ /// Defines -y as the main axis
+ ///
+ /// Items will be added from bottom to top in a column.
ColumnReverse,
}
@@ -83,29 +148,45 @@ impl Default for FlexDirection {
impl FlexDirection {
#[inline]
+ /// Is the direction [`FlexDirection::Row`] or [`FlexDirection::RowReverse`]?
pub(crate) fn is_row(self) -> bool {
matches!(self, Self::Row | Self::RowReverse)
}
#[inline]
+ /// Is the direction [`FlexDirection::Column`] or [`FlexDirection::ColumnReverse`]?
pub(crate) fn is_column(self) -> bool {
matches!(self, Self::Column | Self::ColumnReverse)
}
#[inline]
+ /// Is the direction [`FlexDirection::RowReverse`] or [`FlexDirection::ColumnReverse`]?
pub(crate) fn is_reverse(self) -> bool {
matches!(self, Self::RowReverse | Self::ColumnReverse)
}
}
+/// Sets the distribution of space between and around content items along the main-axis
+///
+/// The default value is [`JustifyContent::FlexStart`].
+///
+/// [Specification](https://www.w3.org/TR/css-flexbox-1/#justify-content-property)
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum JustifyContent {
+ /// Items are packed toward the start of the main axis
FlexStart,
+ /// Items are packed toward the end of the main axis
FlexEnd,
+ /// Items are packed along the center of the main axis
Center,
+ /// Distribute items evenly, such that the first and last item are aligned with the edges
SpaceBetween,
+ /// Distribute items evenly,
+ /// such that the space between items is twice same as the space between the first and last item and the edges
SpaceAround,
+ /// Distribute items evenly,
+ /// such that the space between items is the same as the space between the first and last item and the edges
SpaceEvenly,
}
@@ -115,10 +196,26 @@ impl Default for JustifyContent {
}
}
+/// The positioning strategy for this item.
+///
+/// This controls both how the origin is determined for the [`Style::position`] field,
+/// and whether or not the item will be controlled by flexbox's layout algorithm.
+///
+/// WARNING: this enum follows the behavior of [CSS's `position` property](https://developer.mozilla.org/en-US/docs/Web/CSS/position),
+/// which can be unintuitive.
+///
+/// [`PositionType::Relative`] is the default value, in contrast to the default behavior in CSS.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PositionType {
+ /// The offset is computed relative to the final position given by the layout algorithm.
+ /// Offsets do not affect the position of any other items; they are effectively a correction factor applied at the end.
Relative,
+ /// The offset is computed relative to this item's closest positioned ancestor, if any.
+ /// Otherwise, it is placed relative to the origin.
+ /// No space is created for the item in the page layout, and its size will not be altered.
+ ///
+ /// WARNING: to opt-out of layouting entirely, you must use [`Display::None`] instead on your [`Style`] object.
Absolute,
}
@@ -128,11 +225,19 @@ impl Default for PositionType {
}
}
+/// Controls whether flex items are forced onto one line or can wrap onto multiple lines.
+///
+/// Defaults to [`FlexWrap::NoWrap`]
+///
+/// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-wrap-property)
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum FlexWrap {
+ /// Items will not wrap and stay on a single line
NoWrap,
+ /// Items will wrap according to this item's [`FlexDirection`]
Wrap,
+ /// Items will wrap in the oppposite direction to this item's [`FlexDirection`]
WrapReverse,
}
@@ -142,12 +247,22 @@ impl Default for FlexWrap {
}
}
+/// A unit of linear measurement
+///
+/// This is commonly combined with [`Rect`], [`Point`](crate::geometry::Point) and [`Size<T>`].
+/// The default value is [`Dimension::Undefined`].
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Dimension {
+ /// The dimension is not given
Undefined,
+ /// The dimension should be automatically computed
Auto,
+ /// The dimension is stored in [points](https://en.wikipedia.org/wiki/Point_(typography))
+ ///
+ /// Each point is about 0.353 mm in size.
Points(f32),
+ /// The dimension is stored in percentage relative to the parent item.
Percent(f32),
}
@@ -158,6 +273,7 @@ impl Default for Dimension {
}
impl Dimension {
+ /// Converts the given [`Dimension`] into a concrete value of points
pub(crate) fn resolve(self, parent_dim: Number) -> Number {
match self {
Dimension::Points(points) => Number::Defined(points),
@@ -166,6 +282,7 @@ impl Dimension {
}
}
+ /// Is this value defined?
pub(crate) fn is_defined(self) -> bool {
matches!(self, Dimension::Points(_) | Dimension::Percent(_))
}
@@ -183,28 +300,68 @@ impl Default for Size<Dimension> {
}
}
+/// The flexbox layout information for a single [`Node`](crate::node::Node).
+///
+/// The most important idea in flexbox is the notion of a "main" and "cross" axis, which are always perpendicular to each other.
+/// The orientation of these axes are controlled via the [`FlexDirection`] field of this struct.
+///
+/// This struct follows the [CSS equivalent](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox) directly;
+/// information about the behavior on the web should transfer directly.
+///
+/// Detailed information about the exact behavior of each of these fields
+/// can be found on [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS) by searching for the field name.
+/// The distinction between margin, padding and border is explained well in
+/// this [introduction to the box model](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model).
+///
+/// If the behavior does not match the flexbox layout algorithm on the web, please file a bug!
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Style {
+ /// What layout strategy should be used?
pub display: Display,
+ /// What should the `position` value of this struct use as a base offset?
pub position_type: PositionType,
+ /// Which direction does the main axis flow in?
pub flex_direction: FlexDirection,
+ /// Should elements wrap, or stay in a single line?
pub flex_wrap: FlexWrap,
+ /// How should items be aligned relative to the cross axis?
pub align_items: AlignItems,
+ /// Should this item violate the cross axis alignment specified by its parent's [`AlignItems`]?
pub align_self: AlignSelf,
+ /// How should content contained within this item be aligned relative to the cross axis?
pub align_content: AlignContent,
+ /// How should items be aligned relative to the main axis?
pub justify_content: JustifyContent,
+ /// How should the position of this element be tweaked relative to the layout defined?
pub position: Rect<Dimension>,
+ /// How large should the margin be on each side?
pub margin: Rect<Dimension>,
+ /// How large should the padding be on each side?
pub padding: Rect<Dimension>,
+ /// How large should the border be on each side?
pub border: Rect<Dimension>,
+ /// The relative rate at which this item grows when it is expanding to fill space
+ ///
+ /// 1.0 is the default value, and this value must be positive.
pub flex_grow: f32,
+ /// The relative rate at which this item shrinks when it is contracting to fit into space
+ ///
+ /// 1.0 is the default value, and this value must be positive.
pub flex_shrink: f32,
+ /// Sets the initial main axis size of the item
pub flex_basis: Dimension,
+ /// Sets the initial size of the item
+ // TODO: why does this exist as distinct from flex_basis? How do they interact?
pub size: Size<Dimension>,
+ /// Controls the minimum size of the item
pub min_size: Size<Dimension>,
+ /// Controls the maximum size of the item
pub max_size: Size<Dimension>,
+ /// Sets the preferred aspect ratio for the item
+ ///
+ /// The ratio is calculated as width divided by height.
pub aspect_ratio: Number,
}
@@ -235,6 +392,7 @@ impl Default for Style {
}
impl Style {
+ /// If the `direction` is row-oriented, the min width. Otherwise the min height
pub(crate) fn min_main_size(&self, direction: FlexDirection) -> Dimension {
if direction.is_row() {
self.min_size.width
@@ -243,6 +401,7 @@ impl Style {
}
}
+ /// If the `direction` is row-oriented, the max width. Otherwise the max height
pub(crate) fn max_main_size(&self, direction: FlexDirection) -> Dimension {
if direction.is_row() {
self.max_size.width
@@ -251,6 +410,7 @@ impl Style {
}
}
+ /// If the `direction` is row-oriented, the margin start. Otherwise the margin top
pub(crate) fn main_margin_start(&self, direction: FlexDirection) -> Dimension {
if direction.is_row() {
self.margin.start
@@ -259,6 +419,7 @@ impl Style {
}
}
+ /// If the `direction` is row-oriented, the margin end. Otherwise the margin bottom
pub(crate) fn main_margin_end(&self, direction: FlexDirection) -> Dimension {
if direction.is_row() {
self.margin.end
@@ -267,6 +428,7 @@ impl Style {
}
}
+ /// If the `direction` is row-oriented, the height. Otherwise the width
pub(crate) fn cross_size(&self, direction: FlexDirection) -> Dimension {
if direction.is_row() {
self.size.height
@@ -275,6 +437,7 @@ impl Style {
}
}
+ /// If the `direction` is row-oriented, the min height. Otherwise the min width
pub(crate) fn min_cross_size(&self, direction: FlexDirection) -> Dimension {
if direction.is_row() {
self.min_size.height
@@ -283,6 +446,7 @@ impl Style {
}
}
+ /// If the `direction` is row-oriented, the max height. Otherwise the max width
pub(crate) fn max_cross_size(&self, direction: FlexDirection) -> Dimension {
if direction.is_row() {
self.max_size.height
@@ -291,6 +455,7 @@ impl Style {
}
}
+ /// If the `direction` is row-oriented, the margin top. Otherwise the margin start
pub(crate) fn cross_margin_start(&self, direction: FlexDirection) -> Dimension {
if direction.is_row() {
self.margin.top
@@ -299,6 +464,7 @@ impl Style {
}
}
+ /// If the `direction` is row-oriented, the margin bottom. Otherwise the margin end
pub(crate) fn cross_margin_end(&self, direction: FlexDirection) -> Dimension {
if direction.is_row() {
self.margin.bottom
@@ -307,6 +473,7 @@ impl Style {
}
}
+ /// Computes the final alignment of this item based on the parent's [`AlignItems`] and this item's [`AlignSelf`]
pub(crate) fn align_self(&self, parent: &Style) -> AlignSelf {
if self.align_self == AlignSelf::Auto {
match parent.align_items {
diff --git a/src/sys.rs b/src/sys.rs
index d1ed6134d..1c54af08c 100644
--- a/src/sys.rs
+++ b/src/sys.rs
@@ -1,3 +1,5 @@
+//! Allocator-flexible data types
+
// When std is enabled, prefer those types
#[cfg(feature = "std")]
pub use self::std::*;
@@ -10,14 +12,21 @@ pub use self::alloc::*;
#[cfg(all(not(feature = "alloc"), not(feature = "std")))]
pub use self::core::*;
+/// For when `std` is enabled
#[cfg(feature = "std")]
mod std {
+ /// An allocation-backend agnostic [`Box`] type
pub type Box<A> = std::boxed::Box<A>;
+ /// An allocation-backend agnostic map type
pub type Map<K, V> = std::collections::HashMap<K, V>;
+ /// An allocation-backend agnostic vector type
pub type Vec<A> = std::vec::Vec<A>;
+ /// A vector of child nodes
pub type ChildrenVec<A> = std::vec::Vec<A>;
+ /// A vector of parent nodes
pub type ParentsVec<A> = std::vec::Vec<A>;
+ /// Creates a new map with the capacity for the specified number of items before it must be resized
#[must_use]
pub fn new_map_with_capacity<K, V>(capacity: usize) -> Map<K, V>
where
@@ -26,68 +35,88 @@ mod std {
Map::with_capacity(capacity)
}
+ /// Creates a new vector with the capacity for the specified number of items before it must be resized
#[must_use]
pub fn new_vec_with_capacity<A>(capacity: usize) -> Vec<A> {
Vec::with_capacity(capacity)
}
- #[inline]
+ /// Rounds to the nearest whole number
#[must_use]
pub fn round(value: f32) -> f32 {
value.round()
}
- #[inline]
+ /// Computes the absolute value
#[must_use]
pub fn abs(value: f32) -> f32 {
value.abs()
}
}
+/// For when `alloc` but not `std` is enabled
#[cfg(all(feature = "alloc", not(feature = "std")))]
mod alloc {
extern crate alloc;
+ /// An allocation-backend agnostic `Box` type
pub type Box<A> = alloc::boxed::Box<A>;
+ /// An allocation-backend agnostic map type
pub type Map<K, V> = hashbrown::HashMap<K, V>;
+ /// An allocation-backend agnostic vector type
pub type Vec<A> = alloc::vec::Vec<A>;
+ /// A vector of child nodes
pub type ChildrenVec<A> = alloc::vec::Vec<A>;
+ /// A vector of parent nodes
pub type ParentsVec<A> = alloc::vec::Vec<A>;
+ /// Creates a new map with the capacity for the specified number of items before it must be resized
#[must_use]
pub fn new_map_with_capacity<K, V>(capacity: usize) -> Map<K, V> {
Map::with_capacity(capacity)
}
+ /// Creates a new vector with the capacity for the specified number of items before it must be resized
#[must_use]
pub fn new_vec_with_capacity<A>(capacity: usize) -> Vec<A> {
Vec::with_capacity(capacity)
}
- #[inline]
+ /// Rounds to the nearest whole number
#[must_use]
pub fn round(value: f32) -> f32 {
num_traits::float::FloatCore::round(value)
}
- #[inline]
+ /// Computes the absolute value
#[must_use]
pub fn abs(value: f32) -> f32 {
num_traits::float::FloatCore::abs(value)
}
}
+/// For when neither `alloc` nor `std` is enabled
#[cfg(all(not(feature = "alloc"), not(feature = "std")))]
mod core {
+ /// The maximum number of nodes in the forest
pub const MAX_NODE_COUNT: usize = 256;
+ /// The maximum number of children of any given node
pub const MAX_CHILD_COUNT: usize = 16;
+ /// The maximum number of parents for each child
pub const MAX_PARENTS_COUNT: usize = 1;
+ /// An allocation-backend agnostic map type
pub type Map<K, V> = crate::indexmap::FnvIndexMap<K, V, MAX_NODE_COUNT>;
+ /// An allocation-backend agnostic vector type
pub type Vec<A> = arrayvec::ArrayVec<A, MAX_NODE_COUNT>;
+ /// A vector of child nodes, whose length cannot exceed [`MAX_CHILD_COUNT`]
pub type ChildrenVec<A> = arrayvec::ArrayVec<A, MAX_CHILD_COUNT>;
+ /// A vector of parent nodes, whose length cannot exceed [`MAX_PARENTS_COUNT`]
pub type ParentsVec<A> = arrayvec::ArrayVec<A, MAX_PARENTS_COUNT>;
+ /// Creates a new map with the capacity for the specified number of items
+ ///
+ /// This map cannot be resized.
#[must_use]
pub fn new_map_with_capacity<K, V>(_capacity: usize) -> Map<K, V>
where
@@ -96,17 +125,22 @@ mod core {
Map::new()
}
+ /// Creates a new map with the capacity for the specified number of items before it must be resized
+ ///
+ /// This vector cannot be resized.
#[must_use]
pub fn new_vec_with_capacity<A, const CAP: usize>(_capacity: usize) -> arrayvec::ArrayVec<A, CAP> {
arrayvec::ArrayVec::new()
}
+ /// Rounds to the nearest whole number
#[inline]
#[must_use]
pub fn round(value: f32) -> f32 {
num_traits::float::FloatCore::round(value)
}
+ /// Computes the absolute value
#[inline]
#[must_use]
pub fn abs(value: f32) -> f32 {