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 (19.6K)

  1 use crate::widget::*;
  2 use crate::context::UiContext;
  3 
  4 /// How a container places its children: a rect in, every child's rect set, the height used
  5 /// out. The gallery's Layout dropdown shows each.
  6 pub trait ContainerLayout: std::fmt::Debug {
  7     /// Place `children` in the rect at (`x`, `y`), `w` by `h`; returns the height they take.
  8     fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn WidgetHost + 'static)], ctx: &mut UiContext) -> f32;
  9     /// What `children` need under `constraints`.
 10     fn measure(&self, constraints: LayoutConstraints, children: &[*mut (dyn WidgetHost + 'static)], ctx: &UiContext) -> Size;
 11     fn box_clone_container(&self) -> Box<dyn ContainerLayout>;
 12 }
 13 
 14 impl Clone for Box<dyn ContainerLayout> {
 15     fn clone(&self) -> Self {
 16         self.box_clone_container()
 17     }
 18 }
 19 
 20 /// Every child over the whole rect.
 21 #[derive(Debug, Clone, Copy, Default)]
 22 pub struct OverlayLayout;
 23 
 24 impl ContainerLayout for OverlayLayout {
 25     fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn WidgetHost + 'static)], _ctx: &mut UiContext) -> f32 {
 26         for &child_ptr in children {
 27             unsafe {
 28                 (*child_ptr).set_rect(x, y, w, h);
 29             }
 30         }
 31         h
 32     }
 33 
 34     fn measure(&self, constraints: LayoutConstraints, children: &[*mut (dyn WidgetHost + 'static)], ctx: &UiContext) -> Size {
 35         let mut max_w = 0.0f32;
 36         let mut max_h = 0.0f32;
 37         for &child_ptr in children {
 38             unsafe {
 39                 let size = (*child_ptr).measure(constraints, ctx);
 40                 max_w = max_w.max(size.width);
 41                 max_h = max_h.max(size.height);
 42             }
 43         }
 44         Size {
 45             width: max_w.clamp(constraints.min_width, constraints.max_width),
 46             height: max_h.clamp(constraints.min_height, constraints.max_height),
 47         }
 48     }
 49 
 50     fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
 51         Box::new(*self)
 52     }
 53 }
 54 
 55 /// A child's content height for a strategy to allot: its preferred (intrinsic)
 56 /// height, else its landed rect less the detached-label strip.
 57 pub fn content_height(child: &dyn WidgetHost) -> f32 {
 58     child.preferred_height().unwrap_or(child.rect().3 - child.label_strip())
 59 }
 60 
 61 /// The label row a strategy reserves above EVERY child's content: the tallest
 62 /// detached-label strip among the children, zero when nothing is labeled. A block
 63 /// is label + control; the strategies place blocks and put the gap between them,
 64 /// in both axes, so a carve-out tab (which makes the label part of a control's
 65 /// silhouette) sits a full gap from its neighbour. Reserving the row for every
 66 /// child — an unlabeled one leaves it empty — keeps a row's controls level.
 67 pub fn label_lead(children: &[*mut (dyn WidgetHost + 'static)]) -> f32 {
 68     children.iter().map(|&c| unsafe { (*c).label_strip() }).fold(0.0, f32::max)
 69 }
 70 
 71 #[derive(Debug, Clone, Copy)]
 72 pub struct VerticalLayout {
 73     pub padding_x: f32,
 74     pub padding_y: f32,
 75     pub spacing: f32,
 76 }
 77 
 78 impl Default for VerticalLayout {
 79     fn default() -> Self {
 80         Self {
 81             padding_x: 0.0,
 82             padding_y: 0.0,
 83             spacing: crate::layout::control_gap(),
 84         }
 85     }
 86 }
 87 
 88 impl ContainerLayout for VerticalLayout {
 89     fn layout(&self, x: f32, y: f32, w: f32, _h: f32, children: &[*mut (dyn WidgetHost + 'static)], ctx: &mut UiContext) -> f32 {
 90         let left_x = x + self.padding_x;
 91         let available_w = (w - 2.0 * self.padding_x).max(1.0);
 92         // Blocks: the label row (`label_lead`) then the content, the gap between
 93         // blocks.
 94         let lead = label_lead(children);
 95         let mut current_y = y + self.padding_y;
 96 
 97         for &child_ptr in children {
 98             unsafe {
 99                 let child = &mut *child_ptr;
100                 let ch = content_height(child);
101                 let use_h = if ch > 0.0 { ch } else { 44.0 };
102                 child.layout(
103                     Point { x: left_x, y: current_y + lead },
104                     LayoutConstraints::new(available_w, available_w, use_h, use_h),
105                     ctx,
106                 );
107                 current_y += lead + use_h + self.spacing;
108             }
109         }
110         (current_y - y).max(0.0)
111     }
112 
113     fn measure(&self, constraints: LayoutConstraints, children: &[*mut (dyn WidgetHost + 'static)], ctx: &UiContext) -> Size {
114         let mut total_h = self.padding_y * 2.0;
115         let mut max_w = 0.0f32;
116         let spacing = self.spacing;
117         let lead = label_lead(children);
118 
119         for (i, &child_ptr) in children.iter().enumerate() {
120             unsafe {
121                 let size = (*child_ptr).measure(constraints, ctx);
122                 max_w = max_w.max(size.width);
123                 total_h += lead + size.height;
124                 if i > 0 {
125                     total_h += spacing;
126                 }
127             }
128         }
129 
130         Size {
131             width: (max_w + self.padding_x * 2.0).clamp(constraints.min_width, constraints.max_width),
132             height: total_h.clamp(constraints.min_height, constraints.max_height),
133         }
134     }
135 
136     fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
137         Box::new(*self)
138     }
139 }
140 
141 #[derive(Debug, Clone)]
142 pub struct GridLayout {
143     pub columns: usize,
144     pub gap: f32,
145     pub padding_x: f32,
146     pub padding_y: f32,
147 }
148 
149 impl ContainerLayout for GridLayout {
150     fn layout(&self, x: f32, y: f32, w: f32, _h: f32, children: &[*mut (dyn WidgetHost + 'static)], ctx: &mut UiContext) -> f32 {
151         let count = children.len();
152         if count == 0 {
153             return 0.0;
154         }
155         let cols = self.columns.max(1);
156         let total_gap = self.gap * (cols - 1) as f32;
157         let available_w = (w - 2.0 * self.padding_x - total_gap).max(1.0);
158         let col_w = available_w / cols as f32;
159         
160         // Blocks (see VerticalLayout): the label row, then the content.
161         let lead = label_lead(children);
162         let mut col_heights = vec![y + self.padding_y; cols];
163 
164         for &child_ptr in children {
165             unsafe {
166                 let child = &mut *child_ptr;
167                 let ch = content_height(child);
168                 let use_h = if ch > 0.0 { ch } else { 44.0 };
169                 
170                 let mut min_col = 0;
171                 let mut min_h = col_heights[0];
172                 for i in 1..cols {
173                     if col_heights[i] < min_h {
174                         min_h = col_heights[i];
175                         min_col = i;
176                     }
177                 }
178                 
179                 let cx = x + self.padding_x + min_col as f32 * (col_w + self.gap);
180                 let cy = col_heights[min_col];
181                 child.layout(
182                     Point { x: cx, y: cy + lead },
183                     LayoutConstraints::new(col_w, col_w, use_h, use_h),
184                     ctx,
185                 );
186                 col_heights[min_col] += lead + use_h + self.gap;
187             }
188         }
189         
190         let max_h = col_heights.iter().cloned().fold(0.0f32, |a, b| a.max(b));
191         (max_h - y).max(0.0)
192     }
193 
194     fn measure(&self, constraints: LayoutConstraints, children: &[*mut (dyn WidgetHost + 'static)], ctx: &UiContext) -> Size {
195         let cols = self.columns.max(1);
196         let lead = label_lead(children);
197         let mut col_heights = vec![self.padding_y; cols];
198         let total_gap = self.gap * (cols - 1) as f32;
199         let available_w = (constraints.max_width - 2.0 * self.padding_x - total_gap).max(1.0);
200         let col_w = available_w / cols as f32;
201         
202         let child_constraints = LayoutConstraints::new(col_w, col_w, constraints.min_height, constraints.max_height);
203 
204         for &child_ptr in children {
205             unsafe {
206                 let size = (*child_ptr).measure(child_constraints, ctx);
207                 let mut min_col = 0;
208                 let mut min_h = col_heights[0];
209                 for i in 1..cols {
210                     if col_heights[i] < min_h {
211                         min_h = col_heights[i];
212                         min_col = i;
213                     }
214                 }
215                 col_heights[min_col] += lead + size.height + self.gap;
216             }
217         }
218         
219         let max_h = col_heights.iter().cloned().fold(0.0f32, |a, b| a.max(b));
220         Size {
221             width: constraints.max_width,
222             height: (max_h + self.padding_y).clamp(constraints.min_height, constraints.max_height),
223         }
224     }
225 
226     fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
227         Box::new(self.clone())
228     }
229 }
230 
231 #[derive(Debug, Clone)]
232 pub struct AdaptiveGridLayout {
233     pub min_col_width: f32,
234     pub gap: f32,
235     pub padding_x: f32,
236     pub padding_y: f32,
237 }
238 
239 impl ContainerLayout for AdaptiveGridLayout {
240     fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn WidgetHost + 'static)], ctx: &mut UiContext) -> f32 {
241         let usable_w = (w - 2.0 * self.padding_x).max(1.0);
242         let cols = (((usable_w + self.gap) / (self.min_col_width + self.gap)).floor().max(1.0)) as usize;
243         let grid = GridLayout {
244             columns: cols,
245             gap: self.gap,
246             padding_x: self.padding_x,
247             padding_y: self.padding_y,
248         };
249         grid.layout(x, y, w, h, children, ctx)
250     }
251 
252     fn measure(&self, constraints: LayoutConstraints, children: &[*mut (dyn WidgetHost + 'static)], ctx: &UiContext) -> Size {
253         let usable_w = (constraints.max_width - 2.0 * self.padding_x).max(1.0);
254         let cols = (((usable_w + self.gap) / (self.min_col_width + self.gap)).floor().max(1.0)) as usize;
255         let grid = GridLayout {
256             columns: cols,
257             gap: self.gap,
258             padding_x: self.padding_x,
259             padding_y: self.padding_y,
260         };
261         grid.measure(constraints, children, ctx)
262     }
263 
264     fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
265         Box::new(self.clone())
266     }
267 }
268 
269 #[derive(Debug, Clone, Copy)]
270 pub struct ColumnsLayout {
271     pub padding_x: f32,
272     pub padding_y: f32,
273     pub spacing: f32,
274 }
275 
276 impl Default for ColumnsLayout {
277     fn default() -> Self {
278         Self {
279             padding_x: crate::layout::control_gap(),
280             padding_y: crate::layout::control_gap(),
281             spacing: crate::layout::control_gap(),
282         }
283     }
284 }
285 
286 impl ContainerLayout for ColumnsLayout {
287     fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn WidgetHost + 'static)], ctx: &mut UiContext) -> f32 {
288         let count = children.len();
289         if count == 0 {
290             return 0.0;
291         }
292         let total_spacing = self.spacing * (count - 1) as f32;
293         let total_padding = self.padding_x * 2.0;
294         let available_w = (w - total_padding - total_spacing).max(1.0);
295         let col_w = available_w / count as f32;
296         // Blocks: the label row above the columns' content.
297         let lead = label_lead(children);
298         let use_h = (h - 2.0 * self.padding_y - lead).max(1.0);
299         let start_y = y + self.padding_y + lead;
300 
301         let mut current_x = x + self.padding_x;
302         for &child_ptr in children {
303             unsafe {
304                 let child = &mut *child_ptr;
305                 child.layout(
306                     Point { x: current_x, y: start_y },
307                     LayoutConstraints::new(col_w, col_w, use_h, use_h),
308                     ctx,
309                 );
310                 current_x += col_w + self.spacing;
311             }
312         }
313         use_h + 2.0 * self.padding_y
314     }
315 
316     fn measure(&self, constraints: LayoutConstraints, children: &[*mut (dyn WidgetHost + 'static)], ctx: &UiContext) -> Size {
317         let count = children.len();
318         if count == 0 {
319             return Size { width: constraints.min_width, height: constraints.min_height };
320         }
321         let mut max_h = 0.0f32;
322         for &child_ptr in children {
323             unsafe {
324                 let size = (*child_ptr).measure(constraints, ctx);
325                 max_h = max_h.max(size.height);
326             }
327         }
328         Size {
329             width: constraints.max_width,
330             height: (max_h + 2.0 * self.padding_y + label_lead(children)).clamp(constraints.min_height, constraints.max_height),
331         }
332     }
333 
334     fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
335         Box::new(*self)
336     }
337 }
338 
339 #[derive(Debug, Clone, Copy)]
340 pub struct MosaicLayout {
341     pub gap: f32,
342     pub padding_x: f32,
343     pub padding_y: f32,
344 }
345 
346 impl Default for MosaicLayout {
347     fn default() -> Self {
348         Self {
349             gap: crate::layout::control_gap(),
350             padding_x: crate::layout::control_gap(),
351             padding_y: crate::layout::control_gap(),
352         }
353     }
354 }
355 
356 struct Packer {
357     free_rects: Vec<(f32, f32, f32, f32)>, // (x, y, w, h)
358     max_w: f32,
359     max_h: f32,
360     gap: f32,
361 }
362 
363 impl Packer {
364     fn new(start_x: f32, start_y: f32, max_width: f32, gap: f32) -> Self {
365         Self {
366             free_rects: vec![(start_x, start_y, max_width, 100000.0)],
367             max_w: max_width,
368             max_h: 0.0,
369             gap,
370         }
371     }
372 
373     fn pack(&mut self, cw: f32, ch: f32) -> (f32, f32) {
374         let cw_clamped = cw.min(self.max_w);
375         
376         let mut best_idx = None;
377         let mut best_y = f32::MAX;
378         let mut best_x = f32::MAX;
379 
380         for (idx, &(rx, ry, rw, rh)) in self.free_rects.iter().enumerate() {
381             if rw >= cw_clamped && rh >= ch
382                 && (ry < best_y || (ry == best_y && rx < best_x)) {
383                     best_y = ry;
384                     best_x = rx;
385                     best_idx = Some(idx);
386                 }
387         }
388 
389         let chosen_idx = match best_idx {
390             Some(idx) => idx,
391             None => {
392                 let new_y = self.max_h + self.gap;
393                 let new_rect = (self.free_rects[0].0, new_y, self.max_w, 100000.0);
394                 self.free_rects.push(new_rect);
395                 self.free_rects.len() - 1
396             }
397         };
398 
399         let (fx, fy, fw, fh) = self.free_rects.remove(chosen_idx);
400         let px = fx;
401         let py = fy;
402 
403         let rx = fx + cw_clamped + self.gap;
404         let rw = fw - cw_clamped - self.gap;
405         if rw > 0.0 && ch > 0.0 {
406             self.free_rects.push((rx, py, rw, ch));
407         }
408 
409         let by = py + ch + self.gap;
410         let bh = fh - ch - self.gap;
411         if bh > 0.0 && fw > 0.0 {
412             self.free_rects.push((fx, by, fw, bh));
413         }
414 
415         self.max_h = self.max_h.max(py + ch);
416 
417         (px, py)
418     }
419 }
420 
421 impl ContainerLayout for MosaicLayout {
422     fn layout(&self, x: f32, y: f32, w: f32, _h: f32, children: &[*mut (dyn WidgetHost + 'static)], ctx: &mut UiContext) -> f32 {
423         let count = children.len();
424         if count == 0 {
425             return 0.0;
426         }
427         let total_padding_x = self.padding_x * 2.0;
428         let available_w = (w - total_padding_x).max(1.0);
429 
430         // Blocks (see VerticalLayout): each packed as label row + content.
431         let lead = label_lead(children);
432         let mut packer = Packer::new(x + self.padding_x, y + self.padding_y, available_w, self.gap);
433 
434         for &child_ptr in children {
435             unsafe {
436                 let child = &mut *child_ptr;
437                 let child_rect = child.rect();
438                 let child_w = child_rect.2;
439                 let child_h = content_height(child);
440                 let use_h = if child_h > 0.0 { child_h } else { 44.0 };
441                 
442                 let (px, py) = packer.pack(child_w, lead + use_h);
443                 child.layout(
444                     Point { x: px, y: py + lead },
445                     LayoutConstraints::new(child_w.min(available_w), child_w.min(available_w), use_h, use_h),
446                     ctx,
447                 );
448             }
449         }
450 
451         (packer.max_h - y).max(0.0)
452     }
453 
454     fn measure(&self, constraints: LayoutConstraints, children: &[*mut (dyn WidgetHost + 'static)], ctx: &UiContext) -> Size {
455         let count = children.len();
456         if count == 0 {
457             return Size { width: constraints.min_width, height: constraints.min_height };
458         }
459         let total_padding_x = self.padding_x * 2.0;
460         let available_w = (constraints.max_width - total_padding_x).max(1.0);
461 
462         let lead = label_lead(children);
463         let mut packer = Packer::new(self.padding_x, self.padding_y, available_w, self.gap);
464 
465         for &child_ptr in children {
466             unsafe {
467                 let size = (*child_ptr).measure(constraints, ctx);
468                 packer.pack(size.width, lead + size.height);
469             }
470         }
471 
472         Size {
473             width: constraints.max_width,
474             height: (packer.max_h + self.padding_y).clamp(constraints.min_height, constraints.max_height),
475         }
476     }
477 
478     fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
479         Box::new(*self)
480     }
481 }
482 
483 #[derive(Debug, Clone, Copy)]
484 pub struct ReverseMosaicLayout {
485     pub gap: f32,
486     pub padding_x: f32,
487     pub padding_y: f32,
488 }
489 
490 impl Default for ReverseMosaicLayout {
491     fn default() -> Self {
492         Self {
493             gap: crate::layout::control_gap(),
494             padding_x: crate::layout::control_gap(),
495             padding_y: crate::layout::control_gap(),
496         }
497     }
498 }
499 
500 impl ContainerLayout for ReverseMosaicLayout {
501     fn layout(&self, x: f32, y: f32, w: f32, h: f32, children: &[*mut (dyn WidgetHost + 'static)], ctx: &mut UiContext) -> f32 {
502         let count = children.len();
503         if count == 0 {
504             return 0.0;
505         }
506         let total_padding_x = self.padding_x * 2.0;
507         let available_w = (w - total_padding_x).max(1.0);
508 
509         let lead = label_lead(children);
510         let mut packer = Packer::new(self.padding_x, self.padding_y, available_w, self.gap);
511         let mut temp_positions = Vec::with_capacity(count);
512 
513         for &child_ptr in children {
514             unsafe {
515                 let child = &mut *child_ptr;
516                 let child_rect = child.rect();
517                 let child_w = child_rect.2;
518                 let child_h = content_height(child);
519                 let use_h = if child_h > 0.0 { child_h } else { 44.0 };
520                 
521                 // The block (label row + content) is packed; the content lands
522                 // `lead` below its top, scaled with the rest.
523                 let (px, py) = packer.pack(child_w, lead + use_h);
524                 temp_positions.push((px, py, child_w, lead + use_h));
525             }
526         }
527 
528         let mut x_min = f32::MAX;
529         let mut x_max = f32::MIN;
530         let mut y_min = f32::MAX;
531         let mut y_max = f32::MIN;
532 
533         for &(px, py, pw, ph) in &temp_positions {
534             x_min = x_min.min(px);
535             x_max = x_max.max(px + pw);
536             y_min = y_min.min(py);
537             y_max = y_max.max(py + ph);
538         }
539 
540         let src_w = (x_max - x_min).max(1.0);
541         let src_h = (y_max - y_min).max(1.0);
542 
543         let dst_w = available_w;
544         let dst_h = (h - 2.0 * self.padding_y).max(1.0);
545 
546         let scale_x = dst_w / src_w;
547         let scale_y = dst_h / src_h;
548 
549         for (i, &child_ptr) in children.iter().enumerate() {
550             unsafe {
551                 let child = &mut *child_ptr;
552                 let (px, py, pw, ph) = temp_positions[i];
553 
554                 let new_x = x + self.padding_x + (px - x_min) * scale_x;
555                 let new_y = y + self.padding_y + (py - y_min) * scale_y + lead * scale_y;
556                 let new_w = pw * scale_x;
557                 let new_h = (ph - lead) * scale_y;
558 
559                 child.layout(
560                     Point { x: new_x, y: new_y },
561                     LayoutConstraints::new(new_w, new_w, new_h, new_h),
562                     ctx,
563                 );
564             }
565         }
566 
567         h
568     }
569 
570     fn measure(&self, constraints: LayoutConstraints, _children: &[*mut (dyn WidgetHost + 'static)], _ctx: &UiContext) -> Size {
571         Size {
572             width: constraints.max_width,
573             height: constraints.max_height,
574         }
575     }
576 
577     fn box_clone_container(&self) -> Box<dyn ContainerLayout> {
578         Box::new(*self)
579     }
580 }
581