git.lucas.co / cce-ui
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 }