1use crate::color::Color;
36use crate::device_scale::device_scale;
37use crate::event::{Event, EventResult};
38use crate::geometry::{Rect, Size};
39use crate::draw_ctx::DrawCtx;
40use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase, resolve_fit_or_stretch};
41use crate::widget::Widget;
42
43fn place_cross_h(
56 anchor: HAnchor,
57 pad_l: f64,
58 inner_w: f64,
59 margin_l: f64,
60 margin_r: f64,
61 natural_w: f64,
62 min_w: f64,
63 max_w: f64,
64) -> (f64, f64) {
65 let slot_w = (inner_w - margin_l - margin_r).max(0.0);
66
67 let actual_w = if anchor.is_stretch() {
69 slot_w.clamp(min_w, max_w)
71 } else if anchor == HAnchor::MAX_FIT_OR_STRETCH {
72 resolve_fit_or_stretch(natural_w, slot_w, true).clamp(min_w, max_w)
73 } else if anchor == HAnchor::MIN_FIT_OR_STRETCH {
74 resolve_fit_or_stretch(natural_w, slot_w, false).clamp(min_w, max_w)
75 } else {
76 natural_w.clamp(min_w, max_w)
78 };
79
80 let x = if anchor.contains(HAnchor::RIGHT) && !anchor.contains(HAnchor::LEFT) {
82 (pad_l + inner_w - margin_r - actual_w).max(pad_l)
84 } else if anchor.contains(HAnchor::CENTER) && !anchor.is_stretch() {
85 pad_l + margin_l + (slot_w - actual_w) * 0.5
87 } else {
88 pad_l + margin_l
90 };
91
92 (x, actual_w)
93}
94
95fn place_cross_v(
104 anchor: VAnchor,
105 pad_b: f64,
106 inner_h: f64,
107 margin_b: f64,
108 margin_t: f64,
109 natural_h: f64,
110 min_h: f64,
111 max_h: f64,
112) -> (f64, f64) {
113 let slot_h = (inner_h - margin_b - margin_t).max(0.0);
114
115 let actual_h = if anchor.is_stretch() {
117 slot_h.clamp(min_h, max_h)
118 } else if anchor == VAnchor::MAX_FIT_OR_STRETCH {
119 resolve_fit_or_stretch(natural_h, slot_h, true).clamp(min_h, max_h)
120 } else if anchor == VAnchor::MIN_FIT_OR_STRETCH {
121 resolve_fit_or_stretch(natural_h, slot_h, false).clamp(min_h, max_h)
122 } else {
123 natural_h.clamp(min_h, max_h)
124 };
125
126 let y = if anchor.contains(VAnchor::TOP) && !anchor.contains(VAnchor::BOTTOM) {
128 (pad_b + inner_h - margin_t - actual_h).max(pad_b)
130 } else if anchor.contains(VAnchor::CENTER) && !anchor.is_stretch() {
131 pad_b + margin_b + (slot_h - actual_h) * 0.5
133 } else {
134 pad_b + margin_b
136 };
137
138 (y, actual_h)
139}
140
141pub struct FlexColumn {
147 bounds: Rect,
148 children: Vec<Box<dyn Widget>>,
149 flex_factors: Vec<f64>,
151 base: WidgetBase,
152 pub gap: f64,
153 pub inner_padding: Insets,
154 pub background: Color,
155 pub use_panel_bg: bool,
158}
159
160impl FlexColumn {
161 pub fn new() -> Self {
162 Self {
163 bounds: Rect::default(),
164 children: Vec::new(),
165 flex_factors: Vec::new(),
166 base: WidgetBase::new(),
167 gap: 0.0,
168 inner_padding: Insets::ZERO,
169 background: Color::rgba(0.0, 0.0, 0.0, 0.0),
170 use_panel_bg: false,
171 }
172 }
173
174 pub fn with_gap(mut self, gap: f64) -> Self { self.gap = gap; self }
175 pub fn with_padding(mut self, p: f64) -> Self { self.inner_padding = Insets::all(p); self }
176 pub fn with_inner_padding(mut self, p: Insets) -> Self { self.inner_padding = p; self }
177 pub fn with_background(mut self, c: Color) -> Self { self.background = c; self }
178 pub fn with_panel_bg(mut self) -> Self { self.use_panel_bg = true; self }
180
181 pub fn with_margin(mut self, m: Insets) -> Self { self.base.margin = m; self }
182 pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
183 pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
184 pub fn with_min_size(mut self, s: Size) -> Self { self.base.min_size = s; self }
185 pub fn with_max_size(mut self, s: Size) -> Self { self.base.max_size = s; self }
186
187 pub fn add(mut self, child: Box<dyn Widget>) -> Self {
189 self.children.push(child);
190 self.flex_factors.push(0.0);
191 self
192 }
193
194 pub fn add_flex(mut self, child: Box<dyn Widget>, flex: f64) -> Self {
196 self.children.push(child);
197 self.flex_factors.push(flex.max(0.0));
198 self
199 }
200
201 pub fn push(&mut self, child: Box<dyn Widget>, flex: f64) {
203 self.children.push(child);
204 self.flex_factors.push(flex.max(0.0));
205 }
206}
207
208impl Default for FlexColumn { fn default() -> Self { Self::new() } }
209
210impl Widget for FlexColumn {
211 fn type_name(&self) -> &'static str { "FlexColumn" }
212 fn bounds(&self) -> Rect { self.bounds }
213 fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
214 fn children(&self) -> &[Box<dyn Widget>] { &self.children }
215 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
216
217 fn margin(&self) -> Insets { self.base.margin }
218 fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
219 fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
220 fn min_size(&self) -> Size { self.base.min_size }
221 fn max_size(&self) -> Size { self.base.max_size }
222
223 fn layout(&mut self, available: Size) -> Size {
224 let pad_l = self.inner_padding.left;
225 let pad_r = self.inner_padding.right;
226 let pad_t = self.inner_padding.top;
227 let pad_b = self.inner_padding.bottom;
228 let gap = self.gap;
229 let n = self.children.len();
230 if n == 0 { return available; }
231
232 let inner_w = (available.width - pad_l - pad_r).max(0.0);
233 let inner_h = (available.height - pad_t - pad_b).max(0.0);
234
235 let scale = device_scale();
237 let margins: Vec<Insets> = self.children.iter()
238 .map(|c| c.margin().scale(scale))
239 .collect();
240
241 let total_gap = if n > 1 { gap * (n - 1) as f64 } else { 0.0 };
242
243 let mut content_heights = vec![0.0f64; n];
250 let mut total_fixed_with_margins = 0.0f64;
251 let mut total_flex = 0.0f64;
252 let mut total_flex_margin_v = 0.0f64;
253
254 for i in 0..n {
255 let m = &margins[i];
256 let slot_w = (inner_w - m.left - m.right).max(0.0);
257 if self.flex_factors[i] == 0.0 {
258 let desired = self.children[i].layout(Size::new(slot_w, inner_h));
261 let clamped_h = desired.height
262 .clamp(self.children[i].min_size().height,
263 self.children[i].max_size().height);
264 content_heights[i] = clamped_h;
265 total_fixed_with_margins += clamped_h + m.vertical();
266 } else {
267 total_flex += self.flex_factors[i];
268 total_flex_margin_v += m.vertical();
269 }
270 }
271
272 let remaining = (inner_h
276 - total_fixed_with_margins
277 - total_gap
278 - total_flex_margin_v)
279 .max(0.0);
280 let flex_unit = if total_flex > 0.0 { remaining / total_flex } else { 0.0 };
281
282 for i in 0..n {
283 if self.flex_factors[i] > 0.0 {
284 let raw = self.flex_factors[i] * flex_unit;
285 content_heights[i] = raw
286 .clamp(self.children[i].min_size().height,
287 self.children[i].max_size().height);
288 }
289 }
290
291 let natural_content_h = total_fixed_with_margins + total_gap;
294 let effective_h = if total_flex > 0.0 { inner_h } else { natural_content_h };
295
296 let mut cursor_y = pad_b + effective_h;
303
304 for i in 0..n {
305 let m = &margins[i];
306 let slot_w = (inner_w - m.left - m.right).max(0.0);
307 let content_h = content_heights[i];
308
309 cursor_y -= m.top;
311 let child_bottom = cursor_y - content_h;
312
313 let desired = self.children[i].layout(Size::new(slot_w, content_h));
315 let natural_w = desired.width;
316 let h_anchor = self.children[i].h_anchor();
317 let min_w = self.children[i].min_size().width;
318 let max_w = self.children[i].max_size().width;
319
320 let (child_x, child_w) = place_cross_h(
321 h_anchor, pad_l, inner_w, m.left, m.right, natural_w, min_w, max_w,
322 );
323
324 self.children[i].set_bounds(Rect::new(
327 child_x.round(), child_bottom.round(), child_w.round(), content_h.round(),
328 ));
329
330 cursor_y = child_bottom - m.bottom - gap;
332 }
333
334 if total_flex > 0.0 {
337 available
338 } else {
339 Size::new(available.width, natural_content_h + pad_t + pad_b)
340 }
341 }
342
343 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
344 let bg = if self.use_panel_bg {
345 Some(ctx.visuals().panel_fill)
346 } else if self.background.a > 0.001 {
347 Some(self.background)
348 } else {
349 None
350 };
351 if let Some(color) = bg {
352 let w = self.bounds.width;
353 let h = self.bounds.height;
354 ctx.set_fill_color(color);
355 ctx.begin_path();
356 ctx.rect(0.0, 0.0, w, h);
357 ctx.fill();
358 }
359 }
360
361 fn on_event(&mut self, _: &Event) -> EventResult { EventResult::Ignored }
362}
363
364pub struct FlexRow {
370 bounds: Rect,
371 children: Vec<Box<dyn Widget>>,
372 flex_factors: Vec<f64>,
373 base: WidgetBase,
374 pub gap: f64,
375 pub inner_padding: Insets,
376 pub background: Color,
377}
378
379impl FlexRow {
380 pub fn new() -> Self {
381 Self {
382 bounds: Rect::default(),
383 children: Vec::new(),
384 flex_factors: Vec::new(),
385 base: WidgetBase::new(),
386 gap: 0.0,
387 inner_padding: Insets::ZERO,
388 background: Color::rgba(0.0, 0.0, 0.0, 0.0),
389 }
390 }
391
392 pub fn with_gap(mut self, gap: f64) -> Self { self.gap = gap; self }
393 pub fn with_padding(mut self, p: f64) -> Self { self.inner_padding = Insets::all(p); self }
394 pub fn with_inner_padding(mut self, p: Insets) -> Self { self.inner_padding = p; self }
395 pub fn with_background(mut self, c: Color) -> Self { self.background = c; self }
396
397 pub fn with_margin(mut self, m: Insets) -> Self { self.base.margin = m; self }
398 pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
399 pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
400 pub fn with_min_size(mut self, s: Size) -> Self { self.base.min_size = s; self }
401 pub fn with_max_size(mut self, s: Size) -> Self { self.base.max_size = s; self }
402
403 pub fn add(mut self, child: Box<dyn Widget>) -> Self {
404 self.children.push(child);
405 self.flex_factors.push(0.0);
406 self
407 }
408
409 pub fn add_flex(mut self, child: Box<dyn Widget>, flex: f64) -> Self {
410 self.children.push(child);
411 self.flex_factors.push(flex.max(0.0));
412 self
413 }
414
415 pub fn push(&mut self, child: Box<dyn Widget>, flex: f64) {
416 self.children.push(child);
417 self.flex_factors.push(flex.max(0.0));
418 }
419}
420
421impl Default for FlexRow { fn default() -> Self { Self::new() } }
422
423impl Widget for FlexRow {
424 fn type_name(&self) -> &'static str { "FlexRow" }
425 fn bounds(&self) -> Rect { self.bounds }
426 fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
427 fn children(&self) -> &[Box<dyn Widget>] { &self.children }
428 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
429
430 fn margin(&self) -> Insets { self.base.margin }
431 fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
432 fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
433 fn min_size(&self) -> Size { self.base.min_size }
434 fn max_size(&self) -> Size { self.base.max_size }
435
436 fn layout(&mut self, available: Size) -> Size {
437 let pad_l = self.inner_padding.left;
438 let pad_r = self.inner_padding.right;
439 let pad_t = self.inner_padding.top;
440 let pad_b = self.inner_padding.bottom;
441 let gap = self.gap;
442 let n = self.children.len();
443 if n == 0 { return available; }
444
445 let inner_w = (available.width - pad_l - pad_r).max(0.0);
446 let inner_h = (available.height - pad_t - pad_b).max(0.0);
447
448 let scale = device_scale();
449 let margins: Vec<Insets> = self.children.iter()
450 .map(|c| c.margin().scale(scale))
451 .collect();
452
453 let total_gap = if n > 1 { gap * (n - 1) as f64 } else { 0.0 };
454
455 let mut content_widths = vec![0.0f64; n];
459 let mut total_fixed_with_margins = 0.0f64;
460 let mut total_flex = 0.0f64;
461 let mut total_flex_margin_h = 0.0f64;
462
463 for i in 0..n {
464 let m = &margins[i];
465 let slot_h = (inner_h - m.bottom - m.top).max(0.0);
466 if self.flex_factors[i] == 0.0 {
467 let desired = self.children[i].layout(Size::new(inner_w, slot_h));
470 let clamped_w = desired.width
471 .clamp(self.children[i].min_size().width,
472 self.children[i].max_size().width);
473 content_widths[i] = clamped_w;
474 total_fixed_with_margins += clamped_w + m.horizontal();
475 } else {
476 total_flex += self.flex_factors[i];
477 total_flex_margin_h += m.horizontal();
478 }
479 }
480
481 let remaining = (inner_w
485 - total_fixed_with_margins
486 - total_gap
487 - total_flex_margin_h)
488 .max(0.0);
489 let flex_unit = if total_flex > 0.0 { remaining / total_flex } else { 0.0 };
490
491 for i in 0..n {
492 if self.flex_factors[i] > 0.0 {
493 let raw = self.flex_factors[i] * flex_unit;
494 content_widths[i] = raw
495 .clamp(self.children[i].min_size().width,
496 self.children[i].max_size().width);
497 }
498 }
499
500 let mut cursor_x = pad_l;
504 let mut max_slot_h = 0.0f64; for i in 0..n {
507 let m = &margins[i];
508 let slot_h = (inner_h - m.bottom - m.top).max(0.0);
509 let content_w = content_widths[i];
510
511 cursor_x += m.left;
513
514 let desired = self.children[i].layout(Size::new(content_w, slot_h));
516 let natural_h = desired.height;
517 let v_anchor = self.children[i].v_anchor();
518 let min_h = self.children[i].min_size().height;
519 let max_h = self.children[i].max_size().height;
520
521 let (child_y, child_h) = place_cross_v(
522 v_anchor, pad_b, inner_h, m.bottom, m.top, natural_h, min_h, max_h,
523 );
524
525 self.children[i].set_bounds(Rect::new(
527 cursor_x.round(), child_y.round(), content_w.round(), child_h.round(),
528 ));
529 max_slot_h = max_slot_h.max(child_h + m.vertical());
530
531 cursor_x += content_w + m.right + gap;
533 }
534
535 let natural_h = max_slot_h + pad_t + pad_b;
538 Size::new(available.width, natural_h)
539 }
540
541 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
542 if self.background.a > 0.001 {
543 let w = self.bounds.width;
544 let h = self.bounds.height;
545 ctx.set_fill_color(self.background);
546 ctx.begin_path();
547 ctx.rect(0.0, 0.0, w, h);
548 ctx.fill();
549 }
550 }
551
552 fn on_event(&mut self, _: &Event) -> EventResult { EventResult::Ignored }
553}