rosace_widgets/tree/
row.rs1use std::sync::Mutex;
2use rosace_core::types::Size;
3use rosace_layout::{Constraints, CrossAxisAlignment, MainAxisAlignment, layout_row};
4use super::{Widget, LayoutCtx, PaintCtx, BoxedWidget, avail_w, avail_h, offset, rect_at};
5use super::padding::EdgeInsets;
6
7pub struct Row {
13 children: Vec<BoxedWidget>,
14 spacing: f32,
15 main_axis_alignment: MainAxisAlignment,
16 cross_axis_alignment: CrossAxisAlignment,
17 padding: EdgeInsets,
18 measure_cache: Mutex<Option<(Constraints, Vec<Size>)>>,
19}
20
21impl Row {
22 pub fn new() -> Self {
23 Self {
24 children: Vec::new(),
25 spacing: 0.0,
26 main_axis_alignment: MainAxisAlignment::Start,
27 cross_axis_alignment: CrossAxisAlignment::Center,
28 padding: EdgeInsets::default(),
29 measure_cache: Mutex::new(None),
30 }
31 }
32
33 pub fn spacing(mut self, s: f32) -> Self { self.spacing = s; self }
34 pub fn padding(mut self, p: EdgeInsets) -> Self { self.padding = p; self }
35 pub fn main_axis_alignment(mut self, a: MainAxisAlignment) -> Self { self.main_axis_alignment = a; self }
36 pub fn cross_axis_alignment(mut self, a: CrossAxisAlignment) -> Self { self.cross_axis_alignment = a; self }
37
38 pub fn child(mut self, w: impl Widget + 'static) -> Self {
39 self.children.push(Box::new(w)); self
40 }
41 pub fn children(mut self, ws: Vec<BoxedWidget>) -> Self {
42 self.children.extend(ws); self
43 }
44
45 pub fn scrollable(self) -> super::ScrollView {
49 super::ScrollView::horizontal(self)
50 }
51
52 fn measure(&self, ctx: &LayoutCtx) -> Vec<Size> {
53 let c = ctx.constraints;
54 {
55 let cache = self.measure_cache.lock().unwrap();
56 if let Some((cached_c, ref sizes)) = *cache {
57 if cached_c == c { return sizes.clone(); }
58 }
59 }
60
61 let max_w = (avail_w(c) - self.padding.total_h()).max(0.0);
62 let max_h = (avail_h(c) - self.padding.total_v()).max(0.0);
63 let n = self.children.len();
64 let gap_total = if n > 1 { self.spacing * (n - 1) as f32 } else { 0.0 };
65
66 let total_flex: f32 = self.children.iter().map(|c| c.flex_factor()).sum();
67 let flex_enabled = total_flex > 0.0 && max_w.is_finite();
71 #[cfg(debug_assertions)]
72 if total_flex > 0.0 && !flex_enabled {
73 static WARNED: std::sync::Once = std::sync::Once::new();
74 WARNED.call_once(|| {
75 eprintln!(
76 "[ROSACE] Row: Expanded child inside an unbounded width \
77 (e.g. a horizontal ScrollView) — flex is ignored, the child \
78 sizes to its content. Give the Row a bounded width to flex."
79 );
80 });
81 }
82 let fixed_w: f32 = self.children.iter()
83 .filter(|c| !flex_enabled || c.flex_factor() == 0.0)
84 .map(|c| c.layout(&ctx.with_constraints(Constraints::loose(max_w, max_h))).width)
85 .sum::<f32>() + gap_total;
86
87 let flex_pool = (max_w - fixed_w).max(0.0);
88
89 let sizes: Vec<Size> = self.children.iter().map(|c| {
90 let ff = c.flex_factor();
91 if ff > 0.0 && flex_enabled {
92 let w = flex_pool * ff / total_flex;
93 c.layout(&ctx.with_constraints(Constraints::tight(w, max_h)))
94 } else {
95 c.layout(&ctx.with_constraints(Constraints::loose(max_w, max_h)))
96 }
97 }).collect();
98
99 *self.measure_cache.lock().unwrap() = Some((c, sizes.clone()));
100 sizes
101 }
102
103 fn layout_sizes(&self, ctx: &LayoutCtx) -> Vec<Size> {
106 if let Some((_, sizes)) = &*self.measure_cache.lock().unwrap() {
107 return sizes.clone();
108 }
109 self.measure(ctx)
110 }
111}
112
113impl Default for Row {
114 fn default() -> Self { Self::new() }
115}
116
117impl Widget for Row {
118 fn layout(&self, ctx: &LayoutCtx) -> Size {
119 let sizes = self.measure(ctx);
120 let c = ctx.constraints;
121 let (pad_h, pad_v) = (self.padding.total_h(), self.padding.total_v());
124 let inner_c = Constraints {
125 min_width: (c.min_width - pad_h).max(0.0),
126 max_width: super::shrink_axis(c.max_width, pad_h),
127 min_height: (c.min_height - pad_v).max(0.0),
128 max_height: super::shrink_axis(c.max_height, pad_v),
129 };
130 let result = layout_row(inner_c, &sizes,
131 self.main_axis_alignment, self.cross_axis_alignment, self.spacing);
132 self.padding.grow(result.size)
133 }
134
135 fn paint(&self, ctx: &mut PaintCtx) {
136 let inner_rect = self.padding.shrink(ctx.rect);
137 let inner_c = Constraints::tight(inner_rect.size.width, inner_rect.size.height);
139 let lctx = ctx.layout_ctx(inner_c);
140 let sizes = self.layout_sizes(&lctx);
141 let result = layout_row(inner_c, &sizes,
142 self.main_axis_alignment, self.cross_axis_alignment, self.spacing);
143 for (i, child) in self.children.iter().enumerate() {
144 let pos = result.child_positions[i];
145 let child_rect = rect_at(offset(inner_rect.origin, pos.x, pos.y), sizes[i]);
146 child.paint(&mut ctx.child(child_rect));
147 }
148 }
149}