1use crate::color::Color;
11use crate::draw_ctx::DrawCtx;
12use crate::event::{Event, EventResult};
13use crate::geometry::{Rect, Size};
14use crate::widgets::flex::DEFAULT_ROW_GAP;
15use crate::layout_props::{resolve_fit_or_stretch, HAnchor, Insets, VAnchor, WidgetBase};
16use crate::widget::Widget;
17
18fn place_cross_v(
27 anchor: VAnchor,
28 pad_b: f64,
29 inner_h: f64,
30 margin_b: f64,
31 margin_t: f64,
32 natural_h: f64,
33 min_h: f64,
34 max_h: f64,
35) -> (f64, f64) {
36 let slot_h = (inner_h - margin_b - margin_t).max(0.0);
37
38 let actual_h = if anchor.is_stretch() {
40 slot_h.clamp(min_h, max_h)
41 } else if anchor == VAnchor::MAX_FIT_OR_STRETCH {
42 resolve_fit_or_stretch(natural_h, slot_h, true).clamp(min_h, max_h)
43 } else if anchor == VAnchor::MIN_FIT_OR_STRETCH {
44 resolve_fit_or_stretch(natural_h, slot_h, false).clamp(min_h, max_h)
45 } else {
46 natural_h.clamp(min_h, max_h)
47 };
48
49 let y = if anchor.contains(VAnchor::TOP) && !anchor.contains(VAnchor::BOTTOM) {
51 (pad_b + inner_h - margin_t - actual_h).max(pad_b)
53 } else if anchor.contains(VAnchor::CENTER) && !anchor.is_stretch() {
54 pad_b + margin_b + (slot_h - actual_h) * 0.5
56 } else {
57 pad_b + margin_b
59 };
60
61 (y, actual_h)
62}
63
64pub struct FlexRow {
66 bounds: Rect,
67 children: Vec<Box<dyn Widget>>,
68 flex_factors: Vec<f64>,
69 base: WidgetBase,
70 pub gap: f64,
71 pub inner_padding: Insets,
72 pub background: Color,
73 pub fit_width: bool,
81}
82
83impl FlexRow {
84 pub fn new() -> Self {
85 Self {
86 bounds: Rect::default(),
87 children: Vec::new(),
88 flex_factors: Vec::new(),
89 base: WidgetBase::new(),
90 gap: DEFAULT_ROW_GAP,
91 inner_padding: Insets::ZERO,
92 background: Color::rgba(0.0, 0.0, 0.0, 0.0),
93 fit_width: false,
94 }
95 }
96
97 pub fn with_gap(mut self, gap: f64) -> Self {
98 self.gap = gap;
99 self
100 }
101
102 pub fn with_fit_width(mut self, fit: bool) -> Self {
104 self.fit_width = fit;
105 self
106 }
107 pub fn with_padding(mut self, p: f64) -> Self {
108 self.inner_padding = Insets::all(p);
109 self
110 }
111 pub fn with_inner_padding(mut self, p: Insets) -> Self {
112 self.inner_padding = p;
113 self
114 }
115 pub fn with_background(mut self, c: Color) -> Self {
116 self.background = c;
117 self
118 }
119
120 pub fn with_margin(mut self, m: Insets) -> Self {
121 self.base.margin = m;
122 self
123 }
124 pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
125 self.base.h_anchor = h;
126 self
127 }
128 pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
129 self.base.v_anchor = v;
130 self
131 }
132 pub fn with_min_size(mut self, s: Size) -> Self {
133 self.base.min_size = s;
134 self
135 }
136 pub fn with_max_size(mut self, s: Size) -> Self {
137 self.base.max_size = s;
138 self
139 }
140
141 pub fn add(mut self, child: Box<dyn Widget>) -> Self {
142 self.children.push(child);
143 self.flex_factors.push(0.0);
144 self
145 }
146
147 pub fn add_flex(mut self, child: Box<dyn Widget>, flex: f64) -> Self {
148 self.children.push(child);
149 self.flex_factors.push(flex.max(0.0));
150 self
151 }
152
153 pub fn push(&mut self, child: Box<dyn Widget>, flex: f64) {
154 self.children.push(child);
155 self.flex_factors.push(flex.max(0.0));
156 }
157}
158
159impl Default for FlexRow {
160 fn default() -> Self {
161 Self::new()
162 }
163}
164
165impl Widget for FlexRow {
166 fn type_name(&self) -> &'static str {
167 "FlexRow"
168 }
169 fn bounds(&self) -> Rect {
170 self.bounds
171 }
172 fn set_bounds(&mut self, b: Rect) {
173 self.bounds = b;
174 }
175 fn children(&self) -> &[Box<dyn Widget>] {
176 &self.children
177 }
178 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
179 &mut self.children
180 }
181
182 fn margin(&self) -> Insets {
183 self.base.margin
184 }
185 fn widget_base(&self) -> Option<&WidgetBase> {
186 Some(&self.base)
187 }
188 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
189 Some(&mut self.base)
190 }
191 fn padding(&self) -> Insets {
192 self.inner_padding
193 }
194 fn h_anchor(&self) -> HAnchor {
195 self.base.h_anchor
196 }
197 fn v_anchor(&self) -> VAnchor {
198 self.base.v_anchor
199 }
200 fn min_size(&self) -> Size {
201 self.base.min_size
202 }
203 fn max_size(&self) -> Size {
204 self.base.max_size
205 }
206
207 fn layout(&mut self, available: Size) -> Size {
208 let pad_l = self.inner_padding.left;
209 let pad_r = self.inner_padding.right;
210 let pad_t = self.inner_padding.top;
211 let pad_b = self.inner_padding.bottom;
212 let gap = self.gap;
213 let n = self.children.len();
214 if n == 0 {
215 return available;
216 }
217
218 let inner_w = (available.width - pad_l - pad_r).max(0.0);
219 let inner_h = (available.height - pad_t - pad_b).max(0.0);
220
221 let margins: Vec<Insets> = self.children.iter().map(|c| c.margin()).collect();
223
224 let mut content_widths = vec![0.0f64; n];
228 let mut total_fixed_with_margins = 0.0f64;
229 let mut total_flex = 0.0f64;
230 let mut total_flex_margin_h = 0.0f64;
231
232 for i in 0..n {
233 if self.flex_factors[i] == 0.0 {
234 let m = &margins[i];
235 let slot_h = (inner_h - m.bottom - m.top).max(0.0);
236 let desired = self.children[i].layout(Size::new(inner_w, slot_h));
239 content_widths[i] = desired.width.clamp(
240 self.children[i].min_size().width,
241 self.children[i].max_size().width,
242 );
243 }
244 }
245
246 let visible: Vec<bool> = self.children.iter().map(|c| c.is_visible()).collect();
251 let visible_n = visible.iter().filter(|v| **v).count();
252 let total_gap = if visible_n > 1 {
253 gap * (visible_n - 1) as f64
254 } else {
255 0.0
256 };
257
258 for i in 0..n {
259 if !visible[i] {
260 continue;
261 }
262 let m = &margins[i];
263 if self.flex_factors[i] == 0.0 {
264 total_fixed_with_margins += content_widths[i] + m.horizontal();
265 } else {
266 total_flex += self.flex_factors[i];
267 total_flex_margin_h += m.horizontal();
268 }
269 }
270
271 let remaining =
275 (inner_w - total_fixed_with_margins - total_gap - total_flex_margin_h).max(0.0);
276 let flex_unit = if total_flex > 0.0 {
277 remaining / total_flex
278 } else {
279 0.0
280 };
281
282 for i in 0..n {
283 if self.flex_factors[i] > 0.0 && visible[i] {
284 let raw = self.flex_factors[i] * flex_unit;
285 content_widths[i] = raw.clamp(
286 self.children[i].min_size().width,
287 self.children[i].max_size().width,
288 );
289 }
290 }
291
292 let mut cursor_x = pad_l;
296 let mut max_slot_h = 0.0f64; for i in 0..n {
299 let m = &margins[i];
300 let slot_h = (inner_h - m.bottom - m.top).max(0.0);
301 let content_w = content_widths[i];
302
303 if !visible[i] {
304 self.children[i].set_bounds(Rect::new(cursor_x, pad_b + m.bottom, 0.0, 0.0));
307 continue;
308 }
309
310 cursor_x += m.left;
312
313 let desired = self.children[i].layout(Size::new(content_w, slot_h));
315 let natural_h = desired.height;
316 let v_anchor = self.children[i].v_anchor();
317 let min_h = self.children[i].min_size().height;
318 let max_h = self.children[i].max_size().height;
319
320 let (child_y, child_h) = place_cross_v(
321 v_anchor, pad_b, inner_h, m.bottom, m.top, natural_h, min_h, max_h,
322 );
323
324 let final_w = content_w.round();
326 let final_h = child_h.round();
327 if (final_h - slot_h).abs() > 0.5 || (final_w - content_w).abs() > 0.5 {
334 self.children[i].layout(Size::new(final_w, final_h));
335 }
336 self.children[i].set_bounds(Rect::new(
337 cursor_x.round(),
338 child_y.round(),
339 final_w,
340 final_h,
341 ));
342 max_slot_h = max_slot_h.max(child_h + m.vertical());
343
344 cursor_x += content_w + m.right + gap;
346 }
347
348 let natural_h = max_slot_h + pad_t + pad_b;
351 let reported_w = if self.fit_width {
354 pad_l + pad_r + total_fixed_with_margins + total_gap
355 } else {
356 available.width
357 };
358 Size::new(reported_w, natural_h)
359 }
360
361 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
362 if self.background.a > 0.001 {
363 let w = self.bounds.width;
364 let h = self.bounds.height;
365 ctx.set_fill_color(self.background);
366 ctx.begin_path();
367 ctx.rect(0.0, 0.0, w, h);
368 ctx.fill();
369 }
370 }
371
372 fn on_event(&mut self, _: &Event) -> EventResult {
373 EventResult::Ignored
374 }
375}