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    ///
554    /// If the style has an [`ContainerStyle::extends`] base, the base is applied
555    /// first, then the style's own fields override.
556    ///
557    /// [`ThemeColor`] fields (`theme_bg`, `theme_text_color`, `theme_border_fg`)
558    /// are resolved against the active theme at apply time.
559    pub fn apply(mut self, style: &ContainerStyle) -> Self {
560        // Apply base style first if this style extends another
561        if let Some(base) = style.extends {
562            self = self.apply(base);
563        }
564        if let Some(v) = style.border {
565            self.border = Some(v);
566        }
567        if let Some(v) = style.border_sides {
568            self.border_sides = v;
569        }
570        if let Some(v) = style.border_style {
571            self.border_style = v;
572        }
573        if let Some(v) = style.bg {
574            self.bg = Some(v);
575        }
576        if let Some(v) = style.dark_bg {
577            self.dark_bg = Some(v);
578        }
579        if let Some(v) = style.dark_border_style {
580            self.dark_border_style = Some(v);
581        }
582        if let Some(v) = style.padding {
583            self.padding = v;
584        }
585        if let Some(v) = style.margin {
586            self.margin = v;
587        }
588        if let Some(v) = style.gap {
589            self.gap = v;
590        }
591        if let Some(v) = style.row_gap {
592            self.row_gap = Some(v);
593        }
594        if let Some(v) = style.col_gap {
595            self.col_gap = Some(v);
596        }
597        if let Some(v) = style.grow {
598            self.grow = v;
599        }
600        if let Some(v) = style.align {
601            self.align = v;
602        }
603        if let Some(v) = style.align_self {
604            self.align_self_value = Some(v);
605        }
606        if let Some(v) = style.justify {
607            self.justify = v;
608        }
609        if let Some(v) = style.text_color {
610            self.text_color = Some(v);
611        }
612        if let Some(w) = style.w {
613            self.constraints.min_width = Some(w);
614            self.constraints.max_width = Some(w);
615        }
616        if let Some(h) = style.h {
617            self.constraints.min_height = Some(h);
618            self.constraints.max_height = Some(h);
619        }
620        if let Some(v) = style.min_w {
621            self.constraints.min_width = Some(v);
622        }
623        if let Some(v) = style.max_w {
624            self.constraints.max_width = Some(v);
625        }
626        if let Some(v) = style.min_h {
627            self.constraints.min_height = Some(v);
628        }
629        if let Some(v) = style.max_h {
630            self.constraints.max_height = Some(v);
631        }
632        if let Some(v) = style.w_pct {
633            self.constraints.width_pct = Some(v);
634        }
635        if let Some(v) = style.h_pct {
636            self.constraints.height_pct = Some(v);
637        }
638        // Resolve ThemeColor fields against the active theme (overrides literal colors)
639        if let Some(tc) = style.theme_bg {
640            self.bg = Some(self.ctx.theme.resolve(tc));
641        }
642        if let Some(tc) = style.theme_text_color {
643            self.text_color = Some(self.ctx.theme.resolve(tc));
644        }
645        if let Some(tc) = style.theme_border_fg {
646            let color = self.ctx.theme.resolve(tc);
647            self.border_style = Style::new().fg(color);
648        }
649        self
650    }
651
652    /// Set the border style.
653    pub fn border(mut self, border: Border) -> Self {
654        self.border = Some(border);
655        self
656    }
657
658    /// Show or hide the top border.
659    pub fn border_top(mut self, show: bool) -> Self {
660        self.border_sides.top = show;
661        self
662    }
663
664    /// Show or hide the right border.
665    pub fn border_right(mut self, show: bool) -> Self {
666        self.border_sides.right = show;
667        self
668    }
669
670    /// Show or hide the bottom border.
671    pub fn border_bottom(mut self, show: bool) -> Self {
672        self.border_sides.bottom = show;
673        self
674    }
675
676    /// Show or hide the left border.
677    pub fn border_left(mut self, show: bool) -> Self {
678        self.border_sides.left = show;
679        self
680    }
681
682    /// Set which border sides are visible.
683    pub fn border_sides(mut self, sides: BorderSides) -> Self {
684        self.border_sides = sides;
685        self
686    }
687
688    /// Show only left and right borders. Shorthand for horizontal border sides.
689    pub fn border_x(self) -> Self {
690        self.border_sides(BorderSides {
691            top: false,
692            right: true,
693            bottom: false,
694            left: true,
695        })
696    }
697
698    /// Show only top and bottom borders. Shorthand for vertical border sides.
699    pub fn border_y(self) -> Self {
700        self.border_sides(BorderSides {
701            top: true,
702            right: false,
703            bottom: true,
704            left: false,
705        })
706    }
707
708    /// Set rounded border style. Shorthand for `.border(Border::Rounded)`.
709    pub fn rounded(self) -> Self {
710        self.border(Border::Rounded)
711    }
712
713    /// Set the style applied to the border characters.
714    pub fn border_style(mut self, style: Style) -> Self {
715        self.border_style = style;
716        self
717    }
718
719    /// Set the border foreground color.
720    pub fn border_fg(mut self, color: Color) -> Self {
721        self.border_style = self.border_style.fg(color);
722        self
723    }
724
725    /// Border style used when dark mode is active.
726    pub fn dark_border_style(mut self, style: Style) -> Self {
727        self.dark_border_style = Some(style);
728        self
729    }
730
731    /// Set the background color.
732    pub fn bg(mut self, color: Color) -> Self {
733        self.bg = Some(color);
734        self
735    }
736
737    /// Set the default text color for all child text elements in this container.
738    /// Individual `.fg()` calls on text elements will still override this.
739    pub fn text_color(mut self, color: Color) -> Self {
740        self.text_color = Some(color);
741        self
742    }
743
744    /// Background color used when dark mode is active.
745    pub fn dark_bg(mut self, color: Color) -> Self {
746        self.dark_bg = Some(color);
747        self
748    }
749
750    /// Background color applied when the parent group is hovered.
751    pub fn group_hover_bg(mut self, color: Color) -> Self {
752        self.group_hover_bg = Some(color);
753        self
754    }
755
756    /// Border style applied when the parent group is hovered.
757    pub fn group_hover_border_style(mut self, style: Style) -> Self {
758        self.group_hover_border_style = Some(style);
759        self
760    }
761
762    // ── padding (Tailwind: p, px, py, pt, pr, pb, pl) ───────────────
763
764    /// Set uniform padding on all sides. Alias for [`pad`](Self::pad).
765    pub fn p(self, value: u32) -> Self {
766        self.pad(value)
767    }
768
769    /// Set uniform padding on all sides.
770    pub fn pad(mut self, value: u32) -> Self {
771        self.padding = Padding::all(value);
772        self
773    }
774
775    /// Set horizontal padding (left and right).
776    pub fn px(mut self, value: u32) -> Self {
777        self.padding.left = value;
778        self.padding.right = value;
779        self
780    }
781
782    /// Set vertical padding (top and bottom).
783    pub fn py(mut self, value: u32) -> Self {
784        self.padding.top = value;
785        self.padding.bottom = value;
786        self
787    }
788
789    /// Set top padding.
790    pub fn pt(mut self, value: u32) -> Self {
791        self.padding.top = value;
792        self
793    }
794
795    /// Set right padding.
796    pub fn pr(mut self, value: u32) -> Self {
797        self.padding.right = value;
798        self
799    }
800
801    /// Set bottom padding.
802    pub fn pb(mut self, value: u32) -> Self {
803        self.padding.bottom = value;
804        self
805    }
806
807    /// Set left padding.
808    pub fn pl(mut self, value: u32) -> Self {
809        self.padding.left = value;
810        self
811    }
812
813    /// Set per-side padding using a [`Padding`] value.
814    pub fn padding(mut self, padding: Padding) -> Self {
815        self.padding = padding;
816        self
817    }
818
819    // ── margin (Tailwind: m, mx, my, mt, mr, mb, ml) ────────────────
820
821    /// Set uniform margin on all sides.
822    pub fn m(mut self, value: u32) -> Self {
823        self.margin = Margin::all(value);
824        self
825    }
826
827    /// Set horizontal margin (left and right).
828    pub fn mx(mut self, value: u32) -> Self {
829        self.margin.left = value;
830        self.margin.right = value;
831        self
832    }
833
834    /// Set vertical margin (top and bottom).
835    pub fn my(mut self, value: u32) -> Self {
836        self.margin.top = value;
837        self.margin.bottom = value;
838        self
839    }
840
841    /// Set top margin.
842    pub fn mt(mut self, value: u32) -> Self {
843        self.margin.top = value;
844        self
845    }
846
847    /// Set right margin.
848    pub fn mr(mut self, value: u32) -> Self {
849        self.margin.right = value;
850        self
851    }
852
853    /// Set bottom margin.
854    pub fn mb(mut self, value: u32) -> Self {
855        self.margin.bottom = value;
856        self
857    }
858
859    /// Set left margin.
860    pub fn ml(mut self, value: u32) -> Self {
861        self.margin.left = value;
862        self
863    }
864
865    /// Set per-side margin using a [`Margin`] value.
866    pub fn margin(mut self, margin: Margin) -> Self {
867        self.margin = margin;
868        self
869    }
870
871    // ── sizing (Tailwind: w, h, min-w, max-w, min-h, max-h) ────────
872
873    /// Set a fixed width (sets both min and max width).
874    pub fn w(mut self, value: u32) -> Self {
875        self.constraints.min_width = Some(value);
876        self.constraints.max_width = Some(value);
877        self
878    }
879
880    define_breakpoint_methods!(
881        base = w,
882        arg = value: u32,
883        xs = xs_w => [
884            "Width applied only at Xs breakpoint (< 40 cols).",
885            "",
886            "# Example",
887            "```ignore",
888            "ui.container().w(20).md_w(40).lg_w(60).col(|ui| { ... });",
889            "```"
890        ],
891        sm = sm_w => ["Width applied only at Sm breakpoint (40-79 cols)."],
892        md = md_w => ["Width applied only at Md breakpoint (80-119 cols)."],
893        lg = lg_w => ["Width applied only at Lg breakpoint (120-159 cols)."],
894        xl = xl_w => ["Width applied only at Xl breakpoint (>= 160 cols)."],
895        at = w_at => ["Width applied only at the given breakpoint."]
896    );
897
898    /// Set a fixed height (sets both min and max height).
899    pub fn h(mut self, value: u32) -> Self {
900        self.constraints.min_height = Some(value);
901        self.constraints.max_height = Some(value);
902        self
903    }
904
905    define_breakpoint_methods!(
906        base = h,
907        arg = value: u32,
908        xs = xs_h => ["Height applied only at Xs breakpoint (< 40 cols)."],
909        sm = sm_h => ["Height applied only at Sm breakpoint (40-79 cols)."],
910        md = md_h => ["Height applied only at Md breakpoint (80-119 cols)."],
911        lg = lg_h => ["Height applied only at Lg breakpoint (120-159 cols)."],
912        xl = xl_h => ["Height applied only at Xl breakpoint (>= 160 cols)."],
913        at = h_at => ["Height applied only at the given breakpoint."]
914    );
915
916    /// Set the minimum width constraint. Shorthand for [`min_width`](Self::min_width).
917    pub fn min_w(mut self, value: u32) -> Self {
918        self.constraints.min_width = Some(value);
919        self
920    }
921
922    define_breakpoint_methods!(
923        base = min_w,
924        arg = value: u32,
925        xs = xs_min_w => ["Minimum width applied only at Xs breakpoint (< 40 cols)."],
926        sm = sm_min_w => ["Minimum width applied only at Sm breakpoint (40-79 cols)."],
927        md = md_min_w => ["Minimum width applied only at Md breakpoint (80-119 cols)."],
928        lg = lg_min_w => ["Minimum width applied only at Lg breakpoint (120-159 cols)."],
929        xl = xl_min_w => ["Minimum width applied only at Xl breakpoint (>= 160 cols)."],
930        at = min_w_at => ["Minimum width applied only at the given breakpoint."]
931    );
932
933    /// Set the maximum width constraint. Shorthand for [`max_width`](Self::max_width).
934    pub fn max_w(mut self, value: u32) -> Self {
935        self.constraints.max_width = Some(value);
936        self
937    }
938
939    define_breakpoint_methods!(
940        base = max_w,
941        arg = value: u32,
942        xs = xs_max_w => ["Maximum width applied only at Xs breakpoint (< 40 cols)."],
943        sm = sm_max_w => ["Maximum width applied only at Sm breakpoint (40-79 cols)."],
944        md = md_max_w => ["Maximum width applied only at Md breakpoint (80-119 cols)."],
945        lg = lg_max_w => ["Maximum width applied only at Lg breakpoint (120-159 cols)."],
946        xl = xl_max_w => ["Maximum width applied only at Xl breakpoint (>= 160 cols)."],
947        at = max_w_at => ["Maximum width applied only at the given breakpoint."]
948    );
949
950    /// Set the minimum height constraint. Shorthand for [`min_height`](Self::min_height).
951    pub fn min_h(mut self, value: u32) -> Self {
952        self.constraints.min_height = Some(value);
953        self
954    }
955
956    /// Set the maximum height constraint. Shorthand for [`max_height`](Self::max_height).
957    pub fn max_h(mut self, value: u32) -> Self {
958        self.constraints.max_height = Some(value);
959        self
960    }
961
962    /// Set the minimum width constraint in cells.
963    pub fn min_width(mut self, value: u32) -> Self {
964        self.constraints.min_width = Some(value);
965        self
966    }
967
968    /// Set the maximum width constraint in cells.
969    pub fn max_width(mut self, value: u32) -> Self {
970        self.constraints.max_width = Some(value);
971        self
972    }
973
974    /// Set the minimum height constraint in rows.
975    pub fn min_height(mut self, value: u32) -> Self {
976        self.constraints.min_height = Some(value);
977        self
978    }
979
980    /// Set the maximum height constraint in rows.
981    pub fn max_height(mut self, value: u32) -> Self {
982        self.constraints.max_height = Some(value);
983        self
984    }
985
986    /// Set width as a percentage (1-100) of the parent container.
987    pub fn w_pct(mut self, pct: u8) -> Self {
988        self.constraints.width_pct = Some(pct.min(100));
989        self
990    }
991
992    /// Set height as a percentage (1-100) of the parent container.
993    pub fn h_pct(mut self, pct: u8) -> Self {
994        self.constraints.height_pct = Some(pct.min(100));
995        self
996    }
997
998    /// Set all size constraints at once using a [`Constraints`] value.
999    pub fn constraints(mut self, constraints: Constraints) -> Self {
1000        self.constraints = constraints;
1001        self
1002    }
1003
1004    // ── flex ─────────────────────────────────────────────────────────
1005
1006    /// Set the gap (in cells) between child elements.
1007    pub fn gap(mut self, gap: u32) -> Self {
1008        self.gap = gap;
1009        self
1010    }
1011
1012    /// Set the gap between children for column layouts (vertical spacing).
1013    /// Overrides `.gap()` when finalized with `.col()`.
1014    pub fn row_gap(mut self, value: u32) -> Self {
1015        self.row_gap = Some(value);
1016        self
1017    }
1018
1019    /// Set the gap between children for row layouts (horizontal spacing).
1020    /// Overrides `.gap()` when finalized with `.row()`.
1021    pub fn col_gap(mut self, value: u32) -> Self {
1022        self.col_gap = Some(value);
1023        self
1024    }
1025
1026    define_breakpoint_methods!(
1027        base = gap,
1028        arg = value: u32,
1029        xs = xs_gap => ["Gap applied only at Xs breakpoint (< 40 cols)."],
1030        sm = sm_gap => ["Gap applied only at Sm breakpoint (40-79 cols)."],
1031        md = md_gap => [
1032            "Gap applied only at Md breakpoint (80-119 cols).",
1033            "",
1034            "# Example",
1035            "```ignore",
1036            "ui.container().gap(0).md_gap(2).col(|ui| { ... });",
1037            "```"
1038        ],
1039        lg = lg_gap => ["Gap applied only at Lg breakpoint (120-159 cols)."],
1040        xl = xl_gap => ["Gap applied only at Xl breakpoint (>= 160 cols)."],
1041        at = gap_at => ["Gap applied only at the given breakpoint."]
1042    );
1043
1044    /// Set the flex-grow factor. `1` means the container expands to fill available space.
1045    pub fn grow(mut self, grow: u16) -> Self {
1046        self.grow = grow;
1047        self
1048    }
1049
1050    define_breakpoint_methods!(
1051        base = grow,
1052        arg = value: u16,
1053        xs = xs_grow => ["Grow factor applied only at Xs breakpoint (< 40 cols)."],
1054        sm = sm_grow => ["Grow factor applied only at Sm breakpoint (40-79 cols)."],
1055        md = md_grow => ["Grow factor applied only at Md breakpoint (80-119 cols)."],
1056        lg = lg_grow => ["Grow factor applied only at Lg breakpoint (120-159 cols)."],
1057        xl = xl_grow => ["Grow factor applied only at Xl breakpoint (>= 160 cols)."],
1058        at = grow_at => ["Grow factor applied only at the given breakpoint."]
1059    );
1060
1061    define_breakpoint_methods!(
1062        base = p,
1063        arg = value: u32,
1064        xs = xs_p => ["Uniform padding applied only at Xs breakpoint (< 40 cols)."],
1065        sm = sm_p => ["Uniform padding applied only at Sm breakpoint (40-79 cols)."],
1066        md = md_p => ["Uniform padding applied only at Md breakpoint (80-119 cols)."],
1067        lg = lg_p => ["Uniform padding applied only at Lg breakpoint (120-159 cols)."],
1068        xl = xl_p => ["Uniform padding applied only at Xl breakpoint (>= 160 cols)."],
1069        at = p_at => ["Padding applied only at the given breakpoint."]
1070    );
1071
1072    // ── alignment ───────────────────────────────────────────────────
1073
1074    /// Set the cross-axis alignment of child elements.
1075    pub fn align(mut self, align: Align) -> Self {
1076        self.align = align;
1077        self
1078    }
1079
1080    /// Center children on the cross axis. Shorthand for `.align(Align::Center)`.
1081    pub fn center(self) -> Self {
1082        self.align(Align::Center)
1083    }
1084
1085    /// Set the main-axis content distribution mode.
1086    pub fn justify(mut self, justify: Justify) -> Self {
1087        self.justify = justify;
1088        self
1089    }
1090
1091    /// Distribute children with equal space between; first at start, last at end.
1092    pub fn space_between(self) -> Self {
1093        self.justify(Justify::SpaceBetween)
1094    }
1095
1096    /// Distribute children with equal space around each child.
1097    pub fn space_around(self) -> Self {
1098        self.justify(Justify::SpaceAround)
1099    }
1100
1101    /// Distribute children with equal space between all children and edges.
1102    pub fn space_evenly(self) -> Self {
1103        self.justify(Justify::SpaceEvenly)
1104    }
1105
1106    /// Center children on both axes. Shorthand for `.justify(Justify::Center).align(Align::Center)`.
1107    pub fn flex_center(self) -> Self {
1108        self.justify(Justify::Center).align(Align::Center)
1109    }
1110
1111    /// Override the parent's cross-axis alignment for this container only.
1112    /// Like CSS `align-self`.
1113    pub fn align_self(mut self, align: Align) -> Self {
1114        self.align_self_value = Some(align);
1115        self
1116    }
1117
1118    // ── title ────────────────────────────────────────────────────────
1119
1120    /// Set a plain-text title rendered in the top border.
1121    pub fn title(self, title: impl Into<String>) -> Self {
1122        self.title_styled(title, Style::new())
1123    }
1124
1125    /// Set a styled title rendered in the top border.
1126    pub fn title_styled(mut self, title: impl Into<String>, style: Style) -> Self {
1127        self.title = Some((title.into(), style));
1128        self
1129    }
1130
1131    // ── internal ─────────────────────────────────────────────────────
1132
1133    /// Set the vertical scroll offset in rows. Used internally by [`Context::scrollable`].
1134    pub fn scroll_offset(mut self, offset: u32) -> Self {
1135        self.scroll_offset = Some(offset);
1136        self
1137    }
1138
1139    pub(crate) fn group_name(mut self, name: String) -> Self {
1140        self.group_name = Some(name);
1141        self
1142    }
1143
1144    /// Finalize the builder as a vertical (column) container.
1145    ///
1146    /// The closure receives a `&mut Context` for rendering children.
1147    /// Returns a [`Response`] with click/hover state for this container.
1148    pub fn col(self, f: impl FnOnce(&mut Context)) -> Response {
1149        self.finish(Direction::Column, f)
1150    }
1151
1152    /// Finalize the builder as a horizontal (row) container.
1153    ///
1154    /// The closure receives a `&mut Context` for rendering children.
1155    /// Returns a [`Response`] with click/hover state for this container.
1156    pub fn row(self, f: impl FnOnce(&mut Context)) -> Response {
1157        self.finish(Direction::Row, f)
1158    }
1159
1160    /// Finalize the builder as an inline text line.
1161    ///
1162    /// Like [`row`](ContainerBuilder::row) but gap is forced to zero
1163    /// for seamless inline rendering of mixed-style text.
1164    pub fn line(mut self, f: impl FnOnce(&mut Context)) -> Response {
1165        self.gap = 0;
1166        self.finish(Direction::Row, f)
1167    }
1168
1169    /// Finalize the builder as a raw-draw region with direct buffer access.
1170    ///
1171    /// The closure receives `(&mut Buffer, Rect)` after layout is computed.
1172    /// Use `buf.set_char()`, `buf.set_string()`, `buf.get_mut()` to write
1173    /// directly into the terminal buffer. Writes outside `rect` are clipped.
1174    ///
1175    /// The closure must be `'static` because it is deferred until after layout.
1176    /// To capture local data, clone or move it into the closure:
1177    /// ```ignore
1178    /// let data = my_vec.clone();
1179    /// ui.container().w(40).h(20).draw(move |buf, rect| {
1180    ///     // use `data` here
1181    /// });
1182    /// ```
1183    pub fn draw(self, f: impl FnOnce(&mut crate::buffer::Buffer, Rect) + 'static) {
1184        let draw_id = self.ctx.deferred_draws.len();
1185        self.ctx.deferred_draws.push(Some(Box::new(f)));
1186        self.ctx.skip_interaction_slot();
1187        self.ctx.commands.push(Command::RawDraw {
1188            draw_id,
1189            constraints: self.constraints,
1190            grow: self.grow,
1191            margin: self.margin,
1192        });
1193    }
1194
1195    /// Like [`draw`](Self::draw), but carries owned per-frame `data` through
1196    /// to the deferred closure as a borrow.
1197    ///
1198    /// Raw-draw closures must be `'static` because they run after layout is
1199    /// computed — which normally forces callers to snapshot any borrowed
1200    /// state into an owned value before passing it in. `draw_with` makes
1201    /// that explicit: hand the snapshot over, borrow it inside the closure.
1202    ///
1203    /// # Example
1204    ///
1205    /// ```no_run
1206    /// # use slt::{Buffer, Rect, Style};
1207    /// # slt::run(|ui: &mut slt::Context| {
1208    /// let points: Vec<(u32, u32)> = (0..20).map(|i| (i, i * 2)).collect();
1209    /// ui.container().w(40).h(20).draw_with(points, |buf, rect, points| {
1210    ///     for (x, y) in points {
1211    ///         if rect.contains(*x, *y) {
1212    ///             buf.set_char(*x, *y, '●', Style::new());
1213    ///         }
1214    ///     }
1215    /// });
1216    /// # });
1217    /// ```
1218    pub fn draw_with<D: 'static>(
1219        self,
1220        data: D,
1221        f: impl FnOnce(&mut crate::buffer::Buffer, Rect, &D) + 'static,
1222    ) {
1223        let draw_id = self.ctx.deferred_draws.len();
1224        self.ctx
1225            .deferred_draws
1226            .push(Some(Box::new(move |buf, rect| f(buf, rect, &data))));
1227        self.ctx.skip_interaction_slot();
1228        self.ctx.commands.push(Command::RawDraw {
1229            draw_id,
1230            constraints: self.constraints,
1231            grow: self.grow,
1232            margin: self.margin,
1233        });
1234    }
1235
1236    /// Custom drawing with click and hover detection.
1237    ///
1238    /// Like [`draw`](Self::draw), but the returned [`Response`] reports
1239    /// `clicked` and `hovered` based on the laid-out region — exactly like
1240    /// `.col()` or `.row()`.
1241    ///
1242    /// # Example
1243    ///
1244    /// ```no_run
1245    /// # slt::run(|ui: &mut slt::Context| {
1246    /// let resp = ui.container()
1247    ///     .w(40).h(10)
1248    ///     .draw_interactive(|buf, rect| {
1249    ///         buf.set_string(rect.x, rect.y, "Click me!", slt::Style::new());
1250    ///     });
1251    /// if resp.clicked {
1252    ///     // handle click
1253    /// }
1254    /// # });
1255    /// ```
1256    pub fn draw_interactive(
1257        self,
1258        f: impl FnOnce(&mut crate::buffer::Buffer, Rect) + 'static,
1259    ) -> Response {
1260        let draw_id = self.ctx.deferred_draws.len();
1261        self.ctx.deferred_draws.push(Some(Box::new(f)));
1262        let interaction_id = self.ctx.next_interaction_id();
1263        self.ctx.commands.push(Command::RawDraw {
1264            draw_id,
1265            constraints: self.constraints,
1266            grow: self.grow,
1267            margin: self.margin,
1268        });
1269        self.ctx.response_for(interaction_id)
1270    }
1271
1272    fn finish(mut self, direction: Direction, f: impl FnOnce(&mut Context)) -> Response {
1273        let interaction_id = self.ctx.next_interaction_id();
1274        let resolved_gap = match direction {
1275            Direction::Column => self.row_gap.unwrap_or(self.gap),
1276            Direction::Row => self.col_gap.unwrap_or(self.gap),
1277        };
1278
1279        let in_hovered_group = self
1280            .group_name
1281            .as_ref()
1282            .map(|name| self.ctx.is_group_hovered(name))
1283            .unwrap_or(false)
1284            || self
1285                .ctx
1286                .rollback
1287                .group_stack
1288                .last()
1289                .map(|name| self.ctx.is_group_hovered(name))
1290                .unwrap_or(false);
1291        let in_focused_group = self
1292            .group_name
1293            .as_ref()
1294            .map(|name| self.ctx.is_group_focused(name))
1295            .unwrap_or(false)
1296            || self
1297                .ctx
1298                .rollback
1299                .group_stack
1300                .last()
1301                .map(|name| self.ctx.is_group_focused(name))
1302                .unwrap_or(false);
1303
1304        let resolved_bg = if self.ctx.rollback.dark_mode {
1305            self.dark_bg.or(self.bg)
1306        } else {
1307            self.bg
1308        };
1309        let resolved_border_style = if self.ctx.rollback.dark_mode {
1310            self.dark_border_style.unwrap_or(self.border_style)
1311        } else {
1312            self.border_style
1313        };
1314        let bg_color = if in_hovered_group || in_focused_group {
1315            self.group_hover_bg.or(resolved_bg)
1316        } else {
1317            resolved_bg
1318        };
1319        let border_style = if in_hovered_group || in_focused_group {
1320            self.group_hover_border_style
1321                .unwrap_or(resolved_border_style)
1322        } else {
1323            resolved_border_style
1324        };
1325        let group_name = self.group_name.take();
1326        let is_group_container = group_name.is_some();
1327
1328        if let Some(scroll_offset) = self.scroll_offset {
1329            self.ctx
1330                .commands
1331                .push(Command::BeginScrollable(Box::new(BeginScrollableArgs {
1332                    grow: self.grow,
1333                    border: self.border,
1334                    border_sides: self.border_sides,
1335                    border_style,
1336                    padding: self.padding,
1337                    margin: self.margin,
1338                    constraints: self.constraints,
1339                    title: self.title,
1340                    scroll_offset,
1341                })));
1342        } else {
1343            self.ctx
1344                .commands
1345                .push(Command::BeginContainer(Box::new(BeginContainerArgs {
1346                    direction,
1347                    gap: resolved_gap,
1348                    align: self.align,
1349                    align_self: self.align_self_value,
1350                    justify: self.justify,
1351                    border: self.border,
1352                    border_sides: self.border_sides,
1353                    border_style,
1354                    bg_color,
1355                    padding: self.padding,
1356                    margin: self.margin,
1357                    constraints: self.constraints,
1358                    title: self.title,
1359                    grow: self.grow,
1360                    group_name,
1361                })));
1362        }
1363        self.ctx.rollback.text_color_stack.push(self.text_color);
1364        f(self.ctx);
1365        self.ctx.rollback.text_color_stack.pop();
1366        self.ctx.commands.push(Command::EndContainer);
1367        self.ctx.rollback.last_text_idx = None;
1368
1369        if is_group_container {
1370            self.ctx.rollback.group_stack.pop();
1371            self.ctx.rollback.group_count = self.ctx.rollback.group_count.saturating_sub(1);
1372        }
1373
1374        self.ctx.response_for(interaction_id)
1375    }
1376}