use crate::{anynode::AnyNode, drawable::Drawable, models::*};
pub struct Layout<State> {
tree: fn(&mut State) -> Node<State>,
}
impl<State> Layout<State> {
pub fn new(tree: fn(&mut State) -> Node<State>) -> Self {
Self { tree }
}
}
impl<State> Layout<State> {
pub fn draw(&self, area: Area, state: &mut State) {
let mut layout = (self.tree)(state);
layout.inner.layout(area);
layout.inner.draw(state);
}
}
#[derive(Debug)]
pub struct Node<State> {
pub(crate) inner: NodeValue<State>,
}
impl<State> Clone for Node<State> {
fn clone(&self) -> Self {
Node {
inner: self.inner.clone(),
}
}
}
pub(crate) enum NodeValue<State> {
Padding {
amounts: Padding,
element: Box<NodeValue<State>>,
},
Column {
elements: Vec<NodeValue<State>>,
spacing: f32,
},
Row {
elements: Vec<NodeValue<State>>,
spacing: f32,
},
Stack(Vec<NodeValue<State>>),
Group(Vec<NodeValue<State>>),
Offset {
offset_x: f32,
offset_y: f32,
element: Box<NodeValue<State>>,
},
Draw(Drawable<State>),
Explicit {
options: Size,
element: Box<NodeValue<State>>,
},
Empty,
Space,
Scope {
scoped: AnyNode<State>,
},
}
impl<State> NodeValue<State> {
pub(crate) fn draw(&self, state: &mut State) {
match self {
NodeValue::Draw(drawable) => drawable.draw(drawable.area, state),
NodeValue::Padding { element, .. }
| NodeValue::Explicit { element, .. }
| NodeValue::Offset { element, .. } => {
element.draw(state);
}
NodeValue::Stack(elements) => {
elements.iter().for_each(|el| el.draw(state));
}
NodeValue::Column { elements, .. } | NodeValue::Row { elements, .. } => {
elements.iter().rev().for_each(|el| el.draw(state));
}
NodeValue::Space => (),
NodeValue::Scope { scoped } => scoped.draw(state),
NodeValue::Group(_) | NodeValue::Empty => unreachable!(),
}
}
pub(crate) fn layout(&mut self, available_area: Area) {
match self {
NodeValue::Padding {
amounts,
element: child,
} => {
let inner_area = Area {
x: available_area.x + amounts.leading,
y: available_area.y + amounts.top,
width: available_area.width - amounts.trailing - amounts.leading,
height: available_area.height - amounts.bottom - amounts.top,
};
child.layout(inner_area);
}
NodeValue::Column { elements, spacing } => {
layout_axis(elements, spacing, available_area, Orientation::Vertical)
}
NodeValue::Row { elements, spacing } => {
layout_axis(elements, spacing, available_area, Orientation::Horizontal)
}
NodeValue::Stack(children) => {
for child in children {
child.layout(available_area)
}
}
NodeValue::Draw(drawable) => {
drawable.area = Area {
x: available_area.x,
y: available_area.y,
width: available_area.width.max(0.),
height: available_area.height.max(0.),
};
}
NodeValue::Explicit {
options,
element: child,
} => {
let Size {
width,
width_min,
width_max,
height,
height_min,
height_max,
x_align,
y_align,
x_relative,
y_relative,
} = options;
let explicit_width = if *x_relative {
available_area.width * width.unwrap_or(1.0)
} else {
width.unwrap_or(available_area.width)
}
.clamp(
width_min.unwrap_or(0.).min(width_max.unwrap_or(0.)),
width_max
.unwrap_or(available_area.width.max(0.))
.max(width_min.unwrap_or(0.)),
);
let explicit_height = if *y_relative {
available_area.height * height.unwrap_or(1.0)
} else {
height.unwrap_or(available_area.height)
}
.clamp(
height_min.unwrap_or(0.).min(height_max.unwrap_or(0.)),
height_max
.unwrap_or(available_area.height.max(0.))
.max(height_min.unwrap_or(0.)),
);
let x = match x_align {
XAlign::Leading => available_area.x,
XAlign::Trailing => available_area.x + (available_area.width - explicit_width),
XAlign::Center => {
available_area.x + (available_area.width * 0.5) - (explicit_width * 0.5)
}
};
let y = match y_align {
YAlign::Top => available_area.y,
YAlign::Bottom => available_area.y + (available_area.height - explicit_height),
YAlign::Center => {
available_area.y + (available_area.height * 0.5) - (explicit_height * 0.5)
}
};
child.layout(Area {
x: x.max(available_area.x),
y: y.max(available_area.y),
width: explicit_width,
height: explicit_height,
});
}
NodeValue::Offset {
offset_x,
offset_y,
element,
} => {
element.layout(Area {
x: available_area.x + *offset_x,
y: available_area.y + *offset_y,
width: available_area.width,
height: available_area.height,
});
}
NodeValue::Space => (),
NodeValue::Scope { scoped } => scoped.layout(available_area),
NodeValue::Group(_) | NodeValue::Empty => unreachable!(),
}
}
}
enum Orientation {
Horizontal,
Vertical,
}
fn layout_axis<State>(
elements: &mut [NodeValue<State>],
spacing: &f32,
available_area: Area,
orientation: Orientation,
) {
let sizes: Vec<Option<f32>> = elements
.iter_mut()
.map(|element| match element {
NodeValue::Explicit {
options,
element: _,
} => match orientation {
Orientation::Horizontal => {
if let Some(width) = options.width {
if options.x_relative {
options.width = None;
return Some(width * available_area.width);
} else {
return Some(width);
}
}
None
}
Orientation::Vertical => {
if let Some(height) = options.height {
if options.y_relative {
options.height = None;
return Some(height * available_area.height);
} else {
return Some(height);
}
}
None
}
},
_ => None,
})
.map(|size| {
let Some(size) = size else { return size };
match orientation {
Orientation::Horizontal => Some(size.min(available_area.width)),
Orientation::Vertical => Some(size.min(available_area.height)),
}
})
.collect();
let element_count = sizes.len();
let total_spacing = *spacing * (element_count as i32 - 1).max(0) as f32;
let available_size = match orientation {
Orientation::Horizontal => available_area.width,
Orientation::Vertical => available_area.height,
} - total_spacing;
let explicit_consumed = sizes.iter().filter_map(|&s| s).sum::<f32>();
let remaining = available_size - explicit_consumed;
let unconstrained_sizes = sizes.iter().filter(|&s| s.is_none()).count();
let default_size = remaining / unconstrained_sizes as f32;
let mut current_pos = match orientation {
Orientation::Horizontal => available_area.x,
Orientation::Vertical => available_area.y,
};
for (i, child) in elements.iter_mut().enumerate() {
let child_size = sizes
.get(i)
.unwrap_or(&Some(default_size))
.unwrap_or(default_size);
let area = match orientation {
Orientation::Horizontal => Area {
x: current_pos,
y: available_area.y,
width: child_size,
height: available_area.height,
},
Orientation::Vertical => Area {
x: available_area.x,
y: current_pos,
width: available_area.width,
height: child_size,
},
};
child.layout(area);
current_pos += child_size + *spacing;
}
}
#[cfg(test)]
mod tests {
use crate::layout::*;
use crate::nodes::*;
#[test]
fn test_column_basic() {
Layout::new(|()| {
column(vec![
draw(|a, _| {
assert_eq!(a, Area::new(0., 0., 100., 50.));
}),
draw(|a, _| {
assert_eq!(a, Area::new(0., 50., 100., 50.));
}),
])
})
.draw(Area::new(0., 0., 100., 100.), &mut ());
}
#[test]
fn test_column_constrained_1() {
Layout::new(|()| {
column(vec![
draw(|a, _| {
assert_eq!(a, Area::new(0., 0., 100., 10.));
})
.height(10.),
draw(|a, _| {
assert_eq!(a, Area::new(0., 10., 100., 90.));
}),
])
})
.draw(Area::new(0., 0., 100., 100.), &mut ());
Layout::new(|()| {
column(vec![
draw(|a, _| {
assert_eq!(a, Area::new(0., 0., 100., 10.));
})
.rel_height(0.1),
draw(|a, _| {
assert_eq!(a, Area::new(0., 10., 100., 90.));
}),
])
})
.draw(Area::new(0., 0., 100., 100.), &mut ());
}
#[test]
fn test_column_constrained_2() {
Layout::new(|()| {
column(vec![
draw(|a, _| {
assert_eq!(a, Area::new(0., 0., 100., 90.));
}),
draw(|a, _| {
assert_eq!(a, Area::new(0., 90., 100., 10.));
})
.height(10.),
])
})
.draw(Area::new(0., 0., 100., 100.), &mut ());
Layout::new(|()| {
column(vec![
draw(|a, _| {
assert_eq!(a, Area::new(0., 0., 100., 90.));
}),
draw(|a, _| {
assert_eq!(a, Area::new(0., 90., 100., 10.));
})
.rel_height(0.1),
])
})
.draw(Area::new(0., 0., 100., 100.), &mut ());
}
#[test]
fn test_row_basic() {
Layout::new(|()| {
row(vec![
draw(|a, _| {
assert_eq!(a, Area::new(0., 0., 50., 100.));
}),
draw(|a, _| {
assert_eq!(a, Area::new(50., 0., 50., 100.));
}),
])
})
.draw(Area::new(0., 0., 100., 100.), &mut ());
}
#[test]
fn test_row_constrained_1() {
Layout::new(|()| {
row(vec![
draw(|a, _| {
assert_eq!(a, Area::new(0., 25., 10., 50.));
})
.width(10.)
.height(50.),
draw(|a, _| {
assert_eq!(a, Area::new(10., 0., 90., 100.));
}),
])
})
.draw(Area::new(0., 0., 100., 100.), &mut ());
Layout::new(|()| {
row(vec![
draw(|a, _| {
assert_eq!(a, Area::new(0., 0., 10., 20.));
})
.rel_width(0.1)
.rel_height(0.2)
.y_align(YAlign::Top),
draw(|a, _| {
assert_eq!(a, Area::new(10., 40., 10., 20.));
})
.rel_width(0.1)
.rel_height(0.2),
draw(|a, _| {
assert_eq!(a, Area::new(20., 80., 10., 20.));
})
.rel_width(0.1)
.rel_height(0.2)
.y_align(YAlign::Bottom),
draw(|a, _| {
assert_eq!(a, Area::new(30., 0., 70., 100.));
}),
])
})
.draw(Area::new(0., 0., 100., 100.), &mut ());
}
#[test]
fn test_row_constrained_2() {
Layout::new(|()| {
row(vec![
draw(|a, _| {
assert_eq!(a, Area::new(0., 0., 70., 100.));
}),
draw(|a, _| {
assert_eq!(a, Area::new(70., 0., 10., 20.));
})
.width(10.)
.height(20.)
.y_align(YAlign::Top),
draw(|a, _| {
assert_eq!(a, Area::new(80., 40., 10., 20.));
})
.width(10.)
.height(20.),
draw(|a, _| {
assert_eq!(a, Area::new(90., 80., 10., 20.));
})
.width(10.)
.height(20.)
.y_align(YAlign::Bottom),
])
})
.draw(Area::new(0., 0., 100., 100.), &mut ());
Layout::new(|()| {
row(vec![
draw(|a, _| {
assert_eq!(a, Area::new(0., 0., 70., 100.));
}),
draw(|a, _| {
assert_eq!(a, Area::new(70., 0., 10., 20.));
})
.rel_width(0.1)
.rel_height(0.2)
.y_align(YAlign::Top),
draw(|a, _| {
assert_eq!(a, Area::new(80., 40., 10., 20.));
})
.rel_width(0.1)
.rel_height(0.2),
draw(|a, _| {
assert_eq!(a, Area::new(90., 80., 10., 20.));
})
.rel_width(0.1)
.rel_height(0.2)
.y_align(YAlign::Bottom),
])
})
.draw(Area::new(0., 0., 100., 100.), &mut ());
}
#[test]
fn test_stack_basic() {
Layout::new(|()| {
stack(vec![
draw(|a, _| {
assert_eq!(a, Area::new(0., 0., 100., 100.));
}),
draw(|a, _| {
assert_eq!(a, Area::new(0., 0., 100., 100.));
}),
])
})
.draw(Area::new(0., 0., 100., 100.), &mut ());
}
#[test]
fn test_stack_alignment() {
Layout::new(|()| {
stack(vec![
draw(|a, _| {
assert_eq!(a, Area::new(0., 0., 10., 20.));
})
.rel_width(0.1)
.rel_height(0.2)
.align(Align::TopLeading),
draw(|a, _| {
assert_eq!(a, Area::new(45., 0., 10., 20.));
})
.rel_width(0.1)
.rel_height(0.2)
.align(Align::TopCenter),
draw(|a, _| {
assert_eq!(a, Area::new(90., 0., 10., 20.));
})
.rel_width(0.1)
.rel_height(0.2)
.align(Align::TopTrailing),
draw(|a, _| {
assert_eq!(a, Area::new(90., 40., 10., 20.));
})
.rel_width(0.1)
.rel_height(0.2)
.align(Align::CenterTrailing),
draw(|a, _| {
assert_eq!(a, Area::new(90., 80., 10., 20.));
})
.rel_width(0.1)
.rel_height(0.2)
.align(Align::BottomTrailing),
draw(|a, _| {
assert_eq!(a, Area::new(45., 80., 10., 20.));
})
.width(10.)
.height(20.)
.align(Align::BottomCenter),
draw(|a, _| {
assert_eq!(a, Area::new(0., 80., 10., 20.));
})
.width(10.)
.height(20.)
.align(Align::BottomLeading),
draw(|a, _| {
assert_eq!(a, Area::new(0., 40., 10., 20.));
})
.width(10.)
.height(20.)
.align(Align::CenterLeading),
draw(|a, _| {
assert_eq!(a, Area::new(45., 40., 10., 20.));
})
.width(10.)
.height(20.)
.align(Align::CenterCenter),
])
})
.draw(Area::new(0., 0., 100., 100.), &mut ());
}
#[test]
fn test_sequence_spacing() {
Layout::new(|()| {
row_spaced(
10.,
vec![
draw(|a, _| {
assert_eq!(a, Area::new(0., 40., 10., 20.));
})
.width(10.)
.height(20.),
draw(|a, _| {
assert_eq!(a, Area::new(20., 0., 25., 100.));
}),
draw(|a, _| {
assert_eq!(a, Area::new(55., 40., 10., 20.));
})
.width(10.)
.height(20.),
draw(|a, _| {
assert_eq!(a, Area::new(75., 0., 25., 100.));
}),
],
)
})
.draw(Area::new(0., 0., 100., 100.), &mut ());
Layout::new(|()| {
column_spaced(
10.,
vec![
draw(|a, _| {
assert_eq!(a, Area::new(0., 0., 100., 15.));
}),
draw(|a, _| {
assert_eq!(a, Area::new(45., 25., 10., 20.));
})
.width(10.)
.height(20.),
draw(|a, _| {
assert_eq!(a, Area::new(0., 55., 100., 15.));
}),
draw(|a, _| {
assert_eq!(a, Area::new(45., 80., 10., 20.));
})
.width(10.)
.height(20.),
],
)
})
.draw(Area::new(0., 0., 100., 100.), &mut ());
}
}