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 widget_base(&self) -> Option<&WidgetBase> {
304 Some(&self.base)
305 }
306 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
307 Some(&mut self.base)
308 }
309 fn padding(&self) -> Insets {
310 self.inner_padding
311 }
312 fn h_anchor(&self) -> HAnchor {
313 self.base.h_anchor
314 }
315 fn v_anchor(&self) -> VAnchor {
316 self.base.v_anchor
317 }
318 fn min_size(&self) -> Size {
319 self.base.min_size
320 }
321 fn max_size(&self) -> Size {
322 self.base.max_size
323 }
324
325 fn measure_min_height(&self, available_w: f64) -> f64 {
326 let pad_l = self.inner_padding.left;
333 let pad_r = self.inner_padding.right;
334 let pad_t = self.inner_padding.top;
335 let pad_b = self.inner_padding.bottom;
336 let inner_w = (available_w - pad_l - pad_r).max(0.0);
337 let scale = device_scale();
338 let n = self.children.len();
339 let mut total = 0.0_f64;
340 for child in self.children.iter() {
341 let m = child.margin().scale(scale);
342 let slot_w = (inner_w - m.left - m.right).max(0.0);
343 total += child.measure_min_height(slot_w) + m.vertical();
344 }
345 total += pad_t + pad_b;
346 if n > 1 {
347 total += self.gap * (n - 1) as f64;
348 }
349 total.max(self.base.min_size.height)
350 }
351
352 fn layout(&mut self, available: Size) -> Size {
353 let pad_l = self.inner_padding.left;
354 let pad_r = self.inner_padding.right;
355 let pad_t = self.inner_padding.top;
356 let pad_b = self.inner_padding.bottom;
357 let gap = self.gap;
358 let n = self.children.len();
359 if n == 0 {
360 return available;
361 }
362
363 let inner_w = (available.width - pad_l - pad_r).max(0.0);
364 let inner_h = (available.height - pad_t - pad_b).max(0.0);
365
366 let scale = device_scale();
368 let margins: Vec<Insets> = self
369 .children
370 .iter()
371 .map(|c| c.margin().scale(scale))
372 .collect();
373
374 let total_gap = if n > 1 { gap * (n - 1) as f64 } else { 0.0 };
375
376 let mut content_heights = vec![0.0f64; n];
383 let mut total_fixed_with_margins = 0.0f64;
384 let mut total_flex = 0.0f64;
385 let mut total_flex_margin_v = 0.0f64;
386 let mut max_child_natural_w = 0.0f64;
387
388 for i in 0..n {
389 let m = &margins[i];
390 let slot_w = (inner_w - m.left - m.right).max(0.0);
391 if self.flex_factors[i] == 0.0 {
392 let desired = self.children[i].layout(Size::new(slot_w, inner_h));
395 let clamped_h = desired.height.clamp(
396 self.children[i].min_size().height,
397 self.children[i].max_size().height,
398 );
399 content_heights[i] = clamped_h;
400 total_fixed_with_margins += clamped_h + m.vertical();
401 max_child_natural_w = max_child_natural_w.max(desired.width + m.horizontal());
402 } else {
403 total_flex += self.flex_factors[i];
404 total_flex_margin_v += m.vertical();
405 }
406 }
407
408 let remaining =
412 (inner_h - total_fixed_with_margins - total_gap - total_flex_margin_v).max(0.0);
413 let flex_unit = if total_flex > 0.0 {
414 remaining / total_flex
415 } else {
416 0.0
417 };
418
419 for i in 0..n {
420 if self.flex_factors[i] > 0.0 {
421 let raw = self.flex_factors[i] * flex_unit;
422 content_heights[i] = raw.clamp(
423 self.children[i].min_size().height,
424 self.children[i].max_size().height,
425 );
426 }
427 }
428
429 let natural_content_h = total_fixed_with_margins + total_gap;
432 let effective_h = if total_flex > 0.0 {
433 inner_h
434 } else {
435 natural_content_h
436 };
437
438 let measuring_natural_height = available.height > 1.0e9;
452 let mut cursor_y = if self.top_anchor || !measuring_natural_height {
453 available.height - pad_t
454 } else {
455 pad_b + effective_h
456 };
457
458 for i in 0..n {
459 let m = &margins[i];
460 let slot_w = (inner_w - m.left - m.right).max(0.0);
461 let content_h = content_heights[i];
462
463 cursor_y -= m.top;
465 let child_bottom = cursor_y - content_h;
466
467 let desired = self.children[i].layout(Size::new(slot_w, content_h));
469 let natural_w = desired.width;
470 let h_anchor = self.children[i].h_anchor();
471 let min_w = self.children[i].min_size().width;
472 let max_w = self.children[i].max_size().width;
473
474 let (child_x, child_w) = place_cross_h(
475 h_anchor, pad_l, inner_w, m.left, m.right, natural_w, min_w, max_w,
476 );
477
478 self.children[i].set_bounds(Rect::new(
481 child_x.round(),
482 child_bottom.round(),
483 child_w.round(),
484 content_h.round(),
485 ));
486
487 cursor_y = child_bottom - m.bottom - gap;
489 }
490
491 let reported_w = if self.fit_width {
501 max_child_natural_w + pad_l + pad_r
502 } else {
503 available.width
504 };
505 if total_flex > 0.0 {
506 Size::new(reported_w, available.height)
507 } else {
508 Size::new(reported_w, natural_content_h + pad_t + pad_b)
509 }
510 }
511
512 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
513 let bg = if self.use_panel_bg {
514 Some(ctx.visuals().panel_fill)
515 } else if self.background.a > 0.001 {
516 Some(self.background)
517 } else {
518 None
519 };
520 if let Some(color) = bg {
521 let w = self.bounds.width;
522 let h = self.bounds.height;
523 ctx.set_fill_color(color);
524 ctx.begin_path();
525 ctx.rect(0.0, 0.0, w, h);
526 ctx.fill();
527 }
528 }
529
530 fn on_event(&mut self, _: &Event) -> EventResult {
531 EventResult::Ignored
532 }
533}
534
535pub struct FlexRow {
541 bounds: Rect,
542 children: Vec<Box<dyn Widget>>,
543 flex_factors: Vec<f64>,
544 base: WidgetBase,
545 pub gap: f64,
546 pub inner_padding: Insets,
547 pub background: Color,
548}
549
550impl FlexRow {
551 pub fn new() -> Self {
552 Self {
553 bounds: Rect::default(),
554 children: Vec::new(),
555 flex_factors: Vec::new(),
556 base: WidgetBase::new(),
557 gap: 0.0,
558 inner_padding: Insets::ZERO,
559 background: Color::rgba(0.0, 0.0, 0.0, 0.0),
560 }
561 }
562
563 pub fn with_gap(mut self, gap: f64) -> Self {
564 self.gap = gap;
565 self
566 }
567 pub fn with_padding(mut self, p: f64) -> Self {
568 self.inner_padding = Insets::all(p);
569 self
570 }
571 pub fn with_inner_padding(mut self, p: Insets) -> Self {
572 self.inner_padding = p;
573 self
574 }
575 pub fn with_background(mut self, c: Color) -> Self {
576 self.background = c;
577 self
578 }
579
580 pub fn with_margin(mut self, m: Insets) -> Self {
581 self.base.margin = m;
582 self
583 }
584 pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
585 self.base.h_anchor = h;
586 self
587 }
588 pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
589 self.base.v_anchor = v;
590 self
591 }
592 pub fn with_min_size(mut self, s: Size) -> Self {
593 self.base.min_size = s;
594 self
595 }
596 pub fn with_max_size(mut self, s: Size) -> Self {
597 self.base.max_size = s;
598 self
599 }
600
601 pub fn add(mut self, child: Box<dyn Widget>) -> Self {
602 self.children.push(child);
603 self.flex_factors.push(0.0);
604 self
605 }
606
607 pub fn add_flex(mut self, child: Box<dyn Widget>, flex: f64) -> Self {
608 self.children.push(child);
609 self.flex_factors.push(flex.max(0.0));
610 self
611 }
612
613 pub fn push(&mut self, child: Box<dyn Widget>, flex: f64) {
614 self.children.push(child);
615 self.flex_factors.push(flex.max(0.0));
616 }
617}
618
619impl Default for FlexRow {
620 fn default() -> Self {
621 Self::new()
622 }
623}
624
625impl Widget for FlexRow {
626 fn type_name(&self) -> &'static str {
627 "FlexRow"
628 }
629 fn bounds(&self) -> Rect {
630 self.bounds
631 }
632 fn set_bounds(&mut self, b: Rect) {
633 self.bounds = b;
634 }
635 fn children(&self) -> &[Box<dyn Widget>] {
636 &self.children
637 }
638 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
639 &mut self.children
640 }
641
642 fn margin(&self) -> Insets {
643 self.base.margin
644 }
645 fn widget_base(&self) -> Option<&WidgetBase> {
646 Some(&self.base)
647 }
648 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
649 Some(&mut self.base)
650 }
651 fn padding(&self) -> Insets {
652 self.inner_padding
653 }
654 fn h_anchor(&self) -> HAnchor {
655 self.base.h_anchor
656 }
657 fn v_anchor(&self) -> VAnchor {
658 self.base.v_anchor
659 }
660 fn min_size(&self) -> Size {
661 self.base.min_size
662 }
663 fn max_size(&self) -> Size {
664 self.base.max_size
665 }
666
667 fn layout(&mut self, available: Size) -> Size {
668 let pad_l = self.inner_padding.left;
669 let pad_r = self.inner_padding.right;
670 let pad_t = self.inner_padding.top;
671 let pad_b = self.inner_padding.bottom;
672 let gap = self.gap;
673 let n = self.children.len();
674 if n == 0 {
675 return available;
676 }
677
678 let inner_w = (available.width - pad_l - pad_r).max(0.0);
679 let inner_h = (available.height - pad_t - pad_b).max(0.0);
680
681 let scale = device_scale();
682 let margins: Vec<Insets> = self
683 .children
684 .iter()
685 .map(|c| c.margin().scale(scale))
686 .collect();
687
688 let total_gap = if n > 1 { gap * (n - 1) as f64 } else { 0.0 };
689
690 let mut content_widths = vec![0.0f64; n];
694 let mut total_fixed_with_margins = 0.0f64;
695 let mut total_flex = 0.0f64;
696 let mut total_flex_margin_h = 0.0f64;
697
698 for i in 0..n {
699 let m = &margins[i];
700 let slot_h = (inner_h - m.bottom - m.top).max(0.0);
701 if self.flex_factors[i] == 0.0 {
702 let desired = self.children[i].layout(Size::new(inner_w, slot_h));
705 let clamped_w = desired.width.clamp(
706 self.children[i].min_size().width,
707 self.children[i].max_size().width,
708 );
709 content_widths[i] = clamped_w;
710 total_fixed_with_margins += clamped_w + m.horizontal();
711 } else {
712 total_flex += self.flex_factors[i];
713 total_flex_margin_h += m.horizontal();
714 }
715 }
716
717 let remaining =
721 (inner_w - total_fixed_with_margins - total_gap - total_flex_margin_h).max(0.0);
722 let flex_unit = if total_flex > 0.0 {
723 remaining / total_flex
724 } else {
725 0.0
726 };
727
728 for i in 0..n {
729 if self.flex_factors[i] > 0.0 {
730 let raw = self.flex_factors[i] * flex_unit;
731 content_widths[i] = raw.clamp(
732 self.children[i].min_size().width,
733 self.children[i].max_size().width,
734 );
735 }
736 }
737
738 let mut cursor_x = pad_l;
742 let mut max_slot_h = 0.0f64; for i in 0..n {
745 let m = &margins[i];
746 let slot_h = (inner_h - m.bottom - m.top).max(0.0);
747 let content_w = content_widths[i];
748
749 cursor_x += m.left;
751
752 let desired = self.children[i].layout(Size::new(content_w, slot_h));
754 let natural_h = desired.height;
755 let v_anchor = self.children[i].v_anchor();
756 let min_h = self.children[i].min_size().height;
757 let max_h = self.children[i].max_size().height;
758
759 let (child_y, child_h) = place_cross_v(
760 v_anchor, pad_b, inner_h, m.bottom, m.top, natural_h, min_h, max_h,
761 );
762
763 self.children[i].set_bounds(Rect::new(
765 cursor_x.round(),
766 child_y.round(),
767 content_w.round(),
768 child_h.round(),
769 ));
770 max_slot_h = max_slot_h.max(child_h + m.vertical());
771
772 cursor_x += content_w + m.right + gap;
774 }
775
776 let natural_h = max_slot_h + pad_t + pad_b;
779 Size::new(available.width, natural_h)
780 }
781
782 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
783 if self.background.a > 0.001 {
784 let w = self.bounds.width;
785 let h = self.bounds.height;
786 ctx.set_fill_color(self.background);
787 ctx.begin_path();
788 ctx.rect(0.0, 0.0, w, h);
789 ctx.fill();
790 }
791 }
792
793 fn on_event(&mut self, _: &Event) -> EventResult {
794 EventResult::Ignored
795 }
796}