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