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