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