tessera_ui_basic_components/
boxed.rs1use derive_builder::Builder;
2use tessera_ui::{ComputedData, Constraint, DimensionValue, Px, PxPosition, place_node};
3use tessera_ui_macros::tessera;
4
5use crate::alignment::Alignment;
6
7pub use crate::boxed_ui;
8
9#[derive(Clone, Debug, Builder)]
11#[builder(pattern = "owned")]
12pub struct BoxedArgs {
13 #[builder(default)]
15 pub alignment: Alignment,
16 #[builder(default = "DimensionValue::Wrap { min: None, max: None }")]
18 pub width: DimensionValue,
19 #[builder(default = "DimensionValue::Wrap { min: None, max: None }")]
21 pub height: DimensionValue,
22}
23
24impl Default for BoxedArgs {
25 fn default() -> Self {
26 BoxedArgsBuilder::default().build().unwrap()
27 }
28}
29
30pub struct BoxedItem {
32 pub child: Box<dyn FnOnce() + Send + Sync>,
33}
34
35impl BoxedItem {
36 pub fn new(child: Box<dyn FnOnce() + Send + Sync>) -> Self {
37 BoxedItem { child }
38 }
39}
40
41pub trait AsBoxedItem {
43 fn into_boxed_item(self) -> BoxedItem;
44}
45
46impl AsBoxedItem for BoxedItem {
47 fn into_boxed_item(self) -> BoxedItem {
48 self
49 }
50}
51
52impl<F: FnOnce() + Send + Sync + 'static> AsBoxedItem for F {
53 fn into_boxed_item(self) -> BoxedItem {
54 BoxedItem {
55 child: Box::new(self),
56 }
57 }
58}
59
60#[tessera]
63pub fn boxed<const N: usize>(args: BoxedArgs, children_items_input: [impl AsBoxedItem; N]) {
64 let children_items: [BoxedItem; N] =
65 children_items_input.map(|item_input| item_input.into_boxed_item());
66
67 let mut child_closures = Vec::with_capacity(N);
68
69 for child_item in children_items {
70 child_closures.push(child_item.child);
71 }
72
73 measure(Box::new(move |input| {
74 let boxed_intrinsic_constraint = Constraint::new(args.width, args.height);
75 let effective_constraint = boxed_intrinsic_constraint.merge(input.parent_constraint);
76
77 let mut max_child_width = Px(0);
78 let mut max_child_height = Px(0);
79 let mut children_sizes = vec![None; N];
80
81 for i in 0..N {
82 let child_id = input.children_ids[i];
83 let child_result = tessera_ui::measure_node(
84 child_id,
85 &effective_constraint,
86 input.tree,
87 input.metadatas,
88 input.compute_resource_manager.clone(),
89 input.gpu,
90 )?;
91 max_child_width = max_child_width.max(child_result.width);
92 max_child_height = max_child_height.max(child_result.height);
93 children_sizes[i] = Some(child_result);
94 }
95
96 let final_width = match effective_constraint.width {
97 DimensionValue::Fixed(w) => w,
98 DimensionValue::Fill { min, max } => {
99 let mut w = max.unwrap_or(max_child_width);
100 if let Some(min_w) = min {
101 w = w.max(min_w);
102 }
103 w
104 }
105 DimensionValue::Wrap { min, max } => {
106 let mut w = max_child_width;
107 if let Some(min_w) = min {
108 w = w.max(min_w);
109 }
110 if let Some(max_w) = max {
111 w = w.min(max_w);
112 }
113 w
114 }
115 };
116
117 let final_height = match effective_constraint.height {
118 DimensionValue::Fixed(h) => h,
119 DimensionValue::Fill { min, max } => {
120 let mut h = max.unwrap_or(max_child_height);
121 if let Some(min_h) = min {
122 h = h.max(min_h);
123 }
124 h
125 }
126 DimensionValue::Wrap { min, max } => {
127 let mut h = max_child_height;
128 if let Some(min_h) = min {
129 h = h.max(min_h);
130 }
131 if let Some(max_h) = max {
132 h = h.min(max_h);
133 }
134 h
135 }
136 };
137
138 for (i, child_size_opt) in children_sizes.iter().enumerate() {
139 if let Some(child_size) = child_size_opt {
140 let child_id = input.children_ids[i];
141
142 let (x, y) = match args.alignment {
143 Alignment::TopStart => (Px(0), Px(0)),
144 Alignment::TopCenter => ((final_width - child_size.width) / 2, Px(0)),
145 Alignment::TopEnd => (final_width - child_size.width, Px(0)),
146 Alignment::CenterStart => (Px(0), (final_height - child_size.height) / 2),
147 Alignment::Center => (
148 (final_width - child_size.width) / 2,
149 (final_height - child_size.height) / 2,
150 ),
151 Alignment::CenterEnd => (
152 final_width - child_size.width,
153 (final_height - child_size.height) / 2,
154 ),
155 Alignment::BottomStart => (Px(0), final_height - child_size.height),
156 Alignment::BottomCenter => (
157 (final_width - child_size.width) / 2,
158 final_height - child_size.height,
159 ),
160 Alignment::BottomEnd => (
161 final_width - child_size.width,
162 final_height - child_size.height,
163 ),
164 };
165 place_node(child_id, PxPosition::new(x, y), input.metadatas);
166 }
167 }
168
169 Ok(ComputedData {
170 width: final_width,
171 height: final_height,
172 })
173 }));
174
175 for child_closure in child_closures {
176 child_closure();
177 }
178}
179
180#[macro_export]
182macro_rules! boxed_ui {
183 ($args:expr $(, $child:expr)* $(,)?) => {
184 {
185 use $crate::boxed::AsBoxedItem;
186 $crate::boxed::boxed($args, [
187 $(
188 $child.into_boxed_item()
189 ),*
190 ])
191 }
192 };
193}