Skip to main content

slt/
context.rs

1use crate::chart::{build_histogram_config, render_chart, ChartBuilder, HistogramBuilder};
2use crate::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseKind};
3use crate::halfblock::HalfBlockImage;
4use crate::layout::{Command, Direction};
5use crate::rect::Rect;
6use crate::style::{
7    Align, Border, BorderSides, Breakpoint, Color, Constraints, ContainerStyle, Justify, Margin,
8    Modifiers, Padding, Style, Theme,
9};
10use crate::widgets::{
11    ApprovalAction, ButtonVariant, CommandPaletteState, ContextItem, FormField, FormState,
12    ListState, MultiSelectState, RadioState, ScrollState, SelectState, SpinnerState,
13    StreamingTextState, TableState, TabsState, TextInputState, TextareaState, ToastLevel,
14    ToastState, ToolApprovalState, TreeState,
15};
16use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
17
18#[allow(dead_code)]
19fn slt_assert(condition: bool, msg: &str) {
20    if !condition {
21        panic!("[SLT] {}", msg);
22    }
23}
24
25#[cfg(debug_assertions)]
26#[allow(dead_code)]
27fn slt_warn(msg: &str) {
28    eprintln!("\x1b[33m[SLT warning]\x1b[0m {}", msg);
29}
30
31#[cfg(not(debug_assertions))]
32#[allow(dead_code)]
33fn slt_warn(_msg: &str) {}
34
35/// Handle to state created by `use_state()`. Access via `.get(ui)` / `.get_mut(ui)`.
36#[derive(Debug, Copy, Clone, PartialEq, Eq)]
37pub struct State<T> {
38    idx: usize,
39    _marker: std::marker::PhantomData<T>,
40}
41
42impl<T: 'static> State<T> {
43    /// Read the current value.
44    pub fn get<'a>(&self, ui: &'a Context) -> &'a T {
45        ui.hook_states[self.idx]
46            .downcast_ref::<T>()
47            .unwrap_or_else(|| {
48                panic!(
49                    "use_state type mismatch at hook index {} — expected {}",
50                    self.idx,
51                    std::any::type_name::<T>()
52                )
53            })
54    }
55
56    /// Mutably access the current value.
57    pub fn get_mut<'a>(&self, ui: &'a mut Context) -> &'a mut T {
58        ui.hook_states[self.idx]
59            .downcast_mut::<T>()
60            .unwrap_or_else(|| {
61                panic!(
62                    "use_state type mismatch at hook index {} — expected {}",
63                    self.idx,
64                    std::any::type_name::<T>()
65                )
66            })
67    }
68}
69
70/// Result of a container mouse interaction.
71///
72/// Returned by [`Context::col`], [`Context::row`], and [`ContainerBuilder::col`] /
73/// [`ContainerBuilder::row`] so you can react to clicks and hover without a separate
74/// event loop.
75#[derive(Debug, Clone, Copy, Default)]
76pub struct Response {
77    /// Whether the container was clicked this frame.
78    pub clicked: bool,
79    /// Whether the mouse is over the container.
80    pub hovered: bool,
81}
82
83/// Direction for bar chart rendering.
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum BarDirection {
86    /// Bars grow horizontally (default, current behavior).
87    Horizontal,
88    /// Bars grow vertically from bottom to top.
89    Vertical,
90}
91
92/// A single bar in a styled bar chart.
93#[derive(Debug, Clone)]
94pub struct Bar {
95    /// Display label for this bar.
96    pub label: String,
97    /// Numeric value.
98    pub value: f64,
99    /// Bar color. If None, uses theme.primary.
100    pub color: Option<Color>,
101}
102
103impl Bar {
104    /// Create a new bar with a label and value.
105    pub fn new(label: impl Into<String>, value: f64) -> Self {
106        Self {
107            label: label.into(),
108            value,
109            color: None,
110        }
111    }
112
113    /// Set the bar color.
114    pub fn color(mut self, color: Color) -> Self {
115        self.color = Some(color);
116        self
117    }
118}
119
120/// A group of bars rendered together (for grouped bar charts).
121#[derive(Debug, Clone)]
122pub struct BarGroup {
123    /// Group label displayed below the bars.
124    pub label: String,
125    /// Bars in this group.
126    pub bars: Vec<Bar>,
127}
128
129impl BarGroup {
130    /// Create a new bar group with a label and bars.
131    pub fn new(label: impl Into<String>, bars: Vec<Bar>) -> Self {
132        Self {
133            label: label.into(),
134            bars,
135        }
136    }
137}
138
139/// Trait for creating custom widgets.
140///
141/// Implement this trait to build reusable, composable widgets with full access
142/// to the [`Context`] API — focus, events, theming, layout, and mouse interaction.
143///
144/// # Examples
145///
146/// A simple rating widget:
147///
148/// ```no_run
149/// use slt::{Context, Widget, Color};
150///
151/// struct Rating {
152///     value: u8,
153///     max: u8,
154/// }
155///
156/// impl Rating {
157///     fn new(value: u8, max: u8) -> Self {
158///         Self { value, max }
159///     }
160/// }
161///
162/// impl Widget for Rating {
163///     type Response = bool;
164///
165///     fn ui(&mut self, ui: &mut Context) -> bool {
166///         let focused = ui.register_focusable();
167///         let mut changed = false;
168///
169///         if focused {
170///             if ui.key('+') && self.value < self.max {
171///                 self.value += 1;
172///                 changed = true;
173///             }
174///             if ui.key('-') && self.value > 0 {
175///                 self.value -= 1;
176///                 changed = true;
177///             }
178///         }
179///
180///         let stars: String = (0..self.max).map(|i| {
181///             if i < self.value { '★' } else { '☆' }
182///         }).collect();
183///
184///         let color = if focused { Color::Yellow } else { Color::White };
185///         ui.styled(stars, slt::Style::new().fg(color));
186///
187///         changed
188///     }
189/// }
190///
191/// fn main() -> std::io::Result<()> {
192///     let mut rating = Rating::new(3, 5);
193///     slt::run(|ui| {
194///         if ui.key('q') { ui.quit(); }
195///         ui.text("Rate this:");
196///         ui.widget(&mut rating);
197///     })
198/// }
199/// ```
200pub trait Widget {
201    /// The value returned after rendering. Use `()` for widgets with no return,
202    /// `bool` for widgets that report changes, or [`Response`] for click/hover.
203    type Response;
204
205    /// Render the widget into the given context.
206    ///
207    /// Use [`Context::register_focusable`] to participate in Tab focus cycling,
208    /// [`Context::key`] / [`Context::key_code`] to handle keyboard input,
209    /// and [`Context::interaction`] to detect clicks and hovers.
210    fn ui(&mut self, ctx: &mut Context) -> Self::Response;
211}
212
213/// The main rendering context passed to your closure each frame.
214///
215/// Provides all methods for building UI: text, containers, widgets, and event
216/// handling. You receive a `&mut Context` on every frame and describe what to
217/// render by calling its methods. SLT collects those calls, lays them out with
218/// flexbox, diffs against the previous frame, and flushes only changed cells.
219///
220/// # Example
221///
222/// ```no_run
223/// slt::run(|ui: &mut slt::Context| {
224///     if ui.key('q') { ui.quit(); }
225///     ui.text("Hello, world!").bold();
226/// });
227/// ```
228pub struct Context {
229    pub(crate) commands: Vec<Command>,
230    pub(crate) events: Vec<Event>,
231    pub(crate) consumed: Vec<bool>,
232    pub(crate) should_quit: bool,
233    pub(crate) area_width: u32,
234    pub(crate) area_height: u32,
235    pub(crate) tick: u64,
236    pub(crate) focus_index: usize,
237    pub(crate) focus_count: usize,
238    pub(crate) hook_states: Vec<Box<dyn std::any::Any>>,
239    pub(crate) hook_cursor: usize,
240    prev_focus_count: usize,
241    scroll_count: usize,
242    prev_scroll_infos: Vec<(u32, u32)>,
243    prev_scroll_rects: Vec<Rect>,
244    interaction_count: usize,
245    pub(crate) prev_hit_map: Vec<Rect>,
246    pub(crate) group_stack: Vec<String>,
247    pub(crate) prev_group_rects: Vec<(String, Rect)>,
248    group_count: usize,
249    prev_focus_groups: Vec<Option<String>>,
250    _prev_focus_rects: Vec<(usize, Rect)>,
251    mouse_pos: Option<(u32, u32)>,
252    click_pos: Option<(u32, u32)>,
253    last_text_idx: Option<usize>,
254    overlay_depth: usize,
255    pub(crate) modal_active: bool,
256    prev_modal_active: bool,
257    pub(crate) clipboard_text: Option<String>,
258    debug: bool,
259    theme: Theme,
260    pub(crate) dark_mode: bool,
261    pub(crate) deferred_draws: Vec<Option<RawDrawCallback>>,
262}
263
264type RawDrawCallback = Box<dyn FnOnce(&mut crate::buffer::Buffer, Rect)>;
265
266/// Fluent builder for configuring containers before calling `.col()` or `.row()`.
267///
268/// Obtain one via [`Context::container`] or [`Context::bordered`]. Chain the
269/// configuration methods you need, then finalize with `.col(|ui| { ... })` or
270/// `.row(|ui| { ... })`.
271///
272/// # Example
273///
274/// ```no_run
275/// # slt::run(|ui: &mut slt::Context| {
276/// use slt::{Border, Color};
277/// ui.container()
278///     .border(Border::Rounded)
279///     .pad(1)
280///     .grow(1)
281///     .col(|ui| {
282///         ui.text("inside a bordered, padded, growing column");
283///     });
284/// # });
285/// ```
286#[must_use = "configure and finalize with .col() or .row()"]
287pub struct ContainerBuilder<'a> {
288    ctx: &'a mut Context,
289    gap: u32,
290    align: Align,
291    justify: Justify,
292    border: Option<Border>,
293    border_sides: BorderSides,
294    border_style: Style,
295    bg_color: Option<Color>,
296    dark_bg_color: Option<Color>,
297    dark_border_style: Option<Style>,
298    group_hover_bg: Option<Color>,
299    group_hover_border_style: Option<Style>,
300    group_name: Option<String>,
301    padding: Padding,
302    margin: Margin,
303    constraints: Constraints,
304    title: Option<(String, Style)>,
305    grow: u16,
306    scroll_offset: Option<u32>,
307}
308
309/// Drawing context for the [`Context::canvas`] widget.
310///
311/// Provides pixel-level drawing on a braille character grid. Each terminal
312/// cell maps to a 2x4 dot matrix, so a canvas of `width` columns x `height`
313/// rows gives `width*2` x `height*4` pixel resolution.
314/// A colored pixel in the canvas grid.
315#[derive(Debug, Clone, Copy)]
316struct CanvasPixel {
317    bits: u32,
318    color: Color,
319}
320
321/// Text label placed on the canvas.
322#[derive(Debug, Clone)]
323struct CanvasLabel {
324    x: usize,
325    y: usize,
326    text: String,
327    color: Color,
328}
329
330/// A layer in the canvas, supporting z-ordering.
331#[derive(Debug, Clone)]
332struct CanvasLayer {
333    grid: Vec<Vec<CanvasPixel>>,
334    labels: Vec<CanvasLabel>,
335}
336
337pub struct CanvasContext {
338    layers: Vec<CanvasLayer>,
339    cols: usize,
340    rows: usize,
341    px_w: usize,
342    px_h: usize,
343    current_color: Color,
344}
345
346impl CanvasContext {
347    fn new(cols: usize, rows: usize) -> Self {
348        Self {
349            layers: vec![Self::new_layer(cols, rows)],
350            cols,
351            rows,
352            px_w: cols * 2,
353            px_h: rows * 4,
354            current_color: Color::Reset,
355        }
356    }
357
358    fn new_layer(cols: usize, rows: usize) -> CanvasLayer {
359        CanvasLayer {
360            grid: vec![
361                vec![
362                    CanvasPixel {
363                        bits: 0,
364                        color: Color::Reset,
365                    };
366                    cols
367                ];
368                rows
369            ],
370            labels: Vec::new(),
371        }
372    }
373
374    fn current_layer_mut(&mut self) -> Option<&mut CanvasLayer> {
375        self.layers.last_mut()
376    }
377
378    fn dot_with_color(&mut self, x: usize, y: usize, color: Color) {
379        if x >= self.px_w || y >= self.px_h {
380            return;
381        }
382
383        let char_col = x / 2;
384        let char_row = y / 4;
385        let sub_col = x % 2;
386        let sub_row = y % 4;
387        const LEFT_BITS: [u32; 4] = [0x01, 0x02, 0x04, 0x40];
388        const RIGHT_BITS: [u32; 4] = [0x08, 0x10, 0x20, 0x80];
389
390        let bit = if sub_col == 0 {
391            LEFT_BITS[sub_row]
392        } else {
393            RIGHT_BITS[sub_row]
394        };
395
396        if let Some(layer) = self.current_layer_mut() {
397            let cell = &mut layer.grid[char_row][char_col];
398            let new_bits = cell.bits | bit;
399            if new_bits != cell.bits {
400                cell.bits = new_bits;
401                cell.color = color;
402            }
403        }
404    }
405
406    fn dot_isize(&mut self, x: isize, y: isize) {
407        if x >= 0 && y >= 0 {
408            self.dot(x as usize, y as usize);
409        }
410    }
411
412    /// Get the pixel width of the canvas.
413    pub fn width(&self) -> usize {
414        self.px_w
415    }
416
417    /// Get the pixel height of the canvas.
418    pub fn height(&self) -> usize {
419        self.px_h
420    }
421
422    /// Set a single pixel at `(x, y)`.
423    pub fn dot(&mut self, x: usize, y: usize) {
424        self.dot_with_color(x, y, self.current_color);
425    }
426
427    /// Draw a line from `(x0, y0)` to `(x1, y1)` using Bresenham's algorithm.
428    pub fn line(&mut self, x0: usize, y0: usize, x1: usize, y1: usize) {
429        let (mut x, mut y) = (x0 as isize, y0 as isize);
430        let (x1, y1) = (x1 as isize, y1 as isize);
431        let dx = (x1 - x).abs();
432        let dy = -(y1 - y).abs();
433        let sx = if x < x1 { 1 } else { -1 };
434        let sy = if y < y1 { 1 } else { -1 };
435        let mut err = dx + dy;
436
437        loop {
438            self.dot_isize(x, y);
439            if x == x1 && y == y1 {
440                break;
441            }
442            let e2 = 2 * err;
443            if e2 >= dy {
444                err += dy;
445                x += sx;
446            }
447            if e2 <= dx {
448                err += dx;
449                y += sy;
450            }
451        }
452    }
453
454    /// Draw a rectangle outline from `(x, y)` with `w` width and `h` height.
455    pub fn rect(&mut self, x: usize, y: usize, w: usize, h: usize) {
456        if w == 0 || h == 0 {
457            return;
458        }
459
460        self.line(x, y, x + w.saturating_sub(1), y);
461        self.line(
462            x + w.saturating_sub(1),
463            y,
464            x + w.saturating_sub(1),
465            y + h.saturating_sub(1),
466        );
467        self.line(
468            x + w.saturating_sub(1),
469            y + h.saturating_sub(1),
470            x,
471            y + h.saturating_sub(1),
472        );
473        self.line(x, y + h.saturating_sub(1), x, y);
474    }
475
476    /// Draw a circle outline centered at `(cx, cy)` with radius `r`.
477    pub fn circle(&mut self, cx: usize, cy: usize, r: usize) {
478        let mut x = r as isize;
479        let mut y: isize = 0;
480        let mut err: isize = 1 - x;
481        let (cx, cy) = (cx as isize, cy as isize);
482
483        while x >= y {
484            for &(dx, dy) in &[
485                (x, y),
486                (y, x),
487                (-x, y),
488                (-y, x),
489                (x, -y),
490                (y, -x),
491                (-x, -y),
492                (-y, -x),
493            ] {
494                let px = cx + dx;
495                let py = cy + dy;
496                self.dot_isize(px, py);
497            }
498
499            y += 1;
500            if err < 0 {
501                err += 2 * y + 1;
502            } else {
503                x -= 1;
504                err += 2 * (y - x) + 1;
505            }
506        }
507    }
508
509    /// Set the drawing color for subsequent shapes.
510    pub fn set_color(&mut self, color: Color) {
511        self.current_color = color;
512    }
513
514    /// Get the current drawing color.
515    pub fn color(&self) -> Color {
516        self.current_color
517    }
518
519    /// Draw a filled rectangle.
520    pub fn filled_rect(&mut self, x: usize, y: usize, w: usize, h: usize) {
521        if w == 0 || h == 0 {
522            return;
523        }
524
525        let x_end = x.saturating_add(w).min(self.px_w);
526        let y_end = y.saturating_add(h).min(self.px_h);
527        if x >= x_end || y >= y_end {
528            return;
529        }
530
531        for yy in y..y_end {
532            self.line(x, yy, x_end.saturating_sub(1), yy);
533        }
534    }
535
536    /// Draw a filled circle.
537    pub fn filled_circle(&mut self, cx: usize, cy: usize, r: usize) {
538        let (cx, cy, r) = (cx as isize, cy as isize, r as isize);
539        for y in (cy - r)..=(cy + r) {
540            let dy = y - cy;
541            let span_sq = (r * r - dy * dy).max(0);
542            let dx = (span_sq as f64).sqrt() as isize;
543            for x in (cx - dx)..=(cx + dx) {
544                self.dot_isize(x, y);
545            }
546        }
547    }
548
549    /// Draw a triangle outline.
550    pub fn triangle(&mut self, x0: usize, y0: usize, x1: usize, y1: usize, x2: usize, y2: usize) {
551        self.line(x0, y0, x1, y1);
552        self.line(x1, y1, x2, y2);
553        self.line(x2, y2, x0, y0);
554    }
555
556    /// Draw a filled triangle.
557    pub fn filled_triangle(
558        &mut self,
559        x0: usize,
560        y0: usize,
561        x1: usize,
562        y1: usize,
563        x2: usize,
564        y2: usize,
565    ) {
566        let vertices = [
567            (x0 as isize, y0 as isize),
568            (x1 as isize, y1 as isize),
569            (x2 as isize, y2 as isize),
570        ];
571        let min_y = vertices.iter().map(|(_, y)| *y).min().unwrap_or(0);
572        let max_y = vertices.iter().map(|(_, y)| *y).max().unwrap_or(-1);
573
574        for y in min_y..=max_y {
575            let mut intersections: Vec<f64> = Vec::new();
576
577            for edge in [(0usize, 1usize), (1usize, 2usize), (2usize, 0usize)] {
578                let (x_a, y_a) = vertices[edge.0];
579                let (x_b, y_b) = vertices[edge.1];
580                if y_a == y_b {
581                    continue;
582                }
583
584                let (x_start, y_start, x_end, y_end) = if y_a < y_b {
585                    (x_a, y_a, x_b, y_b)
586                } else {
587                    (x_b, y_b, x_a, y_a)
588                };
589
590                if y < y_start || y >= y_end {
591                    continue;
592                }
593
594                let t = (y - y_start) as f64 / (y_end - y_start) as f64;
595                intersections.push(x_start as f64 + t * (x_end - x_start) as f64);
596            }
597
598            intersections.sort_by(|a, b| a.total_cmp(b));
599            let mut i = 0usize;
600            while i + 1 < intersections.len() {
601                let x_start = intersections[i].ceil() as isize;
602                let x_end = intersections[i + 1].floor() as isize;
603                for x in x_start..=x_end {
604                    self.dot_isize(x, y);
605                }
606                i += 2;
607            }
608        }
609
610        self.triangle(x0, y0, x1, y1, x2, y2);
611    }
612
613    /// Draw multiple points at once.
614    pub fn points(&mut self, pts: &[(usize, usize)]) {
615        for &(x, y) in pts {
616            self.dot(x, y);
617        }
618    }
619
620    /// Draw a polyline connecting the given points in order.
621    pub fn polyline(&mut self, pts: &[(usize, usize)]) {
622        for window in pts.windows(2) {
623            if let [(x0, y0), (x1, y1)] = window {
624                self.line(*x0, *y0, *x1, *y1);
625            }
626        }
627    }
628
629    /// Place a text label at pixel position `(x, y)`.
630    /// Text is rendered in regular characters overlaying the braille grid.
631    pub fn print(&mut self, x: usize, y: usize, text: &str) {
632        if text.is_empty() {
633            return;
634        }
635
636        let color = self.current_color;
637        if let Some(layer) = self.current_layer_mut() {
638            layer.labels.push(CanvasLabel {
639                x,
640                y,
641                text: text.to_string(),
642                color,
643            });
644        }
645    }
646
647    /// Start a new drawing layer. Shapes on later layers overlay earlier ones.
648    pub fn layer(&mut self) {
649        self.layers.push(Self::new_layer(self.cols, self.rows));
650    }
651
652    pub(crate) fn render(&self) -> Vec<Vec<(String, Color)>> {
653        let mut final_grid = vec![
654            vec![
655                CanvasPixel {
656                    bits: 0,
657                    color: Color::Reset,
658                };
659                self.cols
660            ];
661            self.rows
662        ];
663        let mut labels_overlay: Vec<Vec<Option<(char, Color)>>> =
664            vec![vec![None; self.cols]; self.rows];
665
666        for layer in &self.layers {
667            for (row, final_row) in final_grid.iter_mut().enumerate().take(self.rows) {
668                for (col, dst) in final_row.iter_mut().enumerate().take(self.cols) {
669                    let src = layer.grid[row][col];
670                    if src.bits == 0 {
671                        continue;
672                    }
673
674                    let merged = dst.bits | src.bits;
675                    if merged != dst.bits {
676                        dst.bits = merged;
677                        dst.color = src.color;
678                    }
679                }
680            }
681
682            for label in &layer.labels {
683                let row = label.y / 4;
684                if row >= self.rows {
685                    continue;
686                }
687                let start_col = label.x / 2;
688                for (offset, ch) in label.text.chars().enumerate() {
689                    let col = start_col + offset;
690                    if col >= self.cols {
691                        break;
692                    }
693                    labels_overlay[row][col] = Some((ch, label.color));
694                }
695            }
696        }
697
698        let mut lines: Vec<Vec<(String, Color)>> = Vec::with_capacity(self.rows);
699        for row in 0..self.rows {
700            let mut segments: Vec<(String, Color)> = Vec::new();
701            let mut current_color: Option<Color> = None;
702            let mut current_text = String::new();
703
704            for col in 0..self.cols {
705                let (ch, color) = if let Some((label_ch, label_color)) = labels_overlay[row][col] {
706                    (label_ch, label_color)
707                } else {
708                    let bits = final_grid[row][col].bits;
709                    let ch = char::from_u32(0x2800 + bits).unwrap_or(' ');
710                    (ch, final_grid[row][col].color)
711                };
712
713                match current_color {
714                    Some(c) if c == color => {
715                        current_text.push(ch);
716                    }
717                    Some(c) => {
718                        segments.push((std::mem::take(&mut current_text), c));
719                        current_text.push(ch);
720                        current_color = Some(color);
721                    }
722                    None => {
723                        current_text.push(ch);
724                        current_color = Some(color);
725                    }
726                }
727            }
728
729            if let Some(color) = current_color {
730                segments.push((current_text, color));
731            }
732            lines.push(segments);
733        }
734
735        lines
736    }
737}
738
739impl<'a> ContainerBuilder<'a> {
740    // ── border ───────────────────────────────────────────────────────
741
742    /// Apply a reusable [`ContainerStyle`] recipe. Only set fields override
743    /// the builder's current values. Chain multiple `.apply()` calls to compose.
744    pub fn apply(mut self, style: &ContainerStyle) -> Self {
745        if let Some(v) = style.border {
746            self.border = Some(v);
747        }
748        if let Some(v) = style.border_sides {
749            self.border_sides = v;
750        }
751        if let Some(v) = style.border_style {
752            self.border_style = v;
753        }
754        if let Some(v) = style.bg {
755            self.bg_color = Some(v);
756        }
757        if let Some(v) = style.dark_bg {
758            self.dark_bg_color = Some(v);
759        }
760        if let Some(v) = style.dark_border_style {
761            self.dark_border_style = Some(v);
762        }
763        if let Some(v) = style.padding {
764            self.padding = v;
765        }
766        if let Some(v) = style.margin {
767            self.margin = v;
768        }
769        if let Some(v) = style.gap {
770            self.gap = v;
771        }
772        if let Some(v) = style.grow {
773            self.grow = v;
774        }
775        if let Some(v) = style.align {
776            self.align = v;
777        }
778        if let Some(v) = style.justify {
779            self.justify = v;
780        }
781        if let Some(w) = style.w {
782            self.constraints.min_width = Some(w);
783            self.constraints.max_width = Some(w);
784        }
785        if let Some(h) = style.h {
786            self.constraints.min_height = Some(h);
787            self.constraints.max_height = Some(h);
788        }
789        if let Some(v) = style.min_w {
790            self.constraints.min_width = Some(v);
791        }
792        if let Some(v) = style.max_w {
793            self.constraints.max_width = Some(v);
794        }
795        if let Some(v) = style.min_h {
796            self.constraints.min_height = Some(v);
797        }
798        if let Some(v) = style.max_h {
799            self.constraints.max_height = Some(v);
800        }
801        if let Some(v) = style.w_pct {
802            self.constraints.width_pct = Some(v);
803        }
804        if let Some(v) = style.h_pct {
805            self.constraints.height_pct = Some(v);
806        }
807        self
808    }
809
810    /// Set the border style.
811    pub fn border(mut self, border: Border) -> Self {
812        self.border = Some(border);
813        self
814    }
815
816    /// Show or hide the top border.
817    pub fn border_top(mut self, show: bool) -> Self {
818        self.border_sides.top = show;
819        self
820    }
821
822    /// Show or hide the right border.
823    pub fn border_right(mut self, show: bool) -> Self {
824        self.border_sides.right = show;
825        self
826    }
827
828    /// Show or hide the bottom border.
829    pub fn border_bottom(mut self, show: bool) -> Self {
830        self.border_sides.bottom = show;
831        self
832    }
833
834    /// Show or hide the left border.
835    pub fn border_left(mut self, show: bool) -> Self {
836        self.border_sides.left = show;
837        self
838    }
839
840    /// Set which border sides are visible.
841    pub fn border_sides(mut self, sides: BorderSides) -> Self {
842        self.border_sides = sides;
843        self
844    }
845
846    /// Set rounded border style. Shorthand for `.border(Border::Rounded)`.
847    pub fn rounded(self) -> Self {
848        self.border(Border::Rounded)
849    }
850
851    /// Set the style applied to the border characters.
852    pub fn border_style(mut self, style: Style) -> Self {
853        self.border_style = style;
854        self
855    }
856
857    /// Border style used when dark mode is active.
858    pub fn dark_border_style(mut self, style: Style) -> Self {
859        self.dark_border_style = Some(style);
860        self
861    }
862
863    pub fn bg(mut self, color: Color) -> Self {
864        self.bg_color = Some(color);
865        self
866    }
867
868    /// Background color used when dark mode is active.
869    pub fn dark_bg(mut self, color: Color) -> Self {
870        self.dark_bg_color = Some(color);
871        self
872    }
873
874    /// Background color applied when the parent group is hovered.
875    pub fn group_hover_bg(mut self, color: Color) -> Self {
876        self.group_hover_bg = Some(color);
877        self
878    }
879
880    /// Border style applied when the parent group is hovered.
881    pub fn group_hover_border_style(mut self, style: Style) -> Self {
882        self.group_hover_border_style = Some(style);
883        self
884    }
885
886    // ── padding (Tailwind: p, px, py, pt, pr, pb, pl) ───────────────
887
888    /// Set uniform padding on all sides. Alias for [`pad`](Self::pad).
889    pub fn p(self, value: u32) -> Self {
890        self.pad(value)
891    }
892
893    /// Set uniform padding on all sides.
894    pub fn pad(mut self, value: u32) -> Self {
895        self.padding = Padding::all(value);
896        self
897    }
898
899    /// Set horizontal padding (left and right).
900    pub fn px(mut self, value: u32) -> Self {
901        self.padding.left = value;
902        self.padding.right = value;
903        self
904    }
905
906    /// Set vertical padding (top and bottom).
907    pub fn py(mut self, value: u32) -> Self {
908        self.padding.top = value;
909        self.padding.bottom = value;
910        self
911    }
912
913    /// Set top padding.
914    pub fn pt(mut self, value: u32) -> Self {
915        self.padding.top = value;
916        self
917    }
918
919    /// Set right padding.
920    pub fn pr(mut self, value: u32) -> Self {
921        self.padding.right = value;
922        self
923    }
924
925    /// Set bottom padding.
926    pub fn pb(mut self, value: u32) -> Self {
927        self.padding.bottom = value;
928        self
929    }
930
931    /// Set left padding.
932    pub fn pl(mut self, value: u32) -> Self {
933        self.padding.left = value;
934        self
935    }
936
937    /// Set per-side padding using a [`Padding`] value.
938    pub fn padding(mut self, padding: Padding) -> Self {
939        self.padding = padding;
940        self
941    }
942
943    // ── margin (Tailwind: m, mx, my, mt, mr, mb, ml) ────────────────
944
945    /// Set uniform margin on all sides.
946    pub fn m(mut self, value: u32) -> Self {
947        self.margin = Margin::all(value);
948        self
949    }
950
951    /// Set horizontal margin (left and right).
952    pub fn mx(mut self, value: u32) -> Self {
953        self.margin.left = value;
954        self.margin.right = value;
955        self
956    }
957
958    /// Set vertical margin (top and bottom).
959    pub fn my(mut self, value: u32) -> Self {
960        self.margin.top = value;
961        self.margin.bottom = value;
962        self
963    }
964
965    /// Set top margin.
966    pub fn mt(mut self, value: u32) -> Self {
967        self.margin.top = value;
968        self
969    }
970
971    /// Set right margin.
972    pub fn mr(mut self, value: u32) -> Self {
973        self.margin.right = value;
974        self
975    }
976
977    /// Set bottom margin.
978    pub fn mb(mut self, value: u32) -> Self {
979        self.margin.bottom = value;
980        self
981    }
982
983    /// Set left margin.
984    pub fn ml(mut self, value: u32) -> Self {
985        self.margin.left = value;
986        self
987    }
988
989    /// Set per-side margin using a [`Margin`] value.
990    pub fn margin(mut self, margin: Margin) -> Self {
991        self.margin = margin;
992        self
993    }
994
995    // ── sizing (Tailwind: w, h, min-w, max-w, min-h, max-h) ────────
996
997    /// Set a fixed width (sets both min and max width).
998    pub fn w(mut self, value: u32) -> Self {
999        self.constraints.min_width = Some(value);
1000        self.constraints.max_width = Some(value);
1001        self
1002    }
1003
1004    /// Width applied only at Xs breakpoint (< 40 cols).
1005    ///
1006    /// # Example
1007    /// ```ignore
1008    /// ui.container().w(20).md_w(40).lg_w(60).col(|ui| { ... });
1009    /// ```
1010    pub fn xs_w(self, value: u32) -> Self {
1011        let is_xs = self.ctx.breakpoint() == Breakpoint::Xs;
1012        if is_xs {
1013            self.w(value)
1014        } else {
1015            self
1016        }
1017    }
1018
1019    /// Width applied only at Sm breakpoint (40-79 cols).
1020    pub fn sm_w(self, value: u32) -> Self {
1021        let is_sm = self.ctx.breakpoint() == Breakpoint::Sm;
1022        if is_sm {
1023            self.w(value)
1024        } else {
1025            self
1026        }
1027    }
1028
1029    /// Width applied only at Md breakpoint (80-119 cols).
1030    pub fn md_w(self, value: u32) -> Self {
1031        let is_md = self.ctx.breakpoint() == Breakpoint::Md;
1032        if is_md {
1033            self.w(value)
1034        } else {
1035            self
1036        }
1037    }
1038
1039    /// Width applied only at Lg breakpoint (120-159 cols).
1040    pub fn lg_w(self, value: u32) -> Self {
1041        let is_lg = self.ctx.breakpoint() == Breakpoint::Lg;
1042        if is_lg {
1043            self.w(value)
1044        } else {
1045            self
1046        }
1047    }
1048
1049    /// Width applied only at Xl breakpoint (>= 160 cols).
1050    pub fn xl_w(self, value: u32) -> Self {
1051        let is_xl = self.ctx.breakpoint() == Breakpoint::Xl;
1052        if is_xl {
1053            self.w(value)
1054        } else {
1055            self
1056        }
1057    }
1058    pub fn w_at(self, bp: Breakpoint, value: u32) -> Self {
1059        if self.ctx.breakpoint() == bp {
1060            self.w(value)
1061        } else {
1062            self
1063        }
1064    }
1065
1066    /// Set a fixed height (sets both min and max height).
1067    pub fn h(mut self, value: u32) -> Self {
1068        self.constraints.min_height = Some(value);
1069        self.constraints.max_height = Some(value);
1070        self
1071    }
1072
1073    /// Height applied only at Xs breakpoint (< 40 cols).
1074    pub fn xs_h(self, value: u32) -> Self {
1075        let is_xs = self.ctx.breakpoint() == Breakpoint::Xs;
1076        if is_xs {
1077            self.h(value)
1078        } else {
1079            self
1080        }
1081    }
1082
1083    /// Height applied only at Sm breakpoint (40-79 cols).
1084    pub fn sm_h(self, value: u32) -> Self {
1085        let is_sm = self.ctx.breakpoint() == Breakpoint::Sm;
1086        if is_sm {
1087            self.h(value)
1088        } else {
1089            self
1090        }
1091    }
1092
1093    /// Height applied only at Md breakpoint (80-119 cols).
1094    pub fn md_h(self, value: u32) -> Self {
1095        let is_md = self.ctx.breakpoint() == Breakpoint::Md;
1096        if is_md {
1097            self.h(value)
1098        } else {
1099            self
1100        }
1101    }
1102
1103    /// Height applied only at Lg breakpoint (120-159 cols).
1104    pub fn lg_h(self, value: u32) -> Self {
1105        let is_lg = self.ctx.breakpoint() == Breakpoint::Lg;
1106        if is_lg {
1107            self.h(value)
1108        } else {
1109            self
1110        }
1111    }
1112
1113    /// Height applied only at Xl breakpoint (>= 160 cols).
1114    pub fn xl_h(self, value: u32) -> Self {
1115        let is_xl = self.ctx.breakpoint() == Breakpoint::Xl;
1116        if is_xl {
1117            self.h(value)
1118        } else {
1119            self
1120        }
1121    }
1122    pub fn h_at(self, bp: Breakpoint, value: u32) -> Self {
1123        if self.ctx.breakpoint() == bp {
1124            self.h(value)
1125        } else {
1126            self
1127        }
1128    }
1129
1130    /// Set the minimum width constraint. Shorthand for [`min_width`](Self::min_width).
1131    pub fn min_w(mut self, value: u32) -> Self {
1132        self.constraints.min_width = Some(value);
1133        self
1134    }
1135
1136    /// Minimum width applied only at Xs breakpoint (< 40 cols).
1137    pub fn xs_min_w(self, value: u32) -> Self {
1138        let is_xs = self.ctx.breakpoint() == Breakpoint::Xs;
1139        if is_xs {
1140            self.min_w(value)
1141        } else {
1142            self
1143        }
1144    }
1145
1146    /// Minimum width applied only at Sm breakpoint (40-79 cols).
1147    pub fn sm_min_w(self, value: u32) -> Self {
1148        let is_sm = self.ctx.breakpoint() == Breakpoint::Sm;
1149        if is_sm {
1150            self.min_w(value)
1151        } else {
1152            self
1153        }
1154    }
1155
1156    /// Minimum width applied only at Md breakpoint (80-119 cols).
1157    pub fn md_min_w(self, value: u32) -> Self {
1158        let is_md = self.ctx.breakpoint() == Breakpoint::Md;
1159        if is_md {
1160            self.min_w(value)
1161        } else {
1162            self
1163        }
1164    }
1165
1166    /// Minimum width applied only at Lg breakpoint (120-159 cols).
1167    pub fn lg_min_w(self, value: u32) -> Self {
1168        let is_lg = self.ctx.breakpoint() == Breakpoint::Lg;
1169        if is_lg {
1170            self.min_w(value)
1171        } else {
1172            self
1173        }
1174    }
1175
1176    /// Minimum width applied only at Xl breakpoint (>= 160 cols).
1177    pub fn xl_min_w(self, value: u32) -> Self {
1178        let is_xl = self.ctx.breakpoint() == Breakpoint::Xl;
1179        if is_xl {
1180            self.min_w(value)
1181        } else {
1182            self
1183        }
1184    }
1185    pub fn min_w_at(self, bp: Breakpoint, value: u32) -> Self {
1186        if self.ctx.breakpoint() == bp {
1187            self.min_w(value)
1188        } else {
1189            self
1190        }
1191    }
1192
1193    /// Set the maximum width constraint. Shorthand for [`max_width`](Self::max_width).
1194    pub fn max_w(mut self, value: u32) -> Self {
1195        self.constraints.max_width = Some(value);
1196        self
1197    }
1198
1199    /// Maximum width applied only at Xs breakpoint (< 40 cols).
1200    pub fn xs_max_w(self, value: u32) -> Self {
1201        let is_xs = self.ctx.breakpoint() == Breakpoint::Xs;
1202        if is_xs {
1203            self.max_w(value)
1204        } else {
1205            self
1206        }
1207    }
1208
1209    /// Maximum width applied only at Sm breakpoint (40-79 cols).
1210    pub fn sm_max_w(self, value: u32) -> Self {
1211        let is_sm = self.ctx.breakpoint() == Breakpoint::Sm;
1212        if is_sm {
1213            self.max_w(value)
1214        } else {
1215            self
1216        }
1217    }
1218
1219    /// Maximum width applied only at Md breakpoint (80-119 cols).
1220    pub fn md_max_w(self, value: u32) -> Self {
1221        let is_md = self.ctx.breakpoint() == Breakpoint::Md;
1222        if is_md {
1223            self.max_w(value)
1224        } else {
1225            self
1226        }
1227    }
1228
1229    /// Maximum width applied only at Lg breakpoint (120-159 cols).
1230    pub fn lg_max_w(self, value: u32) -> Self {
1231        let is_lg = self.ctx.breakpoint() == Breakpoint::Lg;
1232        if is_lg {
1233            self.max_w(value)
1234        } else {
1235            self
1236        }
1237    }
1238
1239    /// Maximum width applied only at Xl breakpoint (>= 160 cols).
1240    pub fn xl_max_w(self, value: u32) -> Self {
1241        let is_xl = self.ctx.breakpoint() == Breakpoint::Xl;
1242        if is_xl {
1243            self.max_w(value)
1244        } else {
1245            self
1246        }
1247    }
1248    pub fn max_w_at(self, bp: Breakpoint, value: u32) -> Self {
1249        if self.ctx.breakpoint() == bp {
1250            self.max_w(value)
1251        } else {
1252            self
1253        }
1254    }
1255
1256    /// Set the minimum height constraint. Shorthand for [`min_height`](Self::min_height).
1257    pub fn min_h(mut self, value: u32) -> Self {
1258        self.constraints.min_height = Some(value);
1259        self
1260    }
1261
1262    /// Set the maximum height constraint. Shorthand for [`max_height`](Self::max_height).
1263    pub fn max_h(mut self, value: u32) -> Self {
1264        self.constraints.max_height = Some(value);
1265        self
1266    }
1267
1268    /// Set the minimum width constraint in cells.
1269    pub fn min_width(mut self, value: u32) -> Self {
1270        self.constraints.min_width = Some(value);
1271        self
1272    }
1273
1274    /// Set the maximum width constraint in cells.
1275    pub fn max_width(mut self, value: u32) -> Self {
1276        self.constraints.max_width = Some(value);
1277        self
1278    }
1279
1280    /// Set the minimum height constraint in rows.
1281    pub fn min_height(mut self, value: u32) -> Self {
1282        self.constraints.min_height = Some(value);
1283        self
1284    }
1285
1286    /// Set the maximum height constraint in rows.
1287    pub fn max_height(mut self, value: u32) -> Self {
1288        self.constraints.max_height = Some(value);
1289        self
1290    }
1291
1292    /// Set width as a percentage (1-100) of the parent container.
1293    pub fn w_pct(mut self, pct: u8) -> Self {
1294        self.constraints.width_pct = Some(pct.min(100));
1295        self
1296    }
1297
1298    /// Set height as a percentage (1-100) of the parent container.
1299    pub fn h_pct(mut self, pct: u8) -> Self {
1300        self.constraints.height_pct = Some(pct.min(100));
1301        self
1302    }
1303
1304    /// Set all size constraints at once using a [`Constraints`] value.
1305    pub fn constraints(mut self, constraints: Constraints) -> Self {
1306        self.constraints = constraints;
1307        self
1308    }
1309
1310    // ── flex ─────────────────────────────────────────────────────────
1311
1312    /// Set the gap (in cells) between child elements.
1313    pub fn gap(mut self, gap: u32) -> Self {
1314        self.gap = gap;
1315        self
1316    }
1317
1318    /// Gap applied only at Xs breakpoint (< 40 cols).
1319    pub fn xs_gap(self, value: u32) -> Self {
1320        let is_xs = self.ctx.breakpoint() == Breakpoint::Xs;
1321        if is_xs {
1322            self.gap(value)
1323        } else {
1324            self
1325        }
1326    }
1327
1328    /// Gap applied only at Sm breakpoint (40-79 cols).
1329    pub fn sm_gap(self, value: u32) -> Self {
1330        let is_sm = self.ctx.breakpoint() == Breakpoint::Sm;
1331        if is_sm {
1332            self.gap(value)
1333        } else {
1334            self
1335        }
1336    }
1337
1338    /// Gap applied only at Md breakpoint (80-119 cols).
1339    ///
1340    /// # Example
1341    /// ```ignore
1342    /// ui.container().gap(0).md_gap(2).col(|ui| { ... });
1343    /// ```
1344    pub fn md_gap(self, value: u32) -> Self {
1345        let is_md = self.ctx.breakpoint() == Breakpoint::Md;
1346        if is_md {
1347            self.gap(value)
1348        } else {
1349            self
1350        }
1351    }
1352
1353    /// Gap applied only at Lg breakpoint (120-159 cols).
1354    pub fn lg_gap(self, value: u32) -> Self {
1355        let is_lg = self.ctx.breakpoint() == Breakpoint::Lg;
1356        if is_lg {
1357            self.gap(value)
1358        } else {
1359            self
1360        }
1361    }
1362
1363    /// Gap applied only at Xl breakpoint (>= 160 cols).
1364    pub fn xl_gap(self, value: u32) -> Self {
1365        let is_xl = self.ctx.breakpoint() == Breakpoint::Xl;
1366        if is_xl {
1367            self.gap(value)
1368        } else {
1369            self
1370        }
1371    }
1372
1373    pub fn gap_at(self, bp: Breakpoint, value: u32) -> Self {
1374        if self.ctx.breakpoint() == bp {
1375            self.gap(value)
1376        } else {
1377            self
1378        }
1379    }
1380
1381    /// Set the flex-grow factor. `1` means the container expands to fill available space.
1382    pub fn grow(mut self, grow: u16) -> Self {
1383        self.grow = grow;
1384        self
1385    }
1386
1387    /// Grow factor applied only at Xs breakpoint (< 40 cols).
1388    pub fn xs_grow(self, value: u16) -> Self {
1389        let is_xs = self.ctx.breakpoint() == Breakpoint::Xs;
1390        if is_xs {
1391            self.grow(value)
1392        } else {
1393            self
1394        }
1395    }
1396
1397    /// Grow factor applied only at Sm breakpoint (40-79 cols).
1398    pub fn sm_grow(self, value: u16) -> Self {
1399        let is_sm = self.ctx.breakpoint() == Breakpoint::Sm;
1400        if is_sm {
1401            self.grow(value)
1402        } else {
1403            self
1404        }
1405    }
1406
1407    /// Grow factor applied only at Md breakpoint (80-119 cols).
1408    pub fn md_grow(self, value: u16) -> Self {
1409        let is_md = self.ctx.breakpoint() == Breakpoint::Md;
1410        if is_md {
1411            self.grow(value)
1412        } else {
1413            self
1414        }
1415    }
1416
1417    /// Grow factor applied only at Lg breakpoint (120-159 cols).
1418    pub fn lg_grow(self, value: u16) -> Self {
1419        let is_lg = self.ctx.breakpoint() == Breakpoint::Lg;
1420        if is_lg {
1421            self.grow(value)
1422        } else {
1423            self
1424        }
1425    }
1426
1427    /// Grow factor applied only at Xl breakpoint (>= 160 cols).
1428    pub fn xl_grow(self, value: u16) -> Self {
1429        let is_xl = self.ctx.breakpoint() == Breakpoint::Xl;
1430        if is_xl {
1431            self.grow(value)
1432        } else {
1433            self
1434        }
1435    }
1436    pub fn grow_at(self, bp: Breakpoint, value: u16) -> Self {
1437        if self.ctx.breakpoint() == bp {
1438            self.grow(value)
1439        } else {
1440            self
1441        }
1442    }
1443
1444    /// Uniform padding applied only at Xs breakpoint (< 40 cols).
1445    pub fn xs_p(self, value: u32) -> Self {
1446        let is_xs = self.ctx.breakpoint() == Breakpoint::Xs;
1447        if is_xs {
1448            self.p(value)
1449        } else {
1450            self
1451        }
1452    }
1453
1454    /// Uniform padding applied only at Sm breakpoint (40-79 cols).
1455    pub fn sm_p(self, value: u32) -> Self {
1456        let is_sm = self.ctx.breakpoint() == Breakpoint::Sm;
1457        if is_sm {
1458            self.p(value)
1459        } else {
1460            self
1461        }
1462    }
1463
1464    /// Uniform padding applied only at Md breakpoint (80-119 cols).
1465    pub fn md_p(self, value: u32) -> Self {
1466        let is_md = self.ctx.breakpoint() == Breakpoint::Md;
1467        if is_md {
1468            self.p(value)
1469        } else {
1470            self
1471        }
1472    }
1473
1474    /// Uniform padding applied only at Lg breakpoint (120-159 cols).
1475    pub fn lg_p(self, value: u32) -> Self {
1476        let is_lg = self.ctx.breakpoint() == Breakpoint::Lg;
1477        if is_lg {
1478            self.p(value)
1479        } else {
1480            self
1481        }
1482    }
1483
1484    /// Uniform padding applied only at Xl breakpoint (>= 160 cols).
1485    pub fn xl_p(self, value: u32) -> Self {
1486        let is_xl = self.ctx.breakpoint() == Breakpoint::Xl;
1487        if is_xl {
1488            self.p(value)
1489        } else {
1490            self
1491        }
1492    }
1493    pub fn p_at(self, bp: Breakpoint, value: u32) -> Self {
1494        if self.ctx.breakpoint() == bp {
1495            self.p(value)
1496        } else {
1497            self
1498        }
1499    }
1500
1501    // ── alignment ───────────────────────────────────────────────────
1502
1503    /// Set the cross-axis alignment of child elements.
1504    pub fn align(mut self, align: Align) -> Self {
1505        self.align = align;
1506        self
1507    }
1508
1509    /// Center children on the cross axis. Shorthand for `.align(Align::Center)`.
1510    pub fn center(self) -> Self {
1511        self.align(Align::Center)
1512    }
1513
1514    /// Set the main-axis content distribution mode.
1515    pub fn justify(mut self, justify: Justify) -> Self {
1516        self.justify = justify;
1517        self
1518    }
1519
1520    /// Distribute children with equal space between; first at start, last at end.
1521    pub fn space_between(self) -> Self {
1522        self.justify(Justify::SpaceBetween)
1523    }
1524
1525    /// Distribute children with equal space around each child.
1526    pub fn space_around(self) -> Self {
1527        self.justify(Justify::SpaceAround)
1528    }
1529
1530    /// Distribute children with equal space between all children and edges.
1531    pub fn space_evenly(self) -> Self {
1532        self.justify(Justify::SpaceEvenly)
1533    }
1534
1535    // ── title ────────────────────────────────────────────────────────
1536
1537    /// Set a plain-text title rendered in the top border.
1538    pub fn title(self, title: impl Into<String>) -> Self {
1539        self.title_styled(title, Style::new())
1540    }
1541
1542    /// Set a styled title rendered in the top border.
1543    pub fn title_styled(mut self, title: impl Into<String>, style: Style) -> Self {
1544        self.title = Some((title.into(), style));
1545        self
1546    }
1547
1548    // ── internal ─────────────────────────────────────────────────────
1549
1550    /// Set the vertical scroll offset in rows. Used internally by [`Context::scrollable`].
1551    pub fn scroll_offset(mut self, offset: u32) -> Self {
1552        self.scroll_offset = Some(offset);
1553        self
1554    }
1555
1556    fn group_name(mut self, name: String) -> Self {
1557        self.group_name = Some(name);
1558        self
1559    }
1560
1561    /// Finalize the builder as a vertical (column) container.
1562    ///
1563    /// The closure receives a `&mut Context` for rendering children.
1564    /// Returns a [`Response`] with click/hover state for this container.
1565    pub fn col(self, f: impl FnOnce(&mut Context)) -> Response {
1566        self.finish(Direction::Column, f)
1567    }
1568
1569    /// Finalize the builder as a horizontal (row) container.
1570    ///
1571    /// The closure receives a `&mut Context` for rendering children.
1572    /// Returns a [`Response`] with click/hover state for this container.
1573    pub fn row(self, f: impl FnOnce(&mut Context)) -> Response {
1574        self.finish(Direction::Row, f)
1575    }
1576
1577    /// Finalize the builder as an inline text line.
1578    ///
1579    /// Like [`row`](ContainerBuilder::row) but gap is forced to zero
1580    /// for seamless inline rendering of mixed-style text.
1581    pub fn line(mut self, f: impl FnOnce(&mut Context)) -> Response {
1582        self.gap = 0;
1583        self.finish(Direction::Row, f)
1584    }
1585
1586    /// Finalize the builder as a raw-draw region with direct buffer access.
1587    ///
1588    /// The closure receives `(&mut Buffer, Rect)` after layout is computed.
1589    /// Use `buf.set_char()`, `buf.set_string()`, `buf.get_mut()` to write
1590    /// directly into the terminal buffer. Writes outside `rect` are clipped.
1591    ///
1592    /// The closure must be `'static` because it is deferred until after layout.
1593    /// To capture local data, clone or move it into the closure:
1594    /// ```ignore
1595    /// let data = my_vec.clone();
1596    /// ui.container().w(40).h(20).draw(move |buf, rect| {
1597    ///     // use `data` here
1598    /// });
1599    /// ```
1600    pub fn draw(self, f: impl FnOnce(&mut crate::buffer::Buffer, Rect) + 'static) {
1601        let draw_id = self.ctx.deferred_draws.len();
1602        self.ctx.deferred_draws.push(Some(Box::new(f)));
1603        self.ctx.interaction_count += 1;
1604        self.ctx.commands.push(Command::RawDraw {
1605            draw_id,
1606            constraints: self.constraints,
1607            grow: self.grow,
1608            margin: self.margin,
1609        });
1610    }
1611
1612    fn finish(mut self, direction: Direction, f: impl FnOnce(&mut Context)) -> Response {
1613        let interaction_id = self.ctx.interaction_count;
1614        self.ctx.interaction_count += 1;
1615
1616        let in_hovered_group = self
1617            .group_name
1618            .as_ref()
1619            .map(|name| self.ctx.is_group_hovered(name))
1620            .unwrap_or(false)
1621            || self
1622                .ctx
1623                .group_stack
1624                .last()
1625                .map(|name| self.ctx.is_group_hovered(name))
1626                .unwrap_or(false);
1627        let in_focused_group = self
1628            .group_name
1629            .as_ref()
1630            .map(|name| self.ctx.is_group_focused(name))
1631            .unwrap_or(false)
1632            || self
1633                .ctx
1634                .group_stack
1635                .last()
1636                .map(|name| self.ctx.is_group_focused(name))
1637                .unwrap_or(false);
1638
1639        let resolved_bg = if self.ctx.dark_mode {
1640            self.dark_bg_color.or(self.bg_color)
1641        } else {
1642            self.bg_color
1643        };
1644        let resolved_border_style = if self.ctx.dark_mode {
1645            self.dark_border_style.unwrap_or(self.border_style)
1646        } else {
1647            self.border_style
1648        };
1649        let bg_color = if in_hovered_group || in_focused_group {
1650            self.group_hover_bg.or(resolved_bg)
1651        } else {
1652            resolved_bg
1653        };
1654        let border_style = if in_hovered_group || in_focused_group {
1655            self.group_hover_border_style
1656                .unwrap_or(resolved_border_style)
1657        } else {
1658            resolved_border_style
1659        };
1660        let group_name = self.group_name.take();
1661        let is_group_container = group_name.is_some();
1662
1663        if let Some(scroll_offset) = self.scroll_offset {
1664            self.ctx.commands.push(Command::BeginScrollable {
1665                grow: self.grow,
1666                border: self.border,
1667                border_sides: self.border_sides,
1668                border_style,
1669                padding: self.padding,
1670                margin: self.margin,
1671                constraints: self.constraints,
1672                title: self.title,
1673                scroll_offset,
1674            });
1675        } else {
1676            self.ctx.commands.push(Command::BeginContainer {
1677                direction,
1678                gap: self.gap,
1679                align: self.align,
1680                justify: self.justify,
1681                border: self.border,
1682                border_sides: self.border_sides,
1683                border_style,
1684                bg_color,
1685                padding: self.padding,
1686                margin: self.margin,
1687                constraints: self.constraints,
1688                title: self.title,
1689                grow: self.grow,
1690                group_name,
1691            });
1692        }
1693        f(self.ctx);
1694        self.ctx.commands.push(Command::EndContainer);
1695        self.ctx.last_text_idx = None;
1696
1697        if is_group_container {
1698            self.ctx.group_stack.pop();
1699            self.ctx.group_count = self.ctx.group_count.saturating_sub(1);
1700        }
1701
1702        self.ctx.response_for(interaction_id)
1703    }
1704}
1705
1706impl Context {
1707    #[allow(clippy::too_many_arguments)]
1708    pub(crate) fn new(
1709        events: Vec<Event>,
1710        width: u32,
1711        height: u32,
1712        tick: u64,
1713        mut focus_index: usize,
1714        prev_focus_count: usize,
1715        prev_scroll_infos: Vec<(u32, u32)>,
1716        prev_scroll_rects: Vec<Rect>,
1717        prev_hit_map: Vec<Rect>,
1718        prev_group_rects: Vec<(String, Rect)>,
1719        prev_focus_rects: Vec<(usize, Rect)>,
1720        prev_focus_groups: Vec<Option<String>>,
1721        prev_hook_states: Vec<Box<dyn std::any::Any>>,
1722        debug: bool,
1723        theme: Theme,
1724        last_mouse_pos: Option<(u32, u32)>,
1725        prev_modal_active: bool,
1726    ) -> Self {
1727        let consumed = vec![false; events.len()];
1728
1729        let mut mouse_pos = last_mouse_pos;
1730        let mut click_pos = None;
1731        for event in &events {
1732            if let Event::Mouse(mouse) = event {
1733                mouse_pos = Some((mouse.x, mouse.y));
1734                if matches!(mouse.kind, MouseKind::Down(MouseButton::Left)) {
1735                    click_pos = Some((mouse.x, mouse.y));
1736                }
1737            }
1738        }
1739
1740        if let Some((mx, my)) = click_pos {
1741            let mut best: Option<(usize, u64)> = None;
1742            for &(fid, rect) in &prev_focus_rects {
1743                if mx >= rect.x && mx < rect.right() && my >= rect.y && my < rect.bottom() {
1744                    let area = rect.width as u64 * rect.height as u64;
1745                    if best.map_or(true, |(_, ba)| area < ba) {
1746                        best = Some((fid, area));
1747                    }
1748                }
1749            }
1750            if let Some((fid, _)) = best {
1751                focus_index = fid;
1752            }
1753        }
1754
1755        Self {
1756            commands: Vec::new(),
1757            events,
1758            consumed,
1759            should_quit: false,
1760            area_width: width,
1761            area_height: height,
1762            tick,
1763            focus_index,
1764            focus_count: 0,
1765            hook_states: prev_hook_states,
1766            hook_cursor: 0,
1767            prev_focus_count,
1768            scroll_count: 0,
1769            prev_scroll_infos,
1770            prev_scroll_rects,
1771            interaction_count: 0,
1772            prev_hit_map,
1773            group_stack: Vec::new(),
1774            prev_group_rects,
1775            group_count: 0,
1776            prev_focus_groups,
1777            _prev_focus_rects: prev_focus_rects,
1778            mouse_pos,
1779            click_pos,
1780            last_text_idx: None,
1781            overlay_depth: 0,
1782            modal_active: false,
1783            prev_modal_active,
1784            clipboard_text: None,
1785            debug,
1786            theme,
1787            dark_mode: true,
1788            deferred_draws: Vec::new(),
1789        }
1790    }
1791
1792    pub(crate) fn process_focus_keys(&mut self) {
1793        for (i, event) in self.events.iter().enumerate() {
1794            if let Event::Key(key) = event {
1795                if key.kind != KeyEventKind::Press {
1796                    continue;
1797                }
1798                if key.code == KeyCode::Tab && !key.modifiers.contains(KeyModifiers::SHIFT) {
1799                    if self.prev_focus_count > 0 {
1800                        self.focus_index = (self.focus_index + 1) % self.prev_focus_count;
1801                    }
1802                    self.consumed[i] = true;
1803                } else if (key.code == KeyCode::Tab && key.modifiers.contains(KeyModifiers::SHIFT))
1804                    || key.code == KeyCode::BackTab
1805                {
1806                    if self.prev_focus_count > 0 {
1807                        self.focus_index = if self.focus_index == 0 {
1808                            self.prev_focus_count - 1
1809                        } else {
1810                            self.focus_index - 1
1811                        };
1812                    }
1813                    self.consumed[i] = true;
1814                }
1815            }
1816        }
1817    }
1818
1819    /// Render a custom [`Widget`].
1820    ///
1821    /// Calls [`Widget::ui`] with this context and returns the widget's response.
1822    pub fn widget<W: Widget>(&mut self, w: &mut W) -> W::Response {
1823        w.ui(self)
1824    }
1825
1826    /// Wrap child widgets in a panic boundary.
1827    ///
1828    /// If the closure panics, the panic is caught and an error message is
1829    /// rendered in place of the children. The app continues running.
1830    ///
1831    /// # Example
1832    ///
1833    /// ```no_run
1834    /// # slt::run(|ui: &mut slt::Context| {
1835    /// ui.error_boundary(|ui| {
1836    ///     ui.text("risky widget");
1837    /// });
1838    /// # });
1839    /// ```
1840    pub fn error_boundary(&mut self, f: impl FnOnce(&mut Context)) {
1841        self.error_boundary_with(f, |ui, msg| {
1842            ui.styled(
1843                format!("⚠ Error: {msg}"),
1844                Style::new().fg(ui.theme.error).bold(),
1845            );
1846        });
1847    }
1848
1849    /// Like [`error_boundary`](Self::error_boundary), but renders a custom
1850    /// fallback instead of the default error message.
1851    ///
1852    /// The fallback closure receives the panic message as a [`String`].
1853    ///
1854    /// # Example
1855    ///
1856    /// ```no_run
1857    /// # slt::run(|ui: &mut slt::Context| {
1858    /// ui.error_boundary_with(
1859    ///     |ui| {
1860    ///         ui.text("risky widget");
1861    ///     },
1862    ///     |ui, msg| {
1863    ///         ui.text(format!("Recovered from panic: {msg}"));
1864    ///     },
1865    /// );
1866    /// # });
1867    /// ```
1868    pub fn error_boundary_with(
1869        &mut self,
1870        f: impl FnOnce(&mut Context),
1871        fallback: impl FnOnce(&mut Context, String),
1872    ) {
1873        let cmd_count = self.commands.len();
1874        let last_text_idx = self.last_text_idx;
1875
1876        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1877            f(self);
1878        }));
1879
1880        match result {
1881            Ok(()) => {}
1882            Err(panic_info) => {
1883                self.commands.truncate(cmd_count);
1884                self.last_text_idx = last_text_idx;
1885
1886                let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
1887                    (*s).to_string()
1888                } else if let Some(s) = panic_info.downcast_ref::<String>() {
1889                    s.clone()
1890                } else {
1891                    "widget panicked".to_string()
1892                };
1893
1894                fallback(self, msg);
1895            }
1896        }
1897    }
1898
1899    /// Allocate a click/hover interaction slot and return the [`Response`].
1900    ///
1901    /// Use this in custom widgets to detect mouse clicks and hovers without
1902    /// wrapping content in a container. Each call reserves one slot in the
1903    /// hit-test map, so the call order must be stable across frames.
1904    pub fn interaction(&mut self) -> Response {
1905        if (self.modal_active || self.prev_modal_active) && self.overlay_depth == 0 {
1906            return Response::default();
1907        }
1908        let id = self.interaction_count;
1909        self.interaction_count += 1;
1910        self.response_for(id)
1911    }
1912
1913    /// Register a widget as focusable and return whether it currently has focus.
1914    ///
1915    /// Call this in custom widgets that need keyboard focus. Each call increments
1916    /// the internal focus counter, so the call order must be stable across frames.
1917    pub fn register_focusable(&mut self) -> bool {
1918        if (self.modal_active || self.prev_modal_active) && self.overlay_depth == 0 {
1919            return false;
1920        }
1921        let id = self.focus_count;
1922        self.focus_count += 1;
1923        self.commands.push(Command::FocusMarker(id));
1924        if self.prev_focus_count == 0 {
1925            return true;
1926        }
1927        self.focus_index % self.prev_focus_count == id
1928    }
1929
1930    /// Create persistent state that survives across frames.
1931    ///
1932    /// Returns a `State<T>` handle. Access with `state.get(ui)` / `state.get_mut(ui)`.
1933    ///
1934    /// # Rules
1935    /// - Must be called in the same order every frame (like React hooks)
1936    /// - Do NOT call inside if/else that changes between frames
1937    ///
1938    /// # Example
1939    /// ```ignore
1940    /// let count = ui.use_state(|| 0i32);
1941    /// let val = count.get(ui);
1942    /// ui.text(format!("Count: {val}"));
1943    /// if ui.button("+1") {
1944    ///     *count.get_mut(ui) += 1;
1945    /// }
1946    /// ```
1947    pub fn use_state<T: 'static>(&mut self, init: impl FnOnce() -> T) -> State<T> {
1948        let idx = self.hook_cursor;
1949        self.hook_cursor += 1;
1950
1951        if idx >= self.hook_states.len() {
1952            self.hook_states.push(Box::new(init()));
1953        }
1954
1955        State {
1956            idx,
1957            _marker: std::marker::PhantomData,
1958        }
1959    }
1960
1961    /// Memoize a computed value. Recomputes only when `deps` changes.
1962    ///
1963    /// # Example
1964    /// ```ignore
1965    /// let doubled = ui.use_memo(&count, |c| c * 2);
1966    /// ui.text(format!("Doubled: {doubled}"));
1967    /// ```
1968    pub fn use_memo<T: 'static, D: PartialEq + Clone + 'static>(
1969        &mut self,
1970        deps: &D,
1971        compute: impl FnOnce(&D) -> T,
1972    ) -> &T {
1973        let idx = self.hook_cursor;
1974        self.hook_cursor += 1;
1975
1976        let should_recompute = if idx >= self.hook_states.len() {
1977            true
1978        } else {
1979            let (stored_deps, _) = self.hook_states[idx]
1980                .downcast_ref::<(D, T)>()
1981                .expect("use_memo type mismatch");
1982            stored_deps != deps
1983        };
1984
1985        if should_recompute {
1986            let value = compute(deps);
1987            let slot = Box::new((deps.clone(), value));
1988            if idx < self.hook_states.len() {
1989                self.hook_states[idx] = slot;
1990            } else {
1991                self.hook_states.push(slot);
1992            }
1993        }
1994
1995        let (_, value) = self.hook_states[idx]
1996            .downcast_ref::<(D, T)>()
1997            .expect("use_memo type mismatch");
1998        value
1999    }
2000}
2001
2002mod widgets_display;
2003mod widgets_input;
2004mod widgets_viz;
2005
2006#[inline]
2007fn byte_index_for_char(value: &str, char_index: usize) -> usize {
2008    if char_index == 0 {
2009        return 0;
2010    }
2011    value
2012        .char_indices()
2013        .nth(char_index)
2014        .map_or(value.len(), |(idx, _)| idx)
2015}
2016
2017fn format_token_count(count: usize) -> String {
2018    if count >= 1_000_000 {
2019        format!("{:.1}M", count as f64 / 1_000_000.0)
2020    } else if count >= 1_000 {
2021        format!("{:.1}k", count as f64 / 1_000.0)
2022    } else {
2023        format!("{count}")
2024    }
2025}
2026
2027fn format_table_row(cells: &[String], widths: &[u32], separator: &str) -> String {
2028    let mut parts: Vec<String> = Vec::new();
2029    for (i, width) in widths.iter().enumerate() {
2030        let cell = cells.get(i).map(String::as_str).unwrap_or("");
2031        let cell_width = UnicodeWidthStr::width(cell) as u32;
2032        let padding = (*width).saturating_sub(cell_width) as usize;
2033        parts.push(format!("{cell}{}", " ".repeat(padding)));
2034    }
2035    parts.join(separator)
2036}
2037
2038fn format_compact_number(value: f64) -> String {
2039    if value.fract().abs() < f64::EPSILON {
2040        return format!("{value:.0}");
2041    }
2042
2043    let mut s = format!("{value:.2}");
2044    while s.contains('.') && s.ends_with('0') {
2045        s.pop();
2046    }
2047    if s.ends_with('.') {
2048        s.pop();
2049    }
2050    s
2051}
2052
2053fn center_text(text: &str, width: usize) -> String {
2054    let text_width = UnicodeWidthStr::width(text);
2055    if text_width >= width {
2056        return text.to_string();
2057    }
2058
2059    let total = width - text_width;
2060    let left = total / 2;
2061    let right = total - left;
2062    format!("{}{}{}", " ".repeat(left), text, " ".repeat(right))
2063}
2064
2065struct TextareaVLine {
2066    logical_row: usize,
2067    char_start: usize,
2068    char_count: usize,
2069}
2070
2071fn textarea_build_visual_lines(lines: &[String], wrap_width: u32) -> Vec<TextareaVLine> {
2072    let mut out = Vec::new();
2073    for (row, line) in lines.iter().enumerate() {
2074        if line.is_empty() || wrap_width == u32::MAX {
2075            out.push(TextareaVLine {
2076                logical_row: row,
2077                char_start: 0,
2078                char_count: line.chars().count(),
2079            });
2080            continue;
2081        }
2082        let mut seg_start = 0usize;
2083        let mut seg_chars = 0usize;
2084        let mut seg_width = 0u32;
2085        for (idx, ch) in line.chars().enumerate() {
2086            let cw = UnicodeWidthChar::width(ch).unwrap_or(0) as u32;
2087            if seg_width + cw > wrap_width && seg_chars > 0 {
2088                out.push(TextareaVLine {
2089                    logical_row: row,
2090                    char_start: seg_start,
2091                    char_count: seg_chars,
2092                });
2093                seg_start = idx;
2094                seg_chars = 0;
2095                seg_width = 0;
2096            }
2097            seg_chars += 1;
2098            seg_width += cw;
2099        }
2100        out.push(TextareaVLine {
2101            logical_row: row,
2102            char_start: seg_start,
2103            char_count: seg_chars,
2104        });
2105    }
2106    out
2107}
2108
2109fn textarea_logical_to_visual(
2110    vlines: &[TextareaVLine],
2111    logical_row: usize,
2112    logical_col: usize,
2113) -> (usize, usize) {
2114    for (i, vl) in vlines.iter().enumerate() {
2115        if vl.logical_row != logical_row {
2116            continue;
2117        }
2118        let seg_end = vl.char_start + vl.char_count;
2119        if logical_col >= vl.char_start && logical_col < seg_end {
2120            return (i, logical_col - vl.char_start);
2121        }
2122        if logical_col == seg_end {
2123            let is_last_seg = vlines
2124                .get(i + 1)
2125                .map_or(true, |next| next.logical_row != logical_row);
2126            if is_last_seg {
2127                return (i, logical_col - vl.char_start);
2128            }
2129        }
2130    }
2131    (vlines.len().saturating_sub(1), 0)
2132}
2133
2134fn textarea_visual_to_logical(
2135    vlines: &[TextareaVLine],
2136    visual_row: usize,
2137    visual_col: usize,
2138) -> (usize, usize) {
2139    if let Some(vl) = vlines.get(visual_row) {
2140        let logical_col = vl.char_start + visual_col.min(vl.char_count);
2141        (vl.logical_row, logical_col)
2142    } else {
2143        (0, 0)
2144    }
2145}
2146
2147fn open_url(url: &str) -> std::io::Result<()> {
2148    #[cfg(target_os = "macos")]
2149    {
2150        std::process::Command::new("open").arg(url).spawn()?;
2151    }
2152    #[cfg(target_os = "linux")]
2153    {
2154        std::process::Command::new("xdg-open").arg(url).spawn()?;
2155    }
2156    #[cfg(target_os = "windows")]
2157    {
2158        std::process::Command::new("cmd")
2159            .args(["/c", "start", "", url])
2160            .spawn()?;
2161    }
2162    Ok(())
2163}