Skip to main content

slt/context/
container.rs

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