1use crate::color::Color;
36use crate::device_scale::device_scale;
37use crate::draw_ctx::DrawCtx;
38use crate::event::{Event, EventResult};
39use crate::geometry::{Rect, Size};
40use crate::layout_props::{resolve_fit_or_stretch, HAnchor, Insets, VAnchor, WidgetBase};
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 pub fit_width: bool,
164 pub top_anchor: bool,
170}
171
172impl FlexColumn {
173 pub fn new() -> Self {
174 Self {
175 bounds: Rect::default(),
176 children: Vec::new(),
177 flex_factors: Vec::new(),
178 base: WidgetBase::new(),
179 gap: 0.0,
180 inner_padding: Insets::ZERO,
181 background: Color::rgba(0.0, 0.0, 0.0, 0.0),
182 use_panel_bg: false,
183 fit_width: false,
184 top_anchor: false,
185 }
186 }
187
188 pub fn with_gap(mut self, gap: f64) -> Self {
189 self.gap = gap;
190 self
191 }
192 pub fn with_padding(mut self, p: f64) -> Self {
193 self.inner_padding = Insets::all(p);
194 self
195 }
196 pub fn with_inner_padding(mut self, p: Insets) -> Self {
197 self.inner_padding = p;
198 self
199 }
200 pub fn with_background(mut self, c: Color) -> Self {
201 self.background = c;
202 self
203 }
204 pub fn with_panel_bg(mut self) -> Self {
206 self.use_panel_bg = true;
207 self
208 }
209
210 pub fn with_fit_width(mut self, fit: bool) -> Self {
217 self.fit_width = fit;
218 self
219 }
220
221 pub fn with_top_anchor(mut self, on: bool) -> Self {
231 self.top_anchor = on;
232 self
233 }
234
235 pub fn with_margin(mut self, m: Insets) -> Self {
236 self.base.margin = m;
237 self
238 }
239 pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
240 self.base.h_anchor = h;
241 self
242 }
243 pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
244 self.base.v_anchor = v;
245 self
246 }
247 pub fn with_min_size(mut self, s: Size) -> Self {
248 self.base.min_size = s;
249 self
250 }
251 pub fn with_max_size(mut self, s: Size) -> Self {
252 self.base.max_size = s;
253 self
254 }
255
256 pub fn add(mut self, child: Box<dyn Widget>) -> Self {
258 self.children.push(child);
259 self.flex_factors.push(0.0);
260 self
261 }
262
263 pub fn add_flex(mut self, child: Box<dyn Widget>, flex: f64) -> Self {
265 self.children.push(child);
266 self.flex_factors.push(flex.max(0.0));
267 self
268 }
269
270 pub fn push(&mut self, child: Box<dyn Widget>, flex: f64) {
272 self.children.push(child);
273 self.flex_factors.push(flex.max(0.0));
274 }
275}
276
277impl Default for FlexColumn {
278 fn default() -> Self {
279 Self::new()
280 }
281}
282
283impl Widget for FlexColumn {
284 fn type_name(&self) -> &'static str {
285 "FlexColumn"
286 }
287 fn bounds(&self) -> Rect {
288 self.bounds
289 }
290 fn set_bounds(&mut self, b: Rect) {
291 self.bounds = b;
292 }
293 fn children(&self) -> &[Box<dyn Widget>] {
294 &self.children
295 }
296 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
297 &mut self.children
298 }
299
300 fn margin(&self) -> Insets {
301 self.base.margin
302 }
303 fn h_anchor(&self) -> HAnchor {
304 self.base.h_anchor
305 }
306 fn v_anchor(&self) -> VAnchor {
307 self.base.v_anchor
308 }
309 fn min_size(&self) -> Size {
310 self.base.min_size
311 }
312 fn max_size(&self) -> Size {
313 self.base.max_size
314 }
315
316 fn measure_min_height(&self, available_w: f64) -> f64 {
317 let pad_l = self.inner_padding.left;
324 let pad_r = self.inner_padding.right;
325 let pad_t = self.inner_padding.top;
326 let pad_b = self.inner_padding.bottom;
327 let inner_w = (available_w - pad_l - pad_r).max(0.0);
328 let scale = device_scale();
329 let n = self.children.len();
330 let mut total = 0.0_f64;
331 for child in self.children.iter() {
332 let m = child.margin().scale(scale);
333 let slot_w = (inner_w - m.left - m.right).max(0.0);
334 total += child.measure_min_height(slot_w) + m.vertical();
335 }
336 total += pad_t + pad_b;
337 if n > 1 {
338 total += self.gap * (n - 1) as f64;
339 }
340 total.max(self.base.min_size.height)
341 }
342
343 fn layout(&mut self, available: Size) -> Size {
344 let pad_l = self.inner_padding.left;
345 let pad_r = self.inner_padding.right;
346 let pad_t = self.inner_padding.top;
347 let pad_b = self.inner_padding.bottom;
348 let gap = self.gap;
349 let n = self.children.len();
350 if n == 0 {
351 return available;
352 }
353
354 let inner_w = (available.width - pad_l - pad_r).max(0.0);
355 let inner_h = (available.height - pad_t - pad_b).max(0.0);
356
357 let scale = device_scale();
359 let margins: Vec<Insets> = self
360 .children
361 .iter()
362 .map(|c| c.margin().scale(scale))
363 .collect();
364
365 let total_gap = if n > 1 { gap * (n - 1) as f64 } else { 0.0 };
366
367 let mut content_heights = vec![0.0f64; n];
374 let mut total_fixed_with_margins = 0.0f64;
375 let mut total_flex = 0.0f64;
376 let mut total_flex_margin_v = 0.0f64;
377 let mut max_child_natural_w = 0.0f64;
378
379 for i in 0..n {
380 let m = &margins[i];
381 let slot_w = (inner_w - m.left - m.right).max(0.0);
382 if self.flex_factors[i] == 0.0 {
383 let desired = self.children[i].layout(Size::new(slot_w, inner_h));
386 let clamped_h = desired.height.clamp(
387 self.children[i].min_size().height,
388 self.children[i].max_size().height,
389 );
390 content_heights[i] = clamped_h;
391 total_fixed_with_margins += clamped_h + m.vertical();
392 max_child_natural_w = max_child_natural_w.max(desired.width + m.horizontal());
393 } else {
394 total_flex += self.flex_factors[i];
395 total_flex_margin_v += m.vertical();
396 }
397 }
398
399 let remaining =
403 (inner_h - total_fixed_with_margins - total_gap - total_flex_margin_v).max(0.0);
404 let flex_unit = if total_flex > 0.0 {
405 remaining / total_flex
406 } else {
407 0.0
408 };
409
410 for i in 0..n {
411 if self.flex_factors[i] > 0.0 {
412 let raw = self.flex_factors[i] * flex_unit;
413 content_heights[i] = raw.clamp(
414 self.children[i].min_size().height,
415 self.children[i].max_size().height,
416 );
417 }
418 }
419
420 let natural_content_h = total_fixed_with_margins + total_gap;
423 let effective_h = if total_flex > 0.0 {
424 inner_h
425 } else {
426 natural_content_h
427 };
428
429 let measuring_natural_height = available.height > 1.0e9;
443 let mut cursor_y = if self.top_anchor || !measuring_natural_height {
444 available.height - pad_t
445 } else {
446 pad_b + effective_h
447 };
448
449 for i in 0..n {
450 let m = &margins[i];
451 let slot_w = (inner_w - m.left - m.right).max(0.0);
452 let content_h = content_heights[i];
453
454 cursor_y -= m.top;
456 let child_bottom = cursor_y - content_h;
457
458 let desired = self.children[i].layout(Size::new(slot_w, content_h));
460 let natural_w = desired.width;
461 let h_anchor = self.children[i].h_anchor();
462 let min_w = self.children[i].min_size().width;
463 let max_w = self.children[i].max_size().width;
464
465 let (child_x, child_w) = place_cross_h(
466 h_anchor, pad_l, inner_w, m.left, m.right, natural_w, min_w, max_w,
467 );
468
469 self.children[i].set_bounds(Rect::new(
472 child_x.round(),
473 child_bottom.round(),
474 child_w.round(),
475 content_h.round(),
476 ));
477
478 cursor_y = child_bottom - m.bottom - gap;
480 }
481
482 let reported_w = if self.fit_width {
492 max_child_natural_w + pad_l + pad_r
493 } else {
494 available.width
495 };
496 if total_flex > 0.0 {
497 Size::new(reported_w, available.height)
498 } else {
499 Size::new(reported_w, natural_content_h + pad_t + pad_b)
500 }
501 }
502
503 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
504 let bg = if self.use_panel_bg {
505 Some(ctx.visuals().panel_fill)
506 } else if self.background.a > 0.001 {
507 Some(self.background)
508 } else {
509 None
510 };
511 if let Some(color) = bg {
512 let w = self.bounds.width;
513 let h = self.bounds.height;
514 ctx.set_fill_color(color);
515 ctx.begin_path();
516 ctx.rect(0.0, 0.0, w, h);
517 ctx.fill();
518 }
519 }
520
521 fn on_event(&mut self, _: &Event) -> EventResult {
522 EventResult::Ignored
523 }
524}
525
526pub struct FlexRow {
532 bounds: Rect,
533 children: Vec<Box<dyn Widget>>,
534 flex_factors: Vec<f64>,
535 base: WidgetBase,
536 pub gap: f64,
537 pub inner_padding: Insets,
538 pub background: Color,
539}
540
541impl FlexRow {
542 pub fn new() -> Self {
543 Self {
544 bounds: Rect::default(),
545 children: Vec::new(),
546 flex_factors: Vec::new(),
547 base: WidgetBase::new(),
548 gap: 0.0,
549 inner_padding: Insets::ZERO,
550 background: Color::rgba(0.0, 0.0, 0.0, 0.0),
551 }
552 }
553
554 pub fn with_gap(mut self, gap: f64) -> Self {
555 self.gap = gap;
556 self
557 }
558 pub fn with_padding(mut self, p: f64) -> Self {
559 self.inner_padding = Insets::all(p);
560 self
561 }
562 pub fn with_inner_padding(mut self, p: Insets) -> Self {
563 self.inner_padding = p;
564 self
565 }
566 pub fn with_background(mut self, c: Color) -> Self {
567 self.background = c;
568 self
569 }
570
571 pub fn with_margin(mut self, m: Insets) -> Self {
572 self.base.margin = m;
573 self
574 }
575 pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
576 self.base.h_anchor = h;
577 self
578 }
579 pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
580 self.base.v_anchor = v;
581 self
582 }
583 pub fn with_min_size(mut self, s: Size) -> Self {
584 self.base.min_size = s;
585 self
586 }
587 pub fn with_max_size(mut self, s: Size) -> Self {
588 self.base.max_size = s;
589 self
590 }
591
592 pub fn add(mut self, child: Box<dyn Widget>) -> Self {
593 self.children.push(child);
594 self.flex_factors.push(0.0);
595 self
596 }
597
598 pub fn add_flex(mut self, child: Box<dyn Widget>, flex: f64) -> Self {
599 self.children.push(child);
600 self.flex_factors.push(flex.max(0.0));
601 self
602 }
603
604 pub fn push(&mut self, child: Box<dyn Widget>, flex: f64) {
605 self.children.push(child);
606 self.flex_factors.push(flex.max(0.0));
607 }
608}
609
610impl Default for FlexRow {
611 fn default() -> Self {
612 Self::new()
613 }
614}
615
616impl Widget for FlexRow {
617 fn type_name(&self) -> &'static str {
618 "FlexRow"
619 }
620 fn bounds(&self) -> Rect {
621 self.bounds
622 }
623 fn set_bounds(&mut self, b: Rect) {
624 self.bounds = b;
625 }
626 fn children(&self) -> &[Box<dyn Widget>] {
627 &self.children
628 }
629 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
630 &mut self.children
631 }
632
633 fn margin(&self) -> Insets {
634 self.base.margin
635 }
636 fn h_anchor(&self) -> HAnchor {
637 self.base.h_anchor
638 }
639 fn v_anchor(&self) -> VAnchor {
640 self.base.v_anchor
641 }
642 fn min_size(&self) -> Size {
643 self.base.min_size
644 }
645 fn max_size(&self) -> Size {
646 self.base.max_size
647 }
648
649 fn layout(&mut self, available: Size) -> Size {
650 let pad_l = self.inner_padding.left;
651 let pad_r = self.inner_padding.right;
652 let pad_t = self.inner_padding.top;
653 let pad_b = self.inner_padding.bottom;
654 let gap = self.gap;
655 let n = self.children.len();
656 if n == 0 {
657 return available;
658 }
659
660 let inner_w = (available.width - pad_l - pad_r).max(0.0);
661 let inner_h = (available.height - pad_t - pad_b).max(0.0);
662
663 let scale = device_scale();
664 let margins: Vec<Insets> = self
665 .children
666 .iter()
667 .map(|c| c.margin().scale(scale))
668 .collect();
669
670 let total_gap = if n > 1 { gap * (n - 1) as f64 } else { 0.0 };
671
672 let mut content_widths = vec![0.0f64; n];
676 let mut total_fixed_with_margins = 0.0f64;
677 let mut total_flex = 0.0f64;
678 let mut total_flex_margin_h = 0.0f64;
679
680 for i in 0..n {
681 let m = &margins[i];
682 let slot_h = (inner_h - m.bottom - m.top).max(0.0);
683 if self.flex_factors[i] == 0.0 {
684 let desired = self.children[i].layout(Size::new(inner_w, slot_h));
687 let clamped_w = desired.width.clamp(
688 self.children[i].min_size().width,
689 self.children[i].max_size().width,
690 );
691 content_widths[i] = clamped_w;
692 total_fixed_with_margins += clamped_w + m.horizontal();
693 } else {
694 total_flex += self.flex_factors[i];
695 total_flex_margin_h += m.horizontal();
696 }
697 }
698
699 let remaining =
703 (inner_w - total_fixed_with_margins - total_gap - total_flex_margin_h).max(0.0);
704 let flex_unit = if total_flex > 0.0 {
705 remaining / total_flex
706 } else {
707 0.0
708 };
709
710 for i in 0..n {
711 if self.flex_factors[i] > 0.0 {
712 let raw = self.flex_factors[i] * flex_unit;
713 content_widths[i] = raw.clamp(
714 self.children[i].min_size().width,
715 self.children[i].max_size().width,
716 );
717 }
718 }
719
720 let mut cursor_x = pad_l;
724 let mut max_slot_h = 0.0f64; for i in 0..n {
727 let m = &margins[i];
728 let slot_h = (inner_h - m.bottom - m.top).max(0.0);
729 let content_w = content_widths[i];
730
731 cursor_x += m.left;
733
734 let desired = self.children[i].layout(Size::new(content_w, slot_h));
736 let natural_h = desired.height;
737 let v_anchor = self.children[i].v_anchor();
738 let min_h = self.children[i].min_size().height;
739 let max_h = self.children[i].max_size().height;
740
741 let (child_y, child_h) = place_cross_v(
742 v_anchor, pad_b, inner_h, m.bottom, m.top, natural_h, min_h, max_h,
743 );
744
745 self.children[i].set_bounds(Rect::new(
747 cursor_x.round(),
748 child_y.round(),
749 content_w.round(),
750 child_h.round(),
751 ));
752 max_slot_h = max_slot_h.max(child_h + m.vertical());
753
754 cursor_x += content_w + m.right + gap;
756 }
757
758 let natural_h = max_slot_h + pad_t + pad_b;
761 Size::new(available.width, natural_h)
762 }
763
764 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
765 if self.background.a > 0.001 {
766 let w = self.bounds.width;
767 let h = self.bounds.height;
768 ctx.set_fill_color(self.background);
769 ctx.begin_path();
770 ctx.rect(0.0, 0.0, w, h);
771 ctx.fill();
772 }
773 }
774
775 fn on_event(&mut self, _: &Event) -> EventResult {
776 EventResult::Ignored
777 }
778}