Skip to main content

rosace_widgets/tree/
wrap.rs

1use rosace_core::types::{Point, Rect, Size};
2use rosace_layout::Constraints;
3use super::{Widget, Children, LayoutCtx, PaintCtx, BoxedWidget, avail_w};
4
5/// Flows children left→right, wrapping to the next line when the row is full
6/// — chip clouds, tag lists, toolbars. Lays out something new (D095).
7pub struct Wrap {
8    spacing: f32,
9    run_spacing: f32,
10    children: Vec<BoxedWidget>,
11}
12
13impl Wrap {
14    pub fn new() -> Self { Self { spacing: 8.0, run_spacing: 8.0, children: Vec::new() } }
15    pub fn spacing(mut self, s: f32) -> Self { self.spacing = s; self }
16    pub fn run_spacing(mut self, s: f32) -> Self { self.run_spacing = s; self }
17    pub fn child(mut self, w: impl Widget + 'static) -> Self { self.children.push(Box::new(w)); self }
18    pub fn children(mut self, ws: Vec<BoxedWidget>) -> Self { self.children.extend(ws); self }
19
20    /// Returns (per-child rect origins relative to 0,0, total size).
21    fn arrange(&self, ctx: &LayoutCtx, max_w: f32) -> (Vec<Rect>, Size) {
22        let mut rects = Vec::with_capacity(self.children.len());
23        let (mut x, mut y, mut row_h, mut widest) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
24        for c in &self.children {
25            let s = c.layout(&ctx.with_constraints(Constraints::loose(max_w, f32::INFINITY)));
26            if x > 0.0 && x + s.width > max_w {
27                x = 0.0; y += row_h + self.run_spacing; row_h = 0.0;
28            }
29            rects.push(Rect { origin: Point { x, y }, size: s });
30            x += s.width + self.spacing;
31            row_h = row_h.max(s.height);
32            widest = widest.max(x - self.spacing);
33        }
34        (rects, Size { width: widest.min(max_w), height: y + row_h })
35    }
36}
37
38impl Default for Wrap { fn default() -> Self { Self::new() } }
39
40impl Widget for Wrap {
41    fn children(&self) -> Children<'_> { Children::Many(&self.children) }
42
43    fn layout(&self, ctx: &LayoutCtx) -> Size {
44        // Occupy the FULL available width (like Grid) — children keep their
45        // natural size and flow onto new lines. Returning the post-wrap
46        // content width would make the parent allocate a narrower box, and
47        // paint would then re-wrap into it (over-wrapping). Only the height
48        // is content-derived.
49        let w = avail_w(ctx.constraints);
50        let (_, size) = self.arrange(ctx, w);
51        ctx.constraints.constrain(Size { width: w, height: size.height })
52    }
53
54    fn paint(&self, ctx: &mut PaintCtx) {
55        let r = ctx.rect;
56        let (rects, _) = self.arrange(&ctx.layout_ctx(Constraints::loose(r.size.width, r.size.height)), r.size.width);
57        for (child, rel) in self.children.iter().zip(rects) {
58            let rect = Rect {
59                origin: Point { x: r.origin.x + rel.origin.x, y: r.origin.y + rel.origin.y },
60                size: rel.size,
61            };
62            child.paint(&mut ctx.child(rect));
63        }
64    }
65}