Skip to main content

slt/context/
container.rs

1use super::*;
2
3/// Fluent builder for configuring containers before calling `.col()` or `.row()`.
4///
5/// Obtain one via [`Context::container`] or [`Context::bordered`]. Chain the
6/// configuration methods you need, then finalize with `.col(|ui| { ... })` or
7/// `.row(|ui| { ... })`.
8///
9/// # Example
10///
11/// ```no_run
12/// # slt::run(|ui: &mut slt::Context| {
13/// use slt::{Border, Color};
14/// ui.container()
15///     .border(Border::Rounded)
16///     .pad(1)
17///     .grow(1)
18///     .col(|ui| {
19///         ui.text("inside a bordered, padded, growing column");
20///     });
21/// # });
22/// ```
23#[must_use = "ContainerBuilder does nothing until .col(), .row(), .line(), or .draw() is called"]
24pub struct ContainerBuilder<'a> {
25    pub(crate) ctx: &'a mut Context,
26    pub(crate) gap: u32,
27    pub(crate) row_gap: Option<u32>,
28    pub(crate) col_gap: Option<u32>,
29    pub(crate) align: Align,
30    pub(crate) align_self_value: Option<Align>,
31    pub(crate) justify: Justify,
32    pub(crate) border: Option<Border>,
33    pub(crate) border_sides: BorderSides,
34    pub(crate) border_style: Style,
35    pub(crate) bg: Option<Color>,
36    pub(crate) text_color: Option<Color>,
37    pub(crate) dark_bg: Option<Color>,
38    pub(crate) dark_border_style: Option<Style>,
39    pub(crate) group_hover_bg: Option<Color>,
40    pub(crate) group_hover_border_style: Option<Style>,
41    pub(crate) group_name: Option<String>,
42    pub(crate) padding: Padding,
43    pub(crate) margin: Margin,
44    pub(crate) constraints: Constraints,
45    pub(crate) title: Option<(String, Style)>,
46    pub(crate) grow: u16,
47    pub(crate) scroll_offset: Option<u32>,
48}
49
50/// Drawing context for the [`Context::canvas`] widget.
51///
52/// Provides pixel-level drawing on a braille character grid. Each terminal
53/// cell maps to a 2x4 dot matrix, so a canvas of `width` columns x `height`
54/// rows gives `width*2` x `height*4` pixel resolution.
55/// A colored pixel in the canvas grid.
56#[derive(Debug, Clone, Copy)]
57struct CanvasPixel {
58    bits: u32,
59    color: Color,
60}
61
62/// Text label placed on the canvas.
63#[derive(Debug, Clone)]
64struct CanvasLabel {
65    x: usize,
66    y: usize,
67    text: String,
68    color: Color,
69}
70
71/// A layer in the canvas, supporting z-ordering.
72#[derive(Debug, Clone)]
73struct CanvasLayer {
74    grid: Vec<Vec<CanvasPixel>>,
75    labels: Vec<CanvasLabel>,
76}
77
78/// Drawing context for the canvas widget.
79pub struct CanvasContext {
80    layers: Vec<CanvasLayer>,
81    cols: usize,
82    rows: usize,
83    px_w: usize,
84    px_h: usize,
85    current_color: Color,
86}
87
88impl CanvasContext {
89    pub(crate) fn new(cols: usize, rows: usize) -> Self {
90        Self {
91            layers: vec![Self::new_layer(cols, rows)],
92            cols,
93            rows,
94            px_w: cols * 2,
95            px_h: rows * 4,
96            current_color: Color::Reset,
97        }
98    }
99
100    fn new_layer(cols: usize, rows: usize) -> CanvasLayer {
101        CanvasLayer {
102            grid: vec![
103                vec![
104                    CanvasPixel {
105                        bits: 0,
106                        color: Color::Reset,
107                    };
108                    cols
109                ];
110                rows
111            ],
112            labels: Vec::new(),
113        }
114    }
115
116    fn current_layer_mut(&mut self) -> Option<&mut CanvasLayer> {
117        self.layers.last_mut()
118    }
119
120    fn dot_with_color(&mut self, x: usize, y: usize, color: Color) {
121        if x >= self.px_w || y >= self.px_h {
122            return;
123        }
124
125        let char_col = x / 2;
126        let char_row = y / 4;
127        let sub_col = x % 2;
128        let sub_row = y % 4;
129        const LEFT_BITS: [u32; 4] = [0x01, 0x02, 0x04, 0x40];
130        const RIGHT_BITS: [u32; 4] = [0x08, 0x10, 0x20, 0x80];
131
132        let bit = if sub_col == 0 {
133            LEFT_BITS[sub_row]
134        } else {
135            RIGHT_BITS[sub_row]
136        };
137
138        if let Some(layer) = self.current_layer_mut() {
139            let cell = &mut layer.grid[char_row][char_col];
140            let new_bits = cell.bits | bit;
141            if new_bits != cell.bits {
142                cell.bits = new_bits;
143                cell.color = color;
144            }
145        }
146    }
147
148    fn dot_isize(&mut self, x: isize, y: isize) {
149        if x >= 0 && y >= 0 {
150            self.dot(x as usize, y as usize);
151        }
152    }
153
154    /// Get the pixel width of the canvas.
155    pub fn width(&self) -> usize {
156        self.px_w
157    }
158
159    /// Get the pixel height of the canvas.
160    pub fn height(&self) -> usize {
161        self.px_h
162    }
163
164    /// Set a single pixel at `(x, y)`.
165    pub fn dot(&mut self, x: usize, y: usize) {
166        self.dot_with_color(x, y, self.current_color);
167    }
168
169    /// Draw a line from `(x0, y0)` to `(x1, y1)` using Bresenham's algorithm.
170    pub fn line(&mut self, x0: usize, y0: usize, x1: usize, y1: usize) {
171        let (mut x, mut y) = (x0 as isize, y0 as isize);
172        let (x1, y1) = (x1 as isize, y1 as isize);
173        let dx = (x1 - x).abs();
174        let dy = -(y1 - y).abs();
175        let sx = if x < x1 { 1 } else { -1 };
176        let sy = if y < y1 { 1 } else { -1 };
177        let mut err = dx + dy;
178
179        loop {
180            self.dot_isize(x, y);
181            if x == x1 && y == y1 {
182                break;
183            }
184            let e2 = 2 * err;
185            if e2 >= dy {
186                err += dy;
187                x += sx;
188            }
189            if e2 <= dx {
190                err += dx;
191                y += sy;
192            }
193        }
194    }
195
196    /// Draw a rectangle outline from `(x, y)` with `w` width and `h` height.
197    pub fn rect(&mut self, x: usize, y: usize, w: usize, h: usize) {
198        if w == 0 || h == 0 {
199            return;
200        }
201
202        self.line(x, y, x + w.saturating_sub(1), y);
203        self.line(
204            x + w.saturating_sub(1),
205            y,
206            x + w.saturating_sub(1),
207            y + h.saturating_sub(1),
208        );
209        self.line(
210            x + w.saturating_sub(1),
211            y + h.saturating_sub(1),
212            x,
213            y + h.saturating_sub(1),
214        );
215        self.line(x, y + h.saturating_sub(1), x, y);
216    }
217
218    /// Draw a circle outline centered at `(cx, cy)` with radius `r`.
219    pub fn circle(&mut self, cx: usize, cy: usize, r: usize) {
220        let mut x = r as isize;
221        let mut y: isize = 0;
222        let mut err: isize = 1 - x;
223        let (cx, cy) = (cx as isize, cy as isize);
224
225        while x >= y {
226            for &(dx, dy) in &[
227                (x, y),
228                (y, x),
229                (-x, y),
230                (-y, x),
231                (x, -y),
232                (y, -x),
233                (-x, -y),
234                (-y, -x),
235            ] {
236                let px = cx + dx;
237                let py = cy + dy;
238                self.dot_isize(px, py);
239            }
240
241            y += 1;
242            if err < 0 {
243                err += 2 * y + 1;
244            } else {
245                x -= 1;
246                err += 2 * (y - x) + 1;
247            }
248        }
249    }
250
251    /// Set the drawing color for subsequent shapes.
252    pub fn set_color(&mut self, color: Color) {
253        self.current_color = color;
254    }
255
256    /// Get the current drawing color.
257    pub fn color(&self) -> Color {
258        self.current_color
259    }
260
261    /// Draw a filled rectangle.
262    pub fn filled_rect(&mut self, x: usize, y: usize, w: usize, h: usize) {
263        if w == 0 || h == 0 {
264            return;
265        }
266
267        let x_end = x.saturating_add(w).min(self.px_w);
268        let y_end = y.saturating_add(h).min(self.px_h);
269        if x >= x_end || y >= y_end {
270            return;
271        }
272
273        for yy in y..y_end {
274            self.line(x, yy, x_end.saturating_sub(1), yy);
275        }
276    }
277
278    /// Draw a filled circle.
279    pub fn filled_circle(&mut self, cx: usize, cy: usize, r: usize) {
280        let (cx, cy, r) = (cx as isize, cy as isize, r as isize);
281        for y in (cy - r)..=(cy + r) {
282            let dy = y - cy;
283            let span_sq = (r * r - dy * dy).max(0);
284            let dx = (span_sq as f64).sqrt() as isize;
285            for x in (cx - dx)..=(cx + dx) {
286                self.dot_isize(x, y);
287            }
288        }
289    }
290
291    /// Draw a triangle outline.
292    pub fn triangle(&mut self, x0: usize, y0: usize, x1: usize, y1: usize, x2: usize, y2: usize) {
293        self.line(x0, y0, x1, y1);
294        self.line(x1, y1, x2, y2);
295        self.line(x2, y2, x0, y0);
296    }
297
298    /// Draw a filled triangle.
299    pub fn filled_triangle(
300        &mut self,
301        x0: usize,
302        y0: usize,
303        x1: usize,
304        y1: usize,
305        x2: usize,
306        y2: usize,
307    ) {
308        let vertices = [
309            (x0 as isize, y0 as isize),
310            (x1 as isize, y1 as isize),
311            (x2 as isize, y2 as isize),
312        ];
313        let min_y = vertices.iter().map(|(_, y)| *y).min().unwrap_or(0);
314        let max_y = vertices.iter().map(|(_, y)| *y).max().unwrap_or(-1);
315
316        for y in min_y..=max_y {
317            let mut intersections: Vec<f64> = Vec::new();
318
319            for edge in [(0usize, 1usize), (1usize, 2usize), (2usize, 0usize)] {
320                let (x_a, y_a) = vertices[edge.0];
321                let (x_b, y_b) = vertices[edge.1];
322                if y_a == y_b {
323                    continue;
324                }
325
326                let (x_start, y_start, x_end, y_end) = if y_a < y_b {
327                    (x_a, y_a, x_b, y_b)
328                } else {
329                    (x_b, y_b, x_a, y_a)
330                };
331
332                if y < y_start || y >= y_end {
333                    continue;
334                }
335
336                let t = (y - y_start) as f64 / (y_end - y_start) as f64;
337                intersections.push(x_start as f64 + t * (x_end - x_start) as f64);
338            }
339
340            intersections.sort_by(|a, b| a.total_cmp(b));
341            let mut i = 0usize;
342            while i + 1 < intersections.len() {
343                let x_start = intersections[i].ceil() as isize;
344                let x_end = intersections[i + 1].floor() as isize;
345                for x in x_start..=x_end {
346                    self.dot_isize(x, y);
347                }
348                i += 2;
349            }
350        }
351
352        self.triangle(x0, y0, x1, y1, x2, y2);
353    }
354
355    /// Draw multiple points at once.
356    pub fn points(&mut self, pts: &[(usize, usize)]) {
357        for &(x, y) in pts {
358            self.dot(x, y);
359        }
360    }
361
362    /// Draw a polyline connecting the given points in order.
363    pub fn polyline(&mut self, pts: &[(usize, usize)]) {
364        for window in pts.windows(2) {
365            if let [(x0, y0), (x1, y1)] = window {
366                self.line(*x0, *y0, *x1, *y1);
367            }
368        }
369    }
370
371    /// Place a text label at pixel position `(x, y)`.
372    /// Text is rendered in regular characters overlaying the braille grid.
373    pub fn print(&mut self, x: usize, y: usize, text: &str) {
374        if text.is_empty() {
375            return;
376        }
377
378        let color = self.current_color;
379        if let Some(layer) = self.current_layer_mut() {
380            layer.labels.push(CanvasLabel {
381                x,
382                y,
383                text: text.to_string(),
384                color,
385            });
386        }
387    }
388
389    /// Start a new drawing layer. Shapes on later layers overlay earlier ones.
390    pub fn layer(&mut self) {
391        self.layers.push(Self::new_layer(self.cols, self.rows));
392    }
393
394    pub(crate) fn render(&self) -> Vec<Vec<(String, Color)>> {
395        let mut final_grid = vec![
396            vec![
397                CanvasPixel {
398                    bits: 0,
399                    color: Color::Reset,
400                };
401                self.cols
402            ];
403            self.rows
404        ];
405        let mut labels_overlay: Vec<Vec<Option<(char, Color)>>> =
406            vec![vec![None; self.cols]; self.rows];
407
408        for layer in &self.layers {
409            for (row, final_row) in final_grid.iter_mut().enumerate().take(self.rows) {
410                for (col, dst) in final_row.iter_mut().enumerate().take(self.cols) {
411                    let src = layer.grid[row][col];
412                    if src.bits == 0 {
413                        continue;
414                    }
415
416                    let merged = dst.bits | src.bits;
417                    if merged != dst.bits {
418                        dst.bits = merged;
419                        dst.color = src.color;
420                    }
421                }
422            }
423
424            for label in &layer.labels {
425                let row = label.y / 4;
426                if row >= self.rows {
427                    continue;
428                }
429                let start_col = label.x / 2;
430                for (offset, ch) in label.text.chars().enumerate() {
431                    let col = start_col + offset;
432                    if col >= self.cols {
433                        break;
434                    }
435                    labels_overlay[row][col] = Some((ch, label.color));
436                }
437            }
438        }
439
440        let mut lines: Vec<Vec<(String, Color)>> = Vec::with_capacity(self.rows);
441        for row in 0..self.rows {
442            let mut segments: Vec<(String, Color)> = Vec::new();
443            let mut current_color: Option<Color> = None;
444            let mut current_text = String::new();
445
446            for col in 0..self.cols {
447                let (ch, color) = if let Some((label_ch, label_color)) = labels_overlay[row][col] {
448                    (label_ch, label_color)
449                } else {
450                    let bits = final_grid[row][col].bits;
451                    let ch = char::from_u32(0x2800 + bits).unwrap_or(' ');
452                    (ch, final_grid[row][col].color)
453                };
454
455                match current_color {
456                    Some(c) if c == color => {
457                        current_text.push(ch);
458                    }
459                    Some(c) => {
460                        segments.push((std::mem::take(&mut current_text), c));
461                        current_text.push(ch);
462                        current_color = Some(color);
463                    }
464                    None => {
465                        current_text.push(ch);
466                        current_color = Some(color);
467                    }
468                }
469            }
470
471            if let Some(color) = current_color {
472                segments.push((current_text, color));
473            }
474            lines.push(segments);
475        }
476
477        lines
478    }
479}
480
481macro_rules! define_breakpoint_methods {
482    (
483        base = $base:ident,
484        arg = $arg:ident : $arg_ty:ty,
485        xs = $xs_fn:ident => [$( $xs_doc:literal ),* $(,)?],
486        sm = $sm_fn:ident => [$( $sm_doc:literal ),* $(,)?],
487        md = $md_fn:ident => [$( $md_doc:literal ),* $(,)?],
488        lg = $lg_fn:ident => [$( $lg_doc:literal ),* $(,)?],
489        xl = $xl_fn:ident => [$( $xl_doc:literal ),* $(,)?],
490        at = $at_fn:ident => [$( $at_doc:literal ),* $(,)?]
491    ) => {
492        $(#[doc = $xs_doc])*
493        pub fn $xs_fn(self, $arg: $arg_ty) -> Self {
494            if self.ctx.breakpoint() == Breakpoint::Xs {
495                self.$base($arg)
496            } else {
497                self
498            }
499        }
500
501        $(#[doc = $sm_doc])*
502        pub fn $sm_fn(self, $arg: $arg_ty) -> Self {
503            if self.ctx.breakpoint() == Breakpoint::Sm {
504                self.$base($arg)
505            } else {
506                self
507            }
508        }
509
510        $(#[doc = $md_doc])*
511        pub fn $md_fn(self, $arg: $arg_ty) -> Self {
512            if self.ctx.breakpoint() == Breakpoint::Md {
513                self.$base($arg)
514            } else {
515                self
516            }
517        }
518
519        $(#[doc = $lg_doc])*
520        pub fn $lg_fn(self, $arg: $arg_ty) -> Self {
521            if self.ctx.breakpoint() == Breakpoint::Lg {
522                self.$base($arg)
523            } else {
524                self
525            }
526        }
527
528        $(#[doc = $xl_doc])*
529        pub fn $xl_fn(self, $arg: $arg_ty) -> Self {
530            if self.ctx.breakpoint() == Breakpoint::Xl {
531                self.$base($arg)
532            } else {
533                self
534            }
535        }
536
537        $(#[doc = $at_doc])*
538        pub fn $at_fn(self, bp: Breakpoint, $arg: $arg_ty) -> Self {
539            if self.ctx.breakpoint() == bp {
540                self.$base($arg)
541            } else {
542                self
543            }
544        }
545    };
546}
547
548impl<'a> ContainerBuilder<'a> {
549    // ── border ───────────────────────────────────────────────────────
550
551    /// Apply a reusable [`ContainerStyle`] recipe. Only set fields override
552    /// the builder's current values. Chain multiple `.apply()` calls to compose.
553    pub fn apply(mut self, style: &ContainerStyle) -> Self {
554        if let Some(v) = style.border {
555            self.border = Some(v);
556        }
557        if let Some(v) = style.border_sides {
558            self.border_sides = v;
559        }
560        if let Some(v) = style.border_style {
561            self.border_style = v;
562        }
563        if let Some(v) = style.bg {
564            self.bg = Some(v);
565        }
566        if let Some(v) = style.dark_bg {
567            self.dark_bg = Some(v);
568        }
569        if let Some(v) = style.dark_border_style {
570            self.dark_border_style = Some(v);
571        }
572        if let Some(v) = style.padding {
573            self.padding = v;
574        }
575        if let Some(v) = style.margin {
576            self.margin = v;
577        }
578        if let Some(v) = style.gap {
579            self.gap = v;
580        }
581        if let Some(v) = style.row_gap {
582            self.row_gap = Some(v);
583        }
584        if let Some(v) = style.col_gap {
585            self.col_gap = Some(v);
586        }
587        if let Some(v) = style.grow {
588            self.grow = v;
589        }
590        if let Some(v) = style.align {
591            self.align = v;
592        }
593        if let Some(v) = style.align_self {
594            self.align_self_value = Some(v);
595        }
596        if let Some(v) = style.justify {
597            self.justify = v;
598        }
599        if let Some(v) = style.text_color {
600            self.text_color = Some(v);
601        }
602        if let Some(w) = style.w {
603            self.constraints.min_width = Some(w);
604            self.constraints.max_width = Some(w);
605        }
606        if let Some(h) = style.h {
607            self.constraints.min_height = Some(h);
608            self.constraints.max_height = Some(h);
609        }
610        if let Some(v) = style.min_w {
611            self.constraints.min_width = Some(v);
612        }
613        if let Some(v) = style.max_w {
614            self.constraints.max_width = Some(v);
615        }
616        if let Some(v) = style.min_h {
617            self.constraints.min_height = Some(v);
618        }
619        if let Some(v) = style.max_h {
620            self.constraints.max_height = Some(v);
621        }
622        if let Some(v) = style.w_pct {
623            self.constraints.width_pct = Some(v);
624        }
625        if let Some(v) = style.h_pct {
626            self.constraints.height_pct = Some(v);
627        }
628        self
629    }
630
631    /// Set the border style.
632    pub fn border(mut self, border: Border) -> Self {
633        self.border = Some(border);
634        self
635    }
636
637    /// Show or hide the top border.
638    pub fn border_top(mut self, show: bool) -> Self {
639        self.border_sides.top = show;
640        self
641    }
642
643    /// Show or hide the right border.
644    pub fn border_right(mut self, show: bool) -> Self {
645        self.border_sides.right = show;
646        self
647    }
648
649    /// Show or hide the bottom border.
650    pub fn border_bottom(mut self, show: bool) -> Self {
651        self.border_sides.bottom = show;
652        self
653    }
654
655    /// Show or hide the left border.
656    pub fn border_left(mut self, show: bool) -> Self {
657        self.border_sides.left = show;
658        self
659    }
660
661    /// Set which border sides are visible.
662    pub fn border_sides(mut self, sides: BorderSides) -> Self {
663        self.border_sides = sides;
664        self
665    }
666
667    /// Show only left and right borders. Shorthand for horizontal border sides.
668    pub fn border_x(self) -> Self {
669        self.border_sides(BorderSides {
670            top: false,
671            right: true,
672            bottom: false,
673            left: true,
674        })
675    }
676
677    /// Show only top and bottom borders. Shorthand for vertical border sides.
678    pub fn border_y(self) -> Self {
679        self.border_sides(BorderSides {
680            top: true,
681            right: false,
682            bottom: true,
683            left: false,
684        })
685    }
686
687    /// Set rounded border style. Shorthand for `.border(Border::Rounded)`.
688    pub fn rounded(self) -> Self {
689        self.border(Border::Rounded)
690    }
691
692    /// Set the style applied to the border characters.
693    pub fn border_style(mut self, style: Style) -> Self {
694        self.border_style = style;
695        self
696    }
697
698    /// Set the border foreground color.
699    pub fn border_fg(mut self, color: Color) -> Self {
700        self.border_style = self.border_style.fg(color);
701        self
702    }
703
704    /// Border style used when dark mode is active.
705    pub fn dark_border_style(mut self, style: Style) -> Self {
706        self.dark_border_style = Some(style);
707        self
708    }
709
710    /// Set the background color.
711    pub fn bg(mut self, color: Color) -> Self {
712        self.bg = Some(color);
713        self
714    }
715
716    /// Set the default text color for all child text elements in this container.
717    /// Individual `.fg()` calls on text elements will still override this.
718    pub fn text_color(mut self, color: Color) -> Self {
719        self.text_color = Some(color);
720        self
721    }
722
723    /// Background color used when dark mode is active.
724    pub fn dark_bg(mut self, color: Color) -> Self {
725        self.dark_bg = Some(color);
726        self
727    }
728
729    /// Background color applied when the parent group is hovered.
730    pub fn group_hover_bg(mut self, color: Color) -> Self {
731        self.group_hover_bg = Some(color);
732        self
733    }
734
735    /// Border style applied when the parent group is hovered.
736    pub fn group_hover_border_style(mut self, style: Style) -> Self {
737        self.group_hover_border_style = Some(style);
738        self
739    }
740
741    // ── padding (Tailwind: p, px, py, pt, pr, pb, pl) ───────────────
742
743    /// Set uniform padding on all sides. Alias for [`pad`](Self::pad).
744    pub fn p(self, value: u32) -> Self {
745        self.pad(value)
746    }
747
748    /// Set uniform padding on all sides.
749    pub fn pad(mut self, value: u32) -> Self {
750        self.padding = Padding::all(value);
751        self
752    }
753
754    /// Set horizontal padding (left and right).
755    pub fn px(mut self, value: u32) -> Self {
756        self.padding.left = value;
757        self.padding.right = value;
758        self
759    }
760
761    /// Set vertical padding (top and bottom).
762    pub fn py(mut self, value: u32) -> Self {
763        self.padding.top = value;
764        self.padding.bottom = value;
765        self
766    }
767
768    /// Set top padding.
769    pub fn pt(mut self, value: u32) -> Self {
770        self.padding.top = value;
771        self
772    }
773
774    /// Set right padding.
775    pub fn pr(mut self, value: u32) -> Self {
776        self.padding.right = value;
777        self
778    }
779
780    /// Set bottom padding.
781    pub fn pb(mut self, value: u32) -> Self {
782        self.padding.bottom = value;
783        self
784    }
785
786    /// Set left padding.
787    pub fn pl(mut self, value: u32) -> Self {
788        self.padding.left = value;
789        self
790    }
791
792    /// Set per-side padding using a [`Padding`] value.
793    pub fn padding(mut self, padding: Padding) -> Self {
794        self.padding = padding;
795        self
796    }
797
798    // ── margin (Tailwind: m, mx, my, mt, mr, mb, ml) ────────────────
799
800    /// Set uniform margin on all sides.
801    pub fn m(mut self, value: u32) -> Self {
802        self.margin = Margin::all(value);
803        self
804    }
805
806    /// Set horizontal margin (left and right).
807    pub fn mx(mut self, value: u32) -> Self {
808        self.margin.left = value;
809        self.margin.right = value;
810        self
811    }
812
813    /// Set vertical margin (top and bottom).
814    pub fn my(mut self, value: u32) -> Self {
815        self.margin.top = value;
816        self.margin.bottom = value;
817        self
818    }
819
820    /// Set top margin.
821    pub fn mt(mut self, value: u32) -> Self {
822        self.margin.top = value;
823        self
824    }
825
826    /// Set right margin.
827    pub fn mr(mut self, value: u32) -> Self {
828        self.margin.right = value;
829        self
830    }
831
832    /// Set bottom margin.
833    pub fn mb(mut self, value: u32) -> Self {
834        self.margin.bottom = value;
835        self
836    }
837
838    /// Set left margin.
839    pub fn ml(mut self, value: u32) -> Self {
840        self.margin.left = value;
841        self
842    }
843
844    /// Set per-side margin using a [`Margin`] value.
845    pub fn margin(mut self, margin: Margin) -> Self {
846        self.margin = margin;
847        self
848    }
849
850    // ── sizing (Tailwind: w, h, min-w, max-w, min-h, max-h) ────────
851
852    /// Set a fixed width (sets both min and max width).
853    pub fn w(mut self, value: u32) -> Self {
854        self.constraints.min_width = Some(value);
855        self.constraints.max_width = Some(value);
856        self
857    }
858
859    define_breakpoint_methods!(
860        base = w,
861        arg = value: u32,
862        xs = xs_w => [
863            "Width applied only at Xs breakpoint (< 40 cols).",
864            "",
865            "# Example",
866            "```ignore",
867            "ui.container().w(20).md_w(40).lg_w(60).col(|ui| { ... });",
868            "```"
869        ],
870        sm = sm_w => ["Width applied only at Sm breakpoint (40-79 cols)."],
871        md = md_w => ["Width applied only at Md breakpoint (80-119 cols)."],
872        lg = lg_w => ["Width applied only at Lg breakpoint (120-159 cols)."],
873        xl = xl_w => ["Width applied only at Xl breakpoint (>= 160 cols)."],
874        at = w_at => ["Width applied only at the given breakpoint."]
875    );
876
877    /// Set a fixed height (sets both min and max height).
878    pub fn h(mut self, value: u32) -> Self {
879        self.constraints.min_height = Some(value);
880        self.constraints.max_height = Some(value);
881        self
882    }
883
884    define_breakpoint_methods!(
885        base = h,
886        arg = value: u32,
887        xs = xs_h => ["Height applied only at Xs breakpoint (< 40 cols)."],
888        sm = sm_h => ["Height applied only at Sm breakpoint (40-79 cols)."],
889        md = md_h => ["Height applied only at Md breakpoint (80-119 cols)."],
890        lg = lg_h => ["Height applied only at Lg breakpoint (120-159 cols)."],
891        xl = xl_h => ["Height applied only at Xl breakpoint (>= 160 cols)."],
892        at = h_at => ["Height applied only at the given breakpoint."]
893    );
894
895    /// Set the minimum width constraint. Shorthand for [`min_width`](Self::min_width).
896    pub fn min_w(mut self, value: u32) -> Self {
897        self.constraints.min_width = Some(value);
898        self
899    }
900
901    define_breakpoint_methods!(
902        base = min_w,
903        arg = value: u32,
904        xs = xs_min_w => ["Minimum width applied only at Xs breakpoint (< 40 cols)."],
905        sm = sm_min_w => ["Minimum width applied only at Sm breakpoint (40-79 cols)."],
906        md = md_min_w => ["Minimum width applied only at Md breakpoint (80-119 cols)."],
907        lg = lg_min_w => ["Minimum width applied only at Lg breakpoint (120-159 cols)."],
908        xl = xl_min_w => ["Minimum width applied only at Xl breakpoint (>= 160 cols)."],
909        at = min_w_at => ["Minimum width applied only at the given breakpoint."]
910    );
911
912    /// Set the maximum width constraint. Shorthand for [`max_width`](Self::max_width).
913    pub fn max_w(mut self, value: u32) -> Self {
914        self.constraints.max_width = Some(value);
915        self
916    }
917
918    define_breakpoint_methods!(
919        base = max_w,
920        arg = value: u32,
921        xs = xs_max_w => ["Maximum width applied only at Xs breakpoint (< 40 cols)."],
922        sm = sm_max_w => ["Maximum width applied only at Sm breakpoint (40-79 cols)."],
923        md = md_max_w => ["Maximum width applied only at Md breakpoint (80-119 cols)."],
924        lg = lg_max_w => ["Maximum width applied only at Lg breakpoint (120-159 cols)."],
925        xl = xl_max_w => ["Maximum width applied only at Xl breakpoint (>= 160 cols)."],
926        at = max_w_at => ["Maximum width applied only at the given breakpoint."]
927    );
928
929    /// Set the minimum height constraint. Shorthand for [`min_height`](Self::min_height).
930    pub fn min_h(mut self, value: u32) -> Self {
931        self.constraints.min_height = Some(value);
932        self
933    }
934
935    /// Set the maximum height constraint. Shorthand for [`max_height`](Self::max_height).
936    pub fn max_h(mut self, value: u32) -> Self {
937        self.constraints.max_height = Some(value);
938        self
939    }
940
941    /// Set the minimum width constraint in cells.
942    pub fn min_width(mut self, value: u32) -> Self {
943        self.constraints.min_width = Some(value);
944        self
945    }
946
947    /// Set the maximum width constraint in cells.
948    pub fn max_width(mut self, value: u32) -> Self {
949        self.constraints.max_width = Some(value);
950        self
951    }
952
953    /// Set the minimum height constraint in rows.
954    pub fn min_height(mut self, value: u32) -> Self {
955        self.constraints.min_height = Some(value);
956        self
957    }
958
959    /// Set the maximum height constraint in rows.
960    pub fn max_height(mut self, value: u32) -> Self {
961        self.constraints.max_height = Some(value);
962        self
963    }
964
965    /// Set width as a percentage (1-100) of the parent container.
966    pub fn w_pct(mut self, pct: u8) -> Self {
967        self.constraints.width_pct = Some(pct.min(100));
968        self
969    }
970
971    /// Set height as a percentage (1-100) of the parent container.
972    pub fn h_pct(mut self, pct: u8) -> Self {
973        self.constraints.height_pct = Some(pct.min(100));
974        self
975    }
976
977    /// Set all size constraints at once using a [`Constraints`] value.
978    pub fn constraints(mut self, constraints: Constraints) -> Self {
979        self.constraints = constraints;
980        self
981    }
982
983    // ── flex ─────────────────────────────────────────────────────────
984
985    /// Set the gap (in cells) between child elements.
986    pub fn gap(mut self, gap: u32) -> Self {
987        self.gap = gap;
988        self
989    }
990
991    /// Set the gap between children for column layouts (vertical spacing).
992    /// Overrides `.gap()` when finalized with `.col()`.
993    pub fn row_gap(mut self, value: u32) -> Self {
994        self.row_gap = Some(value);
995        self
996    }
997
998    /// Set the gap between children for row layouts (horizontal spacing).
999    /// Overrides `.gap()` when finalized with `.row()`.
1000    pub fn col_gap(mut self, value: u32) -> Self {
1001        self.col_gap = Some(value);
1002        self
1003    }
1004
1005    define_breakpoint_methods!(
1006        base = gap,
1007        arg = value: u32,
1008        xs = xs_gap => ["Gap applied only at Xs breakpoint (< 40 cols)."],
1009        sm = sm_gap => ["Gap applied only at Sm breakpoint (40-79 cols)."],
1010        md = md_gap => [
1011            "Gap applied only at Md breakpoint (80-119 cols).",
1012            "",
1013            "# Example",
1014            "```ignore",
1015            "ui.container().gap(0).md_gap(2).col(|ui| { ... });",
1016            "```"
1017        ],
1018        lg = lg_gap => ["Gap applied only at Lg breakpoint (120-159 cols)."],
1019        xl = xl_gap => ["Gap applied only at Xl breakpoint (>= 160 cols)."],
1020        at = gap_at => ["Gap applied only at the given breakpoint."]
1021    );
1022
1023    /// Set the flex-grow factor. `1` means the container expands to fill available space.
1024    pub fn grow(mut self, grow: u16) -> Self {
1025        self.grow = grow;
1026        self
1027    }
1028
1029    define_breakpoint_methods!(
1030        base = grow,
1031        arg = value: u16,
1032        xs = xs_grow => ["Grow factor applied only at Xs breakpoint (< 40 cols)."],
1033        sm = sm_grow => ["Grow factor applied only at Sm breakpoint (40-79 cols)."],
1034        md = md_grow => ["Grow factor applied only at Md breakpoint (80-119 cols)."],
1035        lg = lg_grow => ["Grow factor applied only at Lg breakpoint (120-159 cols)."],
1036        xl = xl_grow => ["Grow factor applied only at Xl breakpoint (>= 160 cols)."],
1037        at = grow_at => ["Grow factor applied only at the given breakpoint."]
1038    );
1039
1040    define_breakpoint_methods!(
1041        base = p,
1042        arg = value: u32,
1043        xs = xs_p => ["Uniform padding applied only at Xs breakpoint (< 40 cols)."],
1044        sm = sm_p => ["Uniform padding applied only at Sm breakpoint (40-79 cols)."],
1045        md = md_p => ["Uniform padding applied only at Md breakpoint (80-119 cols)."],
1046        lg = lg_p => ["Uniform padding applied only at Lg breakpoint (120-159 cols)."],
1047        xl = xl_p => ["Uniform padding applied only at Xl breakpoint (>= 160 cols)."],
1048        at = p_at => ["Padding applied only at the given breakpoint."]
1049    );
1050
1051    // ── alignment ───────────────────────────────────────────────────
1052
1053    /// Set the cross-axis alignment of child elements.
1054    pub fn align(mut self, align: Align) -> Self {
1055        self.align = align;
1056        self
1057    }
1058
1059    /// Center children on the cross axis. Shorthand for `.align(Align::Center)`.
1060    pub fn center(self) -> Self {
1061        self.align(Align::Center)
1062    }
1063
1064    /// Set the main-axis content distribution mode.
1065    pub fn justify(mut self, justify: Justify) -> Self {
1066        self.justify = justify;
1067        self
1068    }
1069
1070    /// Distribute children with equal space between; first at start, last at end.
1071    pub fn space_between(self) -> Self {
1072        self.justify(Justify::SpaceBetween)
1073    }
1074
1075    /// Distribute children with equal space around each child.
1076    pub fn space_around(self) -> Self {
1077        self.justify(Justify::SpaceAround)
1078    }
1079
1080    /// Distribute children with equal space between all children and edges.
1081    pub fn space_evenly(self) -> Self {
1082        self.justify(Justify::SpaceEvenly)
1083    }
1084
1085    /// Center children on both axes. Shorthand for `.justify(Justify::Center).align(Align::Center)`.
1086    pub fn flex_center(self) -> Self {
1087        self.justify(Justify::Center).align(Align::Center)
1088    }
1089
1090    /// Override the parent's cross-axis alignment for this container only.
1091    /// Like CSS `align-self`.
1092    pub fn align_self(mut self, align: Align) -> Self {
1093        self.align_self_value = Some(align);
1094        self
1095    }
1096
1097    // ── title ────────────────────────────────────────────────────────
1098
1099    /// Set a plain-text title rendered in the top border.
1100    pub fn title(self, title: impl Into<String>) -> Self {
1101        self.title_styled(title, Style::new())
1102    }
1103
1104    /// Set a styled title rendered in the top border.
1105    pub fn title_styled(mut self, title: impl Into<String>, style: Style) -> Self {
1106        self.title = Some((title.into(), style));
1107        self
1108    }
1109
1110    // ── internal ─────────────────────────────────────────────────────
1111
1112    /// Set the vertical scroll offset in rows. Used internally by [`Context::scrollable`].
1113    pub fn scroll_offset(mut self, offset: u32) -> Self {
1114        self.scroll_offset = Some(offset);
1115        self
1116    }
1117
1118    pub(crate) fn group_name(mut self, name: String) -> Self {
1119        self.group_name = Some(name);
1120        self
1121    }
1122
1123    /// Finalize the builder as a vertical (column) container.
1124    ///
1125    /// The closure receives a `&mut Context` for rendering children.
1126    /// Returns a [`Response`] with click/hover state for this container.
1127    pub fn col(self, f: impl FnOnce(&mut Context)) -> Response {
1128        self.finish(Direction::Column, f)
1129    }
1130
1131    /// Finalize the builder as a horizontal (row) container.
1132    ///
1133    /// The closure receives a `&mut Context` for rendering children.
1134    /// Returns a [`Response`] with click/hover state for this container.
1135    pub fn row(self, f: impl FnOnce(&mut Context)) -> Response {
1136        self.finish(Direction::Row, f)
1137    }
1138
1139    /// Finalize the builder as an inline text line.
1140    ///
1141    /// Like [`row`](ContainerBuilder::row) but gap is forced to zero
1142    /// for seamless inline rendering of mixed-style text.
1143    pub fn line(mut self, f: impl FnOnce(&mut Context)) -> Response {
1144        self.gap = 0;
1145        self.finish(Direction::Row, f)
1146    }
1147
1148    /// Finalize the builder as a raw-draw region with direct buffer access.
1149    ///
1150    /// The closure receives `(&mut Buffer, Rect)` after layout is computed.
1151    /// Use `buf.set_char()`, `buf.set_string()`, `buf.get_mut()` to write
1152    /// directly into the terminal buffer. Writes outside `rect` are clipped.
1153    ///
1154    /// The closure must be `'static` because it is deferred until after layout.
1155    /// To capture local data, clone or move it into the closure:
1156    /// ```ignore
1157    /// let data = my_vec.clone();
1158    /// ui.container().w(40).h(20).draw(move |buf, rect| {
1159    ///     // use `data` here
1160    /// });
1161    /// ```
1162    pub fn draw(self, f: impl FnOnce(&mut crate::buffer::Buffer, Rect) + 'static) {
1163        let draw_id = self.ctx.deferred_draws.len();
1164        self.ctx.deferred_draws.push(Some(Box::new(f)));
1165        self.ctx.skip_interaction_slot();
1166        self.ctx.commands.push(Command::RawDraw {
1167            draw_id,
1168            constraints: self.constraints,
1169            grow: self.grow,
1170            margin: self.margin,
1171        });
1172    }
1173
1174    fn finish(mut self, direction: Direction, f: impl FnOnce(&mut Context)) -> Response {
1175        let interaction_id = self.ctx.next_interaction_id();
1176        let resolved_gap = match direction {
1177            Direction::Column => self.row_gap.unwrap_or(self.gap),
1178            Direction::Row => self.col_gap.unwrap_or(self.gap),
1179        };
1180
1181        let in_hovered_group = self
1182            .group_name
1183            .as_ref()
1184            .map(|name| self.ctx.is_group_hovered(name))
1185            .unwrap_or(false)
1186            || self
1187                .ctx
1188                .rollback
1189                .group_stack
1190                .last()
1191                .map(|name| self.ctx.is_group_hovered(name))
1192                .unwrap_or(false);
1193        let in_focused_group = self
1194            .group_name
1195            .as_ref()
1196            .map(|name| self.ctx.is_group_focused(name))
1197            .unwrap_or(false)
1198            || self
1199                .ctx
1200                .rollback
1201                .group_stack
1202                .last()
1203                .map(|name| self.ctx.is_group_focused(name))
1204                .unwrap_or(false);
1205
1206        let resolved_bg = if self.ctx.rollback.dark_mode {
1207            self.dark_bg.or(self.bg)
1208        } else {
1209            self.bg
1210        };
1211        let resolved_border_style = if self.ctx.rollback.dark_mode {
1212            self.dark_border_style.unwrap_or(self.border_style)
1213        } else {
1214            self.border_style
1215        };
1216        let bg_color = if in_hovered_group || in_focused_group {
1217            self.group_hover_bg.or(resolved_bg)
1218        } else {
1219            resolved_bg
1220        };
1221        let border_style = if in_hovered_group || in_focused_group {
1222            self.group_hover_border_style
1223                .unwrap_or(resolved_border_style)
1224        } else {
1225            resolved_border_style
1226        };
1227        let group_name = self.group_name.take();
1228        let is_group_container = group_name.is_some();
1229
1230        if let Some(scroll_offset) = self.scroll_offset {
1231            self.ctx.commands.push(Command::BeginScrollable {
1232                grow: self.grow,
1233                border: self.border,
1234                border_sides: self.border_sides,
1235                border_style,
1236                padding: self.padding,
1237                margin: self.margin,
1238                constraints: self.constraints,
1239                title: self.title,
1240                scroll_offset,
1241            });
1242        } else {
1243            self.ctx.commands.push(Command::BeginContainer {
1244                direction,
1245                gap: resolved_gap,
1246                align: self.align,
1247                align_self: self.align_self_value,
1248                justify: self.justify,
1249                border: self.border,
1250                border_sides: self.border_sides,
1251                border_style,
1252                bg_color,
1253                padding: self.padding,
1254                margin: self.margin,
1255                constraints: self.constraints,
1256                title: self.title,
1257                grow: self.grow,
1258                group_name,
1259            });
1260        }
1261        self.ctx.rollback.text_color_stack.push(self.text_color);
1262        f(self.ctx);
1263        self.ctx.rollback.text_color_stack.pop();
1264        self.ctx.commands.push(Command::EndContainer);
1265        self.ctx.rollback.last_text_idx = None;
1266
1267        if is_group_container {
1268            self.ctx.rollback.group_stack.pop();
1269            self.ctx.rollback.group_count = self.ctx.rollback.group_count.saturating_sub(1);
1270        }
1271
1272        self.ctx.response_for(interaction_id)
1273    }
1274}