GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
src/widget/container/container_layout.rs (25K)
1 use crate::widget::*;
2 use crate::context::UiContext;
3
4 pub trait ContainerLayout: crate::layout::LayoutStrategy {
5 fn box_clone_container(&self) -> Box<dyn ContainerLayout>;
6 }
7
8 impl Clone for Box<dyn ContainerLayout> {
9 fn clone(&self) -> Self {
10 self.box_clone_container()
11 }
12 }
13
14 #[derive(Debug, Clone, Copy, Default)]
15 pub struct OverlayLayout {
16 left: f32,
17 top: f32,
18 width: f32,
19 height: f32,
20 }
21
22 impl crate::layout::LayoutStrategy for OverlayLayout {
23 fn init(&mut self, left: f32, top: f32, width: f32, height: f32) {
24 self.left = left;
25 self.top = top;
26 self.width = width;
27 self.height = height;
28 }
29
30 fn allocate(&mut self, _ww: f32, _wh: f32) -> (f32, f32, f32, f32) {
31 (self.left, self.top, self.width, self.height)
32 }
33
34 fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn WidgetHost + 'static)], _ctx: &mut UiContext) -> f32 {
35 for &child_ptr in children {
36 unsafe {
37 (*child_ptr).set_rect(x, y, w, h);
38 }
39 }
40 h
41 }
42
43 fn measure(&self, constraints: LayoutConstraints, children: &[*mut (dyn WidgetHost + 'static)], ctx: &UiContext) -> Size {
44 let mut max_w = 0.0f32;
45 let mut max_h = 0.0f32;
46 for &child_ptr in children {
47 unsafe {
48 let size = (*child_ptr).measure(constraints, ctx);
49 max_w = max_w.max(size.width);
50 max_h = max_h.max(size.height);
51 }
52 }
53 Size {
54 width: max_w.clamp(constraints.min_width, constraints.max_width),
55 height: max_h.clamp(constraints.min_height, constraints.max_height),
56 }
57 }
58
59 fn box_clone(&self) -> Box<dyn crate::layout::LayoutStrategy> {
60 Box::new(*self)
61 }
62 }
63
64 impl ContainerLayout for OverlayLayout {
65 fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
66 Box::new(*self)
67 }
68 }
69
70 #[derive(Debug, Clone, Copy, Default, PartialEq)]
71 pub struct ManualLayout {
72 left: f32,
73 top: f32,
74 width: f32,
75 height: f32,
76 }
77
78 impl crate::layout::LayoutStrategy for ManualLayout {
79 fn init(&mut self, left: f32, top: f32, width: f32, height: f32) {
80 self.left = left;
81 self.top = top;
82 self.width = width;
83 self.height = height;
84 }
85
86 fn allocate(&mut self, _ww: f32, _wh: f32) -> (f32, f32, f32, f32) {
87 (self.left, self.top, self.width, self.height)
88 }
89
90 fn layout(&self, _x: f32, _y: f32, _w: f32, _h: f32, _children: &[*mut (dyn WidgetHost + 'static)], _ctx: &mut UiContext) -> f32 {
91 self.height
92 }
93
94 fn measure(&self, constraints: LayoutConstraints, _children: &[*mut (dyn WidgetHost + 'static)], _ctx: &UiContext) -> Size {
95 Size {
96 width: self.width.clamp(constraints.min_width, constraints.max_width),
97 height: self.height.clamp(constraints.min_height, constraints.max_height),
98 }
99 }
100
101 fn box_clone(&self) -> Box<dyn crate::layout::LayoutStrategy> {
102 Box::new(*self)
103 }
104 }
105
106 impl ContainerLayout for ManualLayout {
107 fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
108 Box::new(*self)
109 }
110 }
111
112
113 /// A child's content height for a strategy to allot: its preferred (intrinsic)
114 /// height, else its landed rect less the detached-label strip.
115 pub fn content_height(child: &dyn WidgetHost) -> f32 {
116 child.preferred_height().unwrap_or(child.rect().3 - child.label_strip())
117 }
118
119 /// The label row a strategy reserves above EVERY child's content: the tallest
120 /// detached-label strip among the children, zero when nothing is labeled. A block
121 /// is label + control; the strategies place blocks and put the gap between them,
122 /// in both axes, so a carve-out tab (which makes the label part of a control's
123 /// silhouette) sits a full gap from its neighbour. Reserving the row for every
124 /// child — an unlabeled one leaves it empty — keeps a row's controls level.
125 pub fn label_lead(children: &[*mut (dyn WidgetHost + 'static)]) -> f32 {
126 children.iter().map(|&c| unsafe { (*c).label_strip() }).fold(0.0, f32::max)
127 }
128
129 #[derive(Debug, Clone, Copy)]
130 pub struct VerticalLayout {
131 pub padding_x: f32,
132 pub padding_y: f32,
133 pub spacing: f32,
134 left: f32,
135 current_y: f32,
136 }
137
138 impl Default for VerticalLayout {
139 fn default() -> Self {
140 Self {
141 padding_x: 0.0,
142 padding_y: 0.0,
143 spacing: crate::layout::control_gap(),
144 left: 0.0,
145 current_y: 0.0,
146 }
147 }
148 }
149
150 impl crate::layout::LayoutStrategy for VerticalLayout {
151 fn init(&mut self, left: f32, top: f32, _width: f32, _height: f32) {
152 self.left = left;
153 self.current_y = top + self.padding_y;
154 }
155
156 fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) {
157 let x = self.left + self.padding_x;
158 let y = self.current_y;
159 self.current_y += wh + self.spacing;
160 (x, y, ww, wh)
161 }
162
163 fn get_gap(&self) -> f32 {
164 self.spacing
165 }
166
167 fn layout(&self, x: f32, y: f32, w: f32, _h: f32, children: &[*mut (dyn WidgetHost + 'static)], ctx: &mut UiContext) -> f32 {
168 let left_x = x + self.padding_x;
169 let available_w = (w - 2.0 * self.padding_x).max(1.0);
170 // Blocks: the label row (`label_lead`) then the content, the gap between
171 // blocks.
172 let lead = label_lead(children);
173 let mut current_y = y + self.padding_y;
174
175 for &child_ptr in children {
176 unsafe {
177 let child = &mut *child_ptr;
178 let ch = content_height(child);
179 let use_h = if ch > 0.0 { ch } else { 44.0 };
180 child.layout(
181 Point { x: left_x, y: current_y + lead },
182 LayoutConstraints::new(available_w, available_w, use_h, use_h),
183 ctx,
184 );
185 current_y += lead + use_h + self.spacing;
186 }
187 }
188 (current_y - y).max(0.0)
189 }
190
191 fn measure(&self, constraints: LayoutConstraints, children: &[*mut (dyn WidgetHost + 'static)], ctx: &UiContext) -> Size {
192 let mut total_h = self.padding_y * 2.0;
193 let mut max_w = 0.0f32;
194 let spacing = self.spacing;
195 let lead = label_lead(children);
196
197 for (i, &child_ptr) in children.iter().enumerate() {
198 unsafe {
199 let size = (*child_ptr).measure(constraints, ctx);
200 max_w = max_w.max(size.width);
201 total_h += lead + size.height;
202 if i > 0 {
203 total_h += spacing;
204 }
205 }
206 }
207
208 Size {
209 width: (max_w + self.padding_x * 2.0).clamp(constraints.min_width, constraints.max_width),
210 height: total_h.clamp(constraints.min_height, constraints.max_height),
211 }
212 }
213
214 fn box_clone(&self) -> Box<dyn crate::layout::LayoutStrategy> {
215 Box::new(*self)
216 }
217 }
218
219 impl ContainerLayout for VerticalLayout {
220 fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
221 Box::new(*self)
222 }
223 }
224
225 #[derive(Debug, Clone)]
226 pub struct GridLayout {
227 pub columns: usize,
228 pub gap: f32,
229 pub padding_x: f32,
230 pub padding_y: f32,
231 pub grid: Option<crate::layout::Grid>,
232 }
233
234 impl crate::layout::LayoutStrategy for GridLayout {
235 fn init(&mut self, left: f32, top: f32, width: f32, _height: f32) {
236 let count = self.columns.max(1);
237 let usable_w = (width - 2.0 * self.padding_x).max(1.0);
238 let grid = crate::layout::Grid::new(
239 left + self.padding_x,
240 top + self.padding_y,
241 usable_w,
242 usable_w / count as f32,
243 self.gap,
244 count,
245 );
246 self.grid = Some(grid);
247 }
248
249 fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) {
250 if let Some(ref mut grid) = self.grid {
251 let col = grid.next_column();
252 let x = grid.col_lefts[col];
253 let y = grid.col_heights[col];
254 grid.col_heights[col] += wh + grid.gap;
255 (x, y, grid.col_width, wh)
256 } else {
257 (0.0, 0.0, ww, wh)
258 }
259 }
260
261 fn get_column_width(&self) -> Option<f32> {
262 self.grid.as_ref().map(|g| g.col_width)
263 }
264
265 fn get_gap(&self) -> f32 {
266 self.gap
267 }
268
269 fn layout(&self, x: f32, y: f32, w: f32, _h: f32, children: &[*mut (dyn WidgetHost + 'static)], ctx: &mut UiContext) -> f32 {
270 let count = children.len();
271 if count == 0 {
272 return 0.0;
273 }
274 let cols = self.columns.max(1);
275 let total_gap = self.gap * (cols - 1) as f32;
276 let available_w = (w - 2.0 * self.padding_x - total_gap).max(1.0);
277 let col_w = available_w / cols as f32;
278
279 // Blocks (see VerticalLayout): the label row, then the content.
280 let lead = label_lead(children);
281 let mut col_heights = vec![y + self.padding_y; cols];
282
283 for &child_ptr in children {
284 unsafe {
285 let child = &mut *child_ptr;
286 let ch = content_height(child);
287 let use_h = if ch > 0.0 { ch } else { 44.0 };
288
289 let mut min_col = 0;
290 let mut min_h = col_heights[0];
291 for i in 1..cols {
292 if col_heights[i] < min_h {
293 min_h = col_heights[i];
294 min_col = i;
295 }
296 }
297
298 let cx = x + self.padding_x + min_col as f32 * (col_w + self.gap);
299 let cy = col_heights[min_col];
300 child.layout(
301 Point { x: cx, y: cy + lead },
302 LayoutConstraints::new(col_w, col_w, use_h, use_h),
303 ctx,
304 );
305 col_heights[min_col] += lead + use_h + self.gap;
306 }
307 }
308
309 let max_h = col_heights.iter().cloned().fold(0.0f32, |a, b| a.max(b));
310 (max_h - y).max(0.0)
311 }
312
313 fn measure(&self, constraints: LayoutConstraints, children: &[*mut (dyn WidgetHost + 'static)], ctx: &UiContext) -> Size {
314 let cols = self.columns.max(1);
315 let lead = label_lead(children);
316 let mut col_heights = vec![self.padding_y; cols];
317 let total_gap = self.gap * (cols - 1) as f32;
318 let available_w = (constraints.max_width - 2.0 * self.padding_x - total_gap).max(1.0);
319 let col_w = available_w / cols as f32;
320
321 let child_constraints = LayoutConstraints::new(col_w, col_w, constraints.min_height, constraints.max_height);
322
323 for &child_ptr in children {
324 unsafe {
325 let size = (*child_ptr).measure(child_constraints, ctx);
326 let mut min_col = 0;
327 let mut min_h = col_heights[0];
328 for i in 1..cols {
329 if col_heights[i] < min_h {
330 min_h = col_heights[i];
331 min_col = i;
332 }
333 }
334 col_heights[min_col] += lead + size.height + self.gap;
335 }
336 }
337
338 let max_h = col_heights.iter().cloned().fold(0.0f32, |a, b| a.max(b));
339 Size {
340 width: constraints.max_width,
341 height: (max_h + self.padding_y).clamp(constraints.min_height, constraints.max_height),
342 }
343 }
344
345 fn box_clone(&self) -> Box<dyn crate::layout::LayoutStrategy> {
346 Box::new(self.clone())
347 }
348 }
349
350 impl ContainerLayout for GridLayout {
351 fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
352 Box::new(self.clone())
353 }
354 }
355
356 #[derive(Debug, Clone)]
357 pub struct AdaptiveGridLayout {
358 pub min_col_width: f32,
359 pub gap: f32,
360 pub padding_x: f32,
361 pub padding_y: f32,
362 pub grid: Option<crate::layout::Grid>,
363 }
364
365 impl crate::layout::LayoutStrategy for AdaptiveGridLayout {
366 fn init(&mut self, left: f32, top: f32, width: f32, _height: f32) {
367 let usable_w = (width - 2.0 * self.padding_x).max(1.0);
368 let cols = (((usable_w + self.gap) / (self.min_col_width + self.gap)).floor().max(1.0)) as usize;
369 let grid = crate::layout::Grid::new(
370 left + self.padding_x,
371 top + self.padding_y,
372 usable_w,
373 self.min_col_width,
374 self.gap,
375 cols,
376 );
377 self.grid = Some(grid);
378 }
379
380 fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) {
381 if let Some(ref mut grid) = self.grid {
382 let col = grid.next_column();
383 let x = grid.col_lefts[col];
384 let y = grid.col_heights[col];
385 grid.col_heights[col] += wh + grid.gap;
386 (x, y, grid.col_width, wh)
387 } else {
388 (0.0, 0.0, ww, wh)
389 }
390 }
391
392 fn get_column_width(&self) -> Option<f32> {
393 self.grid.as_ref().map(|g| g.col_width)
394 }
395
396 fn get_gap(&self) -> f32 {
397 self.gap
398 }
399
400 fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn WidgetHost + 'static)], ctx: &mut UiContext) -> f32 {
401 let usable_w = (w - 2.0 * self.padding_x).max(1.0);
402 let cols = (((usable_w + self.gap) / (self.min_col_width + self.gap)).floor().max(1.0)) as usize;
403 let grid = GridLayout {
404 columns: cols,
405 gap: self.gap,
406 padding_x: self.padding_x,
407 padding_y: self.padding_y,
408 grid: None,
409 };
410 grid.layout(x, y, w, h, children, ctx)
411 }
412
413 fn measure(&self, constraints: LayoutConstraints, children: &[*mut (dyn WidgetHost + 'static)], ctx: &UiContext) -> Size {
414 let usable_w = (constraints.max_width - 2.0 * self.padding_x).max(1.0);
415 let cols = (((usable_w + self.gap) / (self.min_col_width + self.gap)).floor().max(1.0)) as usize;
416 let grid = GridLayout {
417 columns: cols,
418 gap: self.gap,
419 padding_x: self.padding_x,
420 padding_y: self.padding_y,
421 grid: None,
422 };
423 grid.measure(constraints, children, ctx)
424 }
425
426 fn box_clone(&self) -> Box<dyn crate::layout::LayoutStrategy> {
427 Box::new(self.clone())
428 }
429 }
430
431 impl ContainerLayout for AdaptiveGridLayout {
432 fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
433 Box::new(self.clone())
434 }
435 }
436
437 #[derive(Debug, Clone, Copy)]
438 pub struct ColumnsLayout {
439 pub padding_x: f32,
440 pub padding_y: f32,
441 pub spacing: f32,
442 }
443
444 impl Default for ColumnsLayout {
445 fn default() -> Self {
446 Self {
447 padding_x: crate::layout::control_gap(),
448 padding_y: crate::layout::control_gap(),
449 spacing: crate::layout::control_gap(),
450 }
451 }
452 }
453
454 impl crate::layout::LayoutStrategy for ColumnsLayout {
455 fn init(&mut self, _left: f32, _top: f32, _width: f32, _height: f32) {}
456 fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) { (0.0, 0.0, ww, wh) }
457 fn get_gap(&self) -> f32 { self.spacing }
458
459 fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn WidgetHost + 'static)], ctx: &mut UiContext) -> f32 {
460 let count = children.len();
461 if count == 0 {
462 return 0.0;
463 }
464 let total_spacing = self.spacing * (count - 1) as f32;
465 let total_padding = self.padding_x * 2.0;
466 let available_w = (w - total_padding - total_spacing).max(1.0);
467 let col_w = available_w / count as f32;
468 // Blocks: the label row above the columns' content.
469 let lead = label_lead(children);
470 let use_h = (h - 2.0 * self.padding_y - lead).max(1.0);
471 let start_y = y + self.padding_y + lead;
472
473 let mut current_x = x + self.padding_x;
474 for &child_ptr in children {
475 unsafe {
476 let child = &mut *child_ptr;
477 child.layout(
478 Point { x: current_x, y: start_y },
479 LayoutConstraints::new(col_w, col_w, use_h, use_h),
480 ctx,
481 );
482 current_x += col_w + self.spacing;
483 }
484 }
485 use_h + 2.0 * self.padding_y
486 }
487
488 fn measure(&self, constraints: LayoutConstraints, children: &[*mut (dyn WidgetHost + 'static)], ctx: &UiContext) -> Size {
489 let count = children.len();
490 if count == 0 {
491 return Size { width: constraints.min_width, height: constraints.min_height };
492 }
493 let mut max_h = 0.0f32;
494 for &child_ptr in children {
495 unsafe {
496 let size = (*child_ptr).measure(constraints, ctx);
497 max_h = max_h.max(size.height);
498 }
499 }
500 Size {
501 width: constraints.max_width,
502 height: (max_h + 2.0 * self.padding_y + label_lead(children)).clamp(constraints.min_height, constraints.max_height),
503 }
504 }
505
506 fn box_clone(&self) -> Box<dyn crate::layout::LayoutStrategy> {
507 Box::new(*self)
508 }
509 }
510
511 impl ContainerLayout for ColumnsLayout {
512 fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
513 Box::new(*self)
514 }
515 }
516
517 #[derive(Debug, Clone, Copy)]
518 pub struct MosaicLayout {
519 pub gap: f32,
520 pub padding_x: f32,
521 pub padding_y: f32,
522 }
523
524 impl Default for MosaicLayout {
525 fn default() -> Self {
526 Self {
527 gap: crate::layout::control_gap(),
528 padding_x: crate::layout::control_gap(),
529 padding_y: crate::layout::control_gap(),
530 }
531 }
532 }
533
534 struct Packer {
535 free_rects: Vec<(f32, f32, f32, f32)>, // (x, y, w, h)
536 max_w: f32,
537 max_h: f32,
538 gap: f32,
539 }
540
541 impl Packer {
542 fn new(start_x: f32, start_y: f32, max_width: f32, gap: f32) -> Self {
543 Self {
544 free_rects: vec![(start_x, start_y, max_width, 100000.0)],
545 max_w: max_width,
546 max_h: 0.0,
547 gap,
548 }
549 }
550
551 fn pack(&mut self, cw: f32, ch: f32) -> (f32, f32) {
552 let cw_clamped = cw.min(self.max_w);
553
554 let mut best_idx = None;
555 let mut best_y = f32::MAX;
556 let mut best_x = f32::MAX;
557
558 for (idx, &(rx, ry, rw, rh)) in self.free_rects.iter().enumerate() {
559 if rw >= cw_clamped && rh >= ch {
560 if ry < best_y || (ry == best_y && rx < best_x) {
561 best_y = ry;
562 best_x = rx;
563 best_idx = Some(idx);
564 }
565 }
566 }
567
568 let chosen_idx = match best_idx {
569 Some(idx) => idx,
570 None => {
571 let new_y = self.max_h + self.gap;
572 let new_rect = (self.free_rects[0].0, new_y, self.max_w, 100000.0);
573 self.free_rects.push(new_rect);
574 self.free_rects.len() - 1
575 }
576 };
577
578 let (fx, fy, fw, fh) = self.free_rects.remove(chosen_idx);
579 let px = fx;
580 let py = fy;
581
582 let rx = fx + cw_clamped + self.gap;
583 let rw = fw - cw_clamped - self.gap;
584 if rw > 0.0 && ch > 0.0 {
585 self.free_rects.push((rx, py, rw, ch));
586 }
587
588 let by = py + ch + self.gap;
589 let bh = fh - ch - self.gap;
590 if bh > 0.0 && fw > 0.0 {
591 self.free_rects.push((fx, by, fw, bh));
592 }
593
594 self.max_h = self.max_h.max(py + ch);
595
596 (px, py)
597 }
598 }
599
600 impl crate::layout::LayoutStrategy for MosaicLayout {
601 fn init(&mut self, _left: f32, _top: f32, _width: f32, _height: f32) {}
602 fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) { (0.0, 0.0, ww, wh) }
603 fn get_gap(&self) -> f32 { self.gap }
604
605 fn layout(&self, x: f32, y: f32, w: f32, _h: f32, children: &[*mut (dyn WidgetHost + 'static)], ctx: &mut UiContext) -> f32 {
606 let count = children.len();
607 if count == 0 {
608 return 0.0;
609 }
610 let total_padding_x = self.padding_x * 2.0;
611 let available_w = (w - total_padding_x).max(1.0);
612
613 // Blocks (see VerticalLayout): each packed as label row + content.
614 let lead = label_lead(children);
615 let mut packer = Packer::new(x + self.padding_x, y + self.padding_y, available_w, self.gap);
616
617 for &child_ptr in children {
618 unsafe {
619 let child = &mut *child_ptr;
620 let child_rect = child.rect();
621 let child_w = child_rect.2;
622 let child_h = content_height(child);
623 let use_h = if child_h > 0.0 { child_h } else { 44.0 };
624
625 let (px, py) = packer.pack(child_w, lead + use_h);
626 child.layout(
627 Point { x: px, y: py + lead },
628 LayoutConstraints::new(child_w.min(available_w), child_w.min(available_w), use_h, use_h),
629 ctx,
630 );
631 }
632 }
633
634 (packer.max_h - y).max(0.0)
635 }
636
637 fn measure(&self, constraints: LayoutConstraints, children: &[*mut (dyn WidgetHost + 'static)], ctx: &UiContext) -> Size {
638 let count = children.len();
639 if count == 0 {
640 return Size { width: constraints.min_width, height: constraints.min_height };
641 }
642 let total_padding_x = self.padding_x * 2.0;
643 let available_w = (constraints.max_width - total_padding_x).max(1.0);
644
645 let lead = label_lead(children);
646 let mut packer = Packer::new(self.padding_x, self.padding_y, available_w, self.gap);
647
648 for &child_ptr in children {
649 unsafe {
650 let size = (*child_ptr).measure(constraints, ctx);
651 packer.pack(size.width, lead + size.height);
652 }
653 }
654
655 Size {
656 width: constraints.max_width,
657 height: (packer.max_h + self.padding_y).clamp(constraints.min_height, constraints.max_height),
658 }
659 }
660
661 fn box_clone(&self) -> Box<dyn crate::layout::LayoutStrategy> {
662 Box::new(*self)
663 }
664 }
665
666 impl ContainerLayout for MosaicLayout {
667 fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
668 Box::new(*self)
669 }
670 }
671
672 #[derive(Debug, Clone, Copy)]
673 pub struct ReverseMosaicLayout {
674 pub gap: f32,
675 pub padding_x: f32,
676 pub padding_y: f32,
677 }
678
679 impl Default for ReverseMosaicLayout {
680 fn default() -> Self {
681 Self {
682 gap: crate::layout::control_gap(),
683 padding_x: crate::layout::control_gap(),
684 padding_y: crate::layout::control_gap(),
685 }
686 }
687 }
688
689 impl crate::layout::LayoutStrategy for ReverseMosaicLayout {
690 fn init(&mut self, _left: f32, _top: f32, _width: f32, _height: f32) {}
691 fn allocate(&mut self, ww: f32, wh: f32) -> (f32, f32, f32, f32) { (0.0, 0.0, ww, wh) }
692 fn get_gap(&self) -> f32 { self.gap }
693
694 fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn WidgetHost + 'static)], ctx: &mut UiContext) -> f32 {
695 let count = children.len();
696 if count == 0 {
697 return 0.0;
698 }
699 let total_padding_x = self.padding_x * 2.0;
700 let available_w = (w - total_padding_x).max(1.0);
701
702 let lead = label_lead(children);
703 let mut packer = Packer::new(self.padding_x, self.padding_y, available_w, self.gap);
704 let mut temp_positions = Vec::with_capacity(count);
705
706 for &child_ptr in children {
707 unsafe {
708 let child = &mut *child_ptr;
709 let child_rect = child.rect();
710 let child_w = child_rect.2;
711 let child_h = content_height(child);
712 let use_h = if child_h > 0.0 { child_h } else { 44.0 };
713
714 // The block (label row + content) is packed; the content lands
715 // `lead` below its top, scaled with the rest.
716 let (px, py) = packer.pack(child_w, lead + use_h);
717 temp_positions.push((px, py, child_w, lead + use_h));
718 }
719 }
720
721 let mut x_min = f32::MAX;
722 let mut x_max = f32::MIN;
723 let mut y_min = f32::MAX;
724 let mut y_max = f32::MIN;
725
726 for &(px, py, pw, ph) in &temp_positions {
727 x_min = x_min.min(px);
728 x_max = x_max.max(px + pw);
729 y_min = y_min.min(py);
730 y_max = y_max.max(py + ph);
731 }
732
733 let src_w = (x_max - x_min).max(1.0);
734 let src_h = (y_max - y_min).max(1.0);
735
736 let dst_w = available_w;
737 let dst_h = (h - 2.0 * self.padding_y).max(1.0);
738
739 let scale_x = dst_w / src_w;
740 let scale_y = dst_h / src_h;
741
742 for (i, &child_ptr) in children.iter().enumerate() {
743 unsafe {
744 let child = &mut *child_ptr;
745 let (px, py, pw, ph) = temp_positions[i];
746
747 let new_x = x + self.padding_x + (px - x_min) * scale_x;
748 let new_y = y + self.padding_y + (py - y_min) * scale_y + lead * scale_y;
749 let new_w = pw * scale_x;
750 let new_h = (ph - lead) * scale_y;
751
752 child.layout(
753 Point { x: new_x, y: new_y },
754 LayoutConstraints::new(new_w, new_w, new_h, new_h),
755 ctx,
756 );
757 }
758 }
759
760 h
761 }
762
763 fn measure(&self, constraints: LayoutConstraints, _children: &[*mut (dyn WidgetHost + 'static)], _ctx: &UiContext) -> Size {
764 Size {
765 width: constraints.max_width,
766 height: constraints.max_height,
767 }
768 }
769
770 fn box_clone(&self) -> Box<dyn crate::layout::LayoutStrategy> {
771 Box::new(*self)
772 }
773 }
774
775 impl ContainerLayout for ReverseMosaicLayout {
776 fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
777 Box::new(*self)
778 }
779 }