1use std::hash::{Hash, Hasher};
5
6use frus_core::{Point, Rect, Scene, Size};
7use frus_layout::{Dimension, Style};
8
9use crate::interaction::Status;
10use crate::theme::Theme;
11use crate::widget::{FillAxes, Widget};
12
13#[derive(Clone, Copy, Debug, PartialEq)]
15pub enum ChildAxis {
16 Free,
18 AtMost(f32),
21 Exactly(f32),
24}
25
26#[derive(Clone, Copy, Debug, PartialEq)]
28pub struct ChildConstraints {
29 pub width: ChildAxis,
31 pub height: ChildAxis,
33}
34
35impl ChildConstraints {
36 pub fn free() -> Self {
38 Self {
39 width: ChildAxis::Free,
40 height: ChildAxis::Free,
41 }
42 }
43
44 pub fn loose(size: Size) -> Self {
46 Self {
47 width: ChildAxis::AtMost(size.width),
48 height: ChildAxis::AtMost(size.height),
49 }
50 }
51
52 pub fn tight(size: Size) -> Self {
54 Self {
55 width: ChildAxis::Exactly(size.width),
56 height: ChildAxis::Exactly(size.height),
57 }
58 }
59
60 pub fn width(mut self, width: ChildAxis) -> Self {
62 self.width = width;
63 self
64 }
65
66 pub fn height(mut self, height: ChildAxis) -> Self {
68 self.height = height;
69 self
70 }
71}
72
73pub(crate) fn key_of(key: impl Hash) -> u64 {
75 let mut hasher = std::collections::hash_map::DefaultHasher::new();
76 key.hash(&mut hasher);
77 hasher.finish()
78}
79
80pub struct ChildLayout<'a> {
83 keys: &'a [u64],
84 measure: &'a dyn Fn(usize, ChildConstraints) -> Vec<Rect>,
85 pub(crate) laid: Vec<Option<Vec<Rect>>>,
86 pub(crate) at: Vec<Point>,
87}
88
89impl<'a> ChildLayout<'a> {
90 pub(crate) fn new(
91 keys: &'a [u64],
92 measure: &'a dyn Fn(usize, ChildConstraints) -> Vec<Rect>,
93 ) -> Self {
94 Self {
95 keys,
96 measure,
97 laid: vec![None; keys.len()],
98 at: vec![Point::new(0.0, 0.0); keys.len()],
99 }
100 }
101
102 fn index(&self, key: impl Hash) -> Option<usize> {
103 let key = key_of(key);
104 self.keys.iter().position(|k| *k == key)
105 }
106
107 pub fn has_child(&self, key: impl Hash) -> bool {
110 self.index(key).is_some()
111 }
112
113 pub fn layout_child(&mut self, key: impl Hash, constraints: ChildConstraints) -> Size {
121 let i = self
122 .index(key)
123 .expect("layout_child: no child was given under this key");
124 let rects = (self.measure)(i, constraints);
125 let size = rects
126 .first()
127 .map(|r| Size::new(r.width, r.height))
128 .unwrap_or(Size::new(0.0, 0.0));
129 self.laid[i] = Some(rects);
130 size
131 }
132
133 pub fn position_child(&mut self, key: impl Hash, at: Point) {
140 let i = self
141 .index(key)
142 .expect("position_child: no child was given under this key");
143 self.at[i] = at;
144 }
145}
146
147pub type LayoutFn = dyn Fn(Size, &mut ChildLayout<'_>);
150
151pub struct CustomLayout<'a> {
153 pub delegate: &'a LayoutFn,
155 pub keys: &'a [u64],
157}
158
159pub struct CustomMultiChildLayout<Msg = crate::callback::Callback> {
188 delegate: Box<LayoutFn>,
189 keys: Vec<u64>,
190 children: Vec<Box<dyn Widget<Msg>>>,
191 width: Option<f32>,
192 height: Option<f32>,
193}
194
195impl<Msg> CustomMultiChildLayout<Msg> {
196 pub fn new(delegate: impl Fn(Size, &mut ChildLayout<'_>) + 'static) -> Self {
198 Self {
199 delegate: Box::new(delegate),
200 keys: Vec::new(),
201 children: Vec::new(),
202 width: None,
203 height: None,
204 }
205 }
206
207 pub fn child(mut self, key: impl Hash, child: impl Widget<Msg> + 'static) -> Self {
210 self.keys.push(key_of(key));
211 self.children.push(Box::new(child));
212 self
213 }
214
215 pub fn width(mut self, width: f32) -> Self {
217 self.width = Some(width.max(0.0));
218 self
219 }
220
221 pub fn height(mut self, height: f32) -> Self {
223 self.height = Some(height.max(0.0));
224 self
225 }
226}
227
228impl<Msg: Clone> Widget<Msg> for CustomMultiChildLayout<Msg> {
229 fn style(&self) -> Style {
230 let dimension = |extent: Option<f32>| extent.map_or(Dimension::Auto, Dimension::Length);
231 Style {
232 width: dimension(self.width),
233 height: dimension(self.height),
234 ..Style::default()
235 }
236 }
237
238 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
239 &self.children
240 }
241
242 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
243
244 fn on_click(&self) -> Option<Msg> {
245 None
246 }
247
248 fn custom_layout(&self) -> Option<CustomLayout<'_>> {
249 Some(CustomLayout {
250 delegate: self.delegate.as_ref(),
251 keys: &self.keys,
252 })
253 }
254
255 fn fill_axes(&self, _theme: &Theme) -> FillAxes {
258 FillAxes {
259 horizontal: self.width.is_none(),
260 vertical: self.height.is_none(),
261 }
262 }
263
264 fn debug_name(&self) -> &'static str {
265 "CustomMultiChildLayout"
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272 use crate::{build_ui, Container, Flex, Runtime, Text};
273 use frus_core::{Color, Primitive};
274
275 const RED: Color = Color::rgb(1.0, 0.0, 0.0);
276 const BLUE: Color = Color::rgb(0.0, 0.0, 1.0);
277
278 fn block(width: f32, height: f32, color: Color) -> Container<()> {
279 Container::new().width(width).height(height).color(color)
280 }
281
282 fn painted(root: &dyn Widget<()>, size: Size, color: Color) -> Vec<Rect> {
284 build_ui(root, size, &Runtime::default(), &Theme::dark())
285 .scene()
286 .primitives()
287 .iter()
288 .filter_map(|p| match p {
289 Primitive::Rect { rect, color: c, .. } if *c == color => Some(*rect),
290 _ => None,
291 })
292 .collect()
293 }
294
295 #[test]
298 fn a_child_is_placed_against_another_childs_size() {
299 let root = CustomMultiChildLayout::<()>::new(|size, layout| {
300 let red = layout.layout_child("red", ChildConstraints::free());
301 layout.position_child("red", Point::new(10.0, 10.0));
302 let blue = layout.layout_child("blue", ChildConstraints::free());
303 layout.position_child(
304 "blue",
305 Point::new(size.width - blue.width, 10.0 + red.height),
306 );
307 })
308 .child("red", block(40.0, 30.0, RED))
309 .child("blue", block(20.0, 20.0, BLUE));
310 let size = Size::new(200.0, 100.0);
311 let red = painted(&root, size, RED)[0];
312 let blue = painted(&root, size, BLUE)[0];
313 assert_eq!(
314 (red.x, red.y, red.width, red.height),
315 (10.0, 10.0, 40.0, 30.0)
316 );
317 assert_eq!((blue.x, blue.y), (180.0, 40.0));
318 }
319
320 #[test]
323 fn a_child_left_out_is_not_shown() {
324 let root = CustomMultiChildLayout::<()>::new(|_, layout| {
325 layout.layout_child("red", ChildConstraints::free());
326 })
327 .child("red", block(40.0, 30.0, RED))
328 .child("blue", block(20.0, 20.0, BLUE));
329 let size = Size::new(200.0, 100.0);
330 assert_eq!(painted(&root, size, RED)[0].x, 0.0);
331 assert!(painted(&root, size, BLUE).is_empty());
332 }
333
334 #[test]
338 fn its_size_is_the_room_or_its_own() {
339 let seen = std::rc::Rc::new(std::cell::Cell::new(Size::new(0.0, 0.0)));
340 let told = seen.clone();
341 let root = Flex::<()>::column()
342 .width(160.0)
343 .child(
344 CustomMultiChildLayout::new(move |size, layout| {
345 told.set(size);
346 layout.layout_child("red", ChildConstraints::free());
347 })
348 .height(50.0)
349 .child("red", block(40.0, 300.0, RED)),
350 )
351 .child(block(10.0, 10.0, BLUE));
352 let blue = painted(&root, Size::new(300.0, 400.0), BLUE)[0];
353 assert_eq!(blue.y, 50.0);
354 assert_eq!(seen.get(), Size::new(160.0, 50.0));
355 }
356
357 #[test]
360 fn exact_and_at_most_constraints() {
361 let words = "one two three four five six seven eight nine ten eleven twelve";
362 let root = CustomMultiChildLayout::<()>::new(|_, layout| {
363 layout.layout_child("red", ChildConstraints::tight(Size::new(70.0, 30.0)));
364 let text =
365 layout.layout_child("text", ChildConstraints::loose(Size::new(120.0, 400.0)));
366 assert!(text.width <= 120.0 && text.height > 50.0, "{text:?}");
367 layout.position_child("text", Point::new(0.0, 40.0));
368 })
369 .child("red", Container::new().color(RED))
370 .child("text", Text::new(words));
371 let red = painted(&root, Size::new(300.0, 300.0), RED)[0];
372 assert_eq!((red.width, red.height), (70.0, 30.0));
373 }
374
375 #[test]
377 fn a_placed_child_takes_taps_where_it_is() {
378 let root = CustomMultiChildLayout::<()>::new(|_, layout| {
379 layout.layout_child("button", ChildConstraints::free());
380 layout.position_child("button", Point::new(100.0, 50.0));
381 })
382 .child("button", block(40.0, 20.0, RED).on_click(()));
383 let ui = build_ui(
384 &root,
385 Size::new(200.0, 100.0),
386 &Runtime::default(),
387 &Theme::dark(),
388 );
389 assert!(ui.hit(Point::new(110.0, 60.0)).is_some());
390 assert!(ui.hit(Point::new(10.0, 10.0)).is_none());
391 }
392
393 #[test]
395 #[should_panic(expected = "no child was given under this key")]
396 fn an_unknown_key_panics() {
397 let root = CustomMultiChildLayout::<()>::new(|_, layout| {
398 assert!(!layout.has_child("missing"));
399 layout.layout_child("missing", ChildConstraints::free());
400 });
401 painted(&root, Size::new(100.0, 100.0), RED);
402 }
403
404 #[test]
406 fn a_keyed_layout_still_places_its_children() {
407 let root = crate::keyed(
408 7,
409 CustomMultiChildLayout::<()>::new(|_, layout| {
410 layout.layout_child("red", ChildConstraints::free());
411 layout.position_child("red", Point::new(30.0, 20.0));
412 })
413 .child("red", block(40.0, 30.0, RED)),
414 );
415 let red = painted(&root, Size::new(200.0, 100.0), RED)[0];
416 assert_eq!((red.x, red.y), (30.0, 20.0));
417 }
418}