use derive_setters::Setters;
use tessera_ui::{
ComputedData, Constraint, DimensionValue, LayoutInput, LayoutOutput, LayoutSpec,
MeasurementError, Modifier, Px, PxPosition, tessera,
};
use crate::alignment::Alignment;
#[derive(Clone, Debug, Setters)]
pub struct BoxedArgs {
pub alignment: Alignment,
pub modifier: Modifier,
}
impl Default for BoxedArgs {
fn default() -> Self {
Self {
alignment: Alignment::default(),
modifier: Modifier::new(),
}
}
}
pub struct BoxedScope<'a> {
child_closures: &'a mut Vec<Box<dyn FnOnce() + Send + Sync>>,
child_alignments: &'a mut Vec<Option<Alignment>>,
}
impl<'a> BoxedScope<'a> {
pub fn child<F>(&mut self, child_closure: F)
where
F: FnOnce() + Send + Sync + 'static,
{
self.child_closures.push(Box::new(child_closure));
self.child_alignments.push(None);
}
pub fn child_with_alignment<F>(&mut self, alignment: Alignment, child_closure: F)
where
F: FnOnce() + Send + Sync + 'static,
{
self.child_closures.push(Box::new(child_closure));
self.child_alignments.push(Some(alignment));
}
}
fn resolve_final_dimension(dv: DimensionValue, largest_child: Px) -> Px {
match dv {
DimensionValue::Fixed(v) => v,
DimensionValue::Fill { min, max } => {
let Some(max) = max else {
panic!(
"Seems that you are trying to fill an infinite dimension, which is not allowed\nboxed constraint = {dv:?}"
);
};
let mut v = max.max(largest_child);
if let Some(min_v) = min {
v = v.max(min_v);
}
v
}
DimensionValue::Wrap { min, max } => {
let mut v = largest_child;
if let Some(min_v) = min {
v = v.max(min_v);
}
if let Some(max_v) = max {
v = v.min(max_v);
}
v
}
}
}
fn center_axis(container: Px, child: Px) -> Px {
(container - child) / 2
}
fn compute_child_offset(
alignment: Alignment,
container_w: Px,
container_h: Px,
child_w: Px,
child_h: Px,
) -> (Px, Px) {
match alignment {
Alignment::TopStart => (Px(0), Px(0)),
Alignment::TopCenter => (center_axis(container_w, child_w), Px(0)),
Alignment::TopEnd => (container_w - child_w, Px(0)),
Alignment::CenterStart => (Px(0), center_axis(container_h, child_h)),
Alignment::Center => (
center_axis(container_w, child_w),
center_axis(container_h, child_h),
),
Alignment::CenterEnd => (container_w - child_w, center_axis(container_h, child_h)),
Alignment::BottomStart => (Px(0), container_h - child_h),
Alignment::BottomCenter => (center_axis(container_w, child_w), container_h - child_h),
Alignment::BottomEnd => (container_w - child_w, container_h - child_h),
}
}
#[tessera]
pub fn boxed<F>(args: BoxedArgs, scope_config: F)
where
F: FnOnce(&mut BoxedScope),
{
let modifier = args.modifier;
let mut child_closures: Vec<Box<dyn FnOnce() + Send + Sync>> = Vec::new();
let mut child_alignments: Vec<Option<Alignment>> = Vec::new();
{
let mut scope = BoxedScope {
child_closures: &mut child_closures,
child_alignments: &mut child_alignments,
};
scope_config(&mut scope);
}
modifier.run(move || boxed_inner(args, child_closures, child_alignments));
}
#[tessera]
fn boxed_inner(
args: BoxedArgs,
child_closures: Vec<Box<dyn FnOnce() + Send + Sync>>,
child_alignments: Vec<Option<Alignment>>,
) {
layout(BoxedLayout {
alignment: args.alignment,
child_alignments,
});
for child_closure in child_closures {
child_closure();
}
}
#[derive(Clone, PartialEq)]
struct BoxedLayout {
alignment: Alignment,
child_alignments: Vec<Option<Alignment>>,
}
impl LayoutSpec for BoxedLayout {
fn measure(
&self,
input: &LayoutInput<'_>,
output: &mut LayoutOutput<'_>,
) -> Result<ComputedData, MeasurementError> {
let n = self.child_alignments.len();
debug_assert_eq!(
input.children_ids().len(),
n,
"Mismatch between children defined in scope and runtime children count"
);
let effective_constraint = Constraint::new(
input.parent_constraint().width(),
input.parent_constraint().height(),
);
let mut max_child_width = Px(0);
let mut max_child_height = Px(0);
let mut children_sizes = vec![None; n];
let children_to_measure: Vec<_> = input
.children_ids()
.iter()
.map(|&child_id| (child_id, effective_constraint))
.collect();
let children_results = input.measure_children(children_to_measure)?;
for (i, &child_id) in input.children_ids().iter().enumerate().take(n) {
if let Some(child_result) = children_results.get(&child_id) {
max_child_width = max_child_width.max(child_result.width);
max_child_height = max_child_height.max(child_result.height);
children_sizes[i] = Some(*child_result);
}
}
let final_width = resolve_final_dimension(effective_constraint.width, max_child_width);
let final_height = resolve_final_dimension(effective_constraint.height, max_child_height);
for (i, child_size_opt) in children_sizes.iter().enumerate() {
if let Some(child_size) = child_size_opt {
let child_id = input.children_ids()[i];
let child_alignment = self.child_alignments[i].unwrap_or(self.alignment);
let (x, y) = compute_child_offset(
child_alignment,
final_width,
final_height,
child_size.width,
child_size.height,
);
output.place_child(child_id, PxPosition::new(x, y));
}
}
Ok(ComputedData {
width: final_width,
height: final_height,
})
}
}