use crate::{
passes::{collect, perform_layout_passes},
tree::TreeNode,
types::*,
};
use std::ops::RangeBounds;
macro_rules! container_doc {
() => {
r#"
Container nodes, by default, will only take up enough space to fit their contents.
If you want the container to take up as much space as is available you can use an `expand` modifier,
or add an unconstrained node to it's contents.
Unconstrained nodes can be conceptualized as "pushing" outwards & expanding their container,
or pushing against other unconstrained nodes with equal force.
"#
};
}
impl<'a, D, S> Layout<'a, D, S> {
pub fn draw(&mut self, available_area: Area, state: &mut S) -> Vec<D> {
perform_layout_passes(self, available_area, state);
collect(self, state)
}
pub fn min_height(&mut self, available_area: Area, state: &mut S) -> Option<f32> {
perform_layout_passes(self, available_area, state);
self.constraints().height.lower
}
pub fn min_width(&mut self, available_area: Area, state: &mut S) -> Option<f32> {
perform_layout_passes(self, available_area, state);
self.constraints().width.lower
}
}
impl<'a, D: 'a, S: 'a> Layout<'a, D, S> {
pub fn map<B: 'a>(self, f: impl Fn(D) -> B + 'a) -> Layout<'a, B, S> {
use std::rc::Rc;
let f = Rc::new(f);
self.map_tree(|node, children| {
let mapped_layout = match node.layout {
LayoutType::Draw(Some(draw_fn)) => {
let f = f.clone();
LayoutType::Draw(Some(Box::new(move |area, s| {
draw_fn(area, s).into_iter().map(|draw| f(draw)).collect()
})))
}
LayoutType::Draw(None) => LayoutType::Draw(None),
LayoutType::Column {
spacing,
x_align,
y_align,
} => LayoutType::Column {
spacing,
x_align,
y_align,
},
LayoutType::Row {
spacing,
x_align,
y_align,
} => LayoutType::Row {
spacing,
x_align,
y_align,
},
LayoutType::Stack { x_align, y_align } => LayoutType::Stack { x_align, y_align },
LayoutType::Padding {
leading,
trailing,
top,
bottom,
} => LayoutType::Padding {
leading,
trailing,
top,
bottom,
},
LayoutType::Offset { x, y } => LayoutType::Offset { x, y },
LayoutType::Space => LayoutType::Space,
LayoutType::Empty => LayoutType::Empty,
};
Layout {
layout: mapped_layout,
constraints: node.constraints,
dynamic_constraints: node.dynamic_constraints,
layer: node.layer,
resolved: node.resolved,
allocated: node.allocated,
children,
}
})
}
}
struct NodeBuilder<'a, D, S = ()> {
layout: LayoutType<'a, D, S>,
constraints: Constraints,
dynamic_constraints: DynamicConstraints<'a, S>,
children: Vec<Layout<'a, D, S>>,
}
impl<'a, D, S> NodeBuilder<'a, D, S> {
fn new(layout: LayoutType<'a, D, S>) -> Self {
Self {
layout,
constraints: Constraints::default(),
dynamic_constraints: DynamicConstraints::default(),
children: Vec::new(),
}
}
fn children(mut self, children: Vec<Layout<'a, D, S>>) -> Self {
self.children = children
.into_iter()
.filter(|child| !matches!(child.layout, LayoutType::Empty))
.collect();
self
}
fn child(self, child: Layout<'a, D, S>) -> Self {
self.children(vec![child])
}
fn build(self) -> Layout<'a, D, S> {
Layout {
layout: self.layout,
constraints: self.constraints,
dynamic_constraints: self.dynamic_constraints,
layer: None,
resolved: None,
allocated: None,
children: self.children,
}
}
}
pub fn draw<'a, D, S>(data: impl FnOnce(Area, &mut S) -> Vec<D> + 'a) -> Layout<'a, D, S> {
NodeBuilder::new(LayoutType::Draw(Some(Box::new(data)))).build()
}
#[doc = container_doc!()]
pub fn column<'a, D, S>(elements: Vec<Layout<'a, D, S>>) -> Layout<'a, D, S> {
NodeBuilder::new(LayoutType::Column {
spacing: 0.,
x_align: None,
y_align: None,
})
.children(elements)
.build()
}
#[doc = container_doc!()]
pub fn column_spaced<'a, D, S>(spacing: f32, elements: Vec<Layout<'a, D, S>>) -> Layout<'a, D, S> {
NodeBuilder::new(LayoutType::Column {
spacing,
x_align: None,
y_align: None,
})
.children(elements)
.build()
}
#[doc = container_doc!()]
pub fn column_aligned<'a, D, S>(align: Align, elements: Vec<Layout<'a, D, S>>) -> Layout<'a, D, S> {
let (x_align, y_align) = align.axis_aligns();
NodeBuilder::new(LayoutType::Column {
spacing: 0.,
x_align,
y_align,
})
.children(elements)
.build()
}
#[doc = container_doc!()]
pub fn column_spaced_aligned<'a, D, S>(
spacing: f32,
align: Align,
elements: Vec<Layout<'a, D, S>>,
) -> Layout<'a, D, S> {
let (x_align, y_align) = align.axis_aligns();
NodeBuilder::new(LayoutType::Column {
spacing,
x_align,
y_align,
})
.children(elements)
.build()
}
#[doc = container_doc!()]
pub fn row<'a, D, S>(elements: Vec<Layout<'a, D, S>>) -> Layout<'a, D, S> {
NodeBuilder::new(LayoutType::Row {
spacing: 0.0,
x_align: None,
y_align: None,
})
.children(elements)
.build()
}
#[doc = container_doc!()]
pub fn row_spaced<'a, D, S>(spacing: f32, elements: Vec<Layout<'a, D, S>>) -> Layout<'a, D, S> {
NodeBuilder::new(LayoutType::Row {
spacing,
x_align: None,
y_align: None,
})
.children(elements)
.build()
}
#[doc = container_doc!()]
pub fn row_aligned<'a, D, S>(align: Align, elements: Vec<Layout<'a, D, S>>) -> Layout<'a, D, S> {
let (x_align, y_align) = align.axis_aligns();
NodeBuilder::new(LayoutType::Row {
spacing: 0.0,
x_align,
y_align,
})
.children(elements)
.build()
}
#[doc = container_doc!()]
pub fn row_spaced_aligned<'a, D, S>(
spacing: f32,
align: Align,
elements: Vec<Layout<'a, D, S>>,
) -> Layout<'a, D, S> {
let (x_align, y_align) = align.axis_aligns();
NodeBuilder::new(LayoutType::Row {
spacing,
x_align,
y_align,
})
.children(elements)
.build()
}
#[doc = container_doc!()]
pub fn stack<'a, D, S>(elements: Vec<Layout<'a, D, S>>) -> Layout<'a, D, S> {
NodeBuilder::new(LayoutType::Stack {
x_align: None,
y_align: None,
})
.children(elements)
.build()
}
#[doc = container_doc!()]
pub fn stack_aligned<'a, D, S>(align: Align, elements: Vec<Layout<'a, D, S>>) -> Layout<'a, D, S> {
let (x_align, y_align) = align.axis_aligns();
NodeBuilder::new(LayoutType::Stack { x_align, y_align })
.children(elements)
.build()
}
pub fn space<'a, D, S>() -> Layout<'a, D, S> {
NodeBuilder::new(LayoutType::Space).build()
}
pub fn empty<'a, D, S>() -> Layout<'a, D, S> {
NodeBuilder::new(LayoutType::Empty).build()
}
impl<'a, D, S> Layout<'a, D, S> {
pub fn pad(self, amount: f32) -> Layout<'a, D, S> {
NodeBuilder::new(LayoutType::Padding {
leading: amount,
trailing: amount,
top: amount,
bottom: amount,
})
.child(self)
.build()
}
pub fn pad_x(self, amount: f32) -> Self {
NodeBuilder::new(LayoutType::Padding {
leading: amount,
trailing: amount,
top: 0.,
bottom: 0.,
})
.child(self)
.build()
}
pub fn pad_y(self, amount: f32) -> Self {
NodeBuilder::new(LayoutType::Padding {
leading: 0.,
trailing: 0.,
top: amount,
bottom: amount,
})
.child(self)
.build()
}
pub fn pad_top(self, amount: f32) -> Self {
NodeBuilder::new(LayoutType::Padding {
leading: 0.,
trailing: 0.,
top: amount,
bottom: 0.,
})
.child(self)
.build()
}
pub fn pad_bottom(self, amount: f32) -> Self {
NodeBuilder::new(LayoutType::Padding {
leading: 0.,
trailing: 0.,
top: 0.,
bottom: amount,
})
.child(self)
.build()
}
pub fn pad_leading(self, amount: f32) -> Self {
NodeBuilder::new(LayoutType::Padding {
leading: amount,
trailing: 0.,
top: 0.,
bottom: 0.,
})
.child(self)
.build()
}
pub fn pad_trailing(self, amount: f32) -> Self {
NodeBuilder::new(LayoutType::Padding {
leading: 0.,
trailing: amount,
top: 0.,
bottom: 0.,
})
.child(self)
.build()
}
pub fn offset(self, x: f32, y: f32) -> Layout<'a, D, S> {
NodeBuilder::new(LayoutType::Offset { x, y })
.child(self)
.build()
}
pub fn offset_x(self, x: f32) -> Layout<'a, D, S> {
NodeBuilder::new(LayoutType::Offset { x, y: 0. })
.child(self)
.build()
}
pub fn offset_y(self, y: f32) -> Layout<'a, D, S> {
NodeBuilder::new(LayoutType::Offset { x: 0., y })
.child(self)
.build()
}
}
impl<'a, D, S> Layout<'a, D, S> {
pub fn layer(mut self, layer: i32) -> Layout<'a, D, S> {
self.layer = Some(layer);
self
}
pub fn width(mut self, width: f32) -> Self {
self.constraints.width.lower = Some(width);
self.constraints.width.upper = Some(width);
self
}
pub fn height(mut self, height: f32) -> Self {
self.constraints.height.lower = Some(height);
self.constraints.height.upper = Some(height);
self
}
pub fn expand_x(mut self) -> Self {
self.constraints.expand_x = true;
self
}
pub fn expand_y(mut self) -> Self {
self.constraints.expand_y = true;
self
}
pub fn expand(self) -> Self {
self.expand_x().expand_y()
}
pub fn inert(self) -> Self {
self.inert_x().inert_y()
}
pub fn inert_x(mut self) -> Self {
self.constraints.transparent_x = true;
self
}
pub fn inert_y(mut self) -> Self {
self.constraints.transparent_y = true;
self
}
pub fn width_range<R>(mut self, range: R) -> Self
where
R: RangeBounds<f32>,
{
let (width_min, width_max) = Self::extract_bounds(range);
self.constraints.width.lower = width_min;
self.constraints.width.upper = width_max;
self
}
pub fn height_range<R>(mut self, range: R) -> Self
where
R: RangeBounds<f32>,
{
let (height_min, height_max) = Self::extract_bounds(range);
self.constraints.height.lower = height_min;
self.constraints.height.upper = height_max;
self
}
fn extract_bounds<R: RangeBounds<f32>>(range: R) -> (Option<f32>, Option<f32>) {
let min = match range.start_bound() {
std::ops::Bound::Included(bound) | std::ops::Bound::Excluded(bound) => Some(*bound),
std::ops::Bound::Unbounded => None,
};
let max = match range.end_bound() {
std::ops::Bound::Included(bound) | std::ops::Bound::Excluded(bound) => Some(*bound),
std::ops::Bound::Unbounded => None,
};
(min, max)
}
pub fn align(mut self, align: Align) -> Self {
let (x_align, y_align) = align.axis_aligns();
if let Some(x) = x_align {
self.constraints.x_align = Some(x);
}
if let Some(y) = y_align {
self.constraints.y_align = Some(y);
}
self
}
pub fn dynamic_width(mut self, f: impl Fn(f32, &mut S) -> f32 + 'a) -> Layout<'a, D, S> {
self.dynamic_constraints.width = Some(Box::new(f));
self
}
pub fn dynamic_height(mut self, f: impl Fn(f32, &mut S) -> f32 + 'a) -> Layout<'a, D, S> {
self.dynamic_constraints.height = Some(Box::new(f));
self
}
pub fn aspect_width(mut self, ratio: f32) -> Self {
self.dynamic_constraints.width = Some(Box::new(move |height, _| height * ratio));
self
}
pub fn aspect_height(mut self, ratio: f32) -> Self {
self.dynamic_constraints.height = Some(Box::new(move |width, _| width / ratio));
self
}
}