Skip to main content

telex/
view.rs

1use std::rc::Rc;
2
3/// Callback type for event handlers (no arguments).
4pub type Callback = Rc<dyn Fn()>;
5
6/// Alignment along the main axis (justify).
7#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
8pub enum Justify {
9    /// Items at the start (default).
10    #[default]
11    Start,
12    /// Items at the end.
13    End,
14    /// Items centered.
15    Center,
16    /// Items spread with space between them.
17    SpaceBetween,
18    /// Items spread with space around them.
19    SpaceAround,
20}
21
22/// Alignment along the cross axis (align).
23#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
24pub enum Align {
25    /// Items at the start.
26    Start,
27    /// Items at the end.
28    End,
29    /// Items centered.
30    Center,
31    /// Items stretch to fill (default).
32    #[default]
33    Stretch,
34}
35
36/// Layout mode for stack containers.
37///
38/// This enum allows switching between different layout algorithms.
39/// Currently only Flex is implemented, but this provides the hook
40/// for future layout experiments (e.g., percentage-based layouts).
41#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
42pub enum LayoutMode {
43    /// Flex-based layout (default).
44    /// Children with flex > 0 share remaining space proportionally.
45    /// Children with flex = 0 use their intrinsic/min size.
46    #[default]
47    Flex,
48    // Future: Percent - children specify exact percentages
49    // Future: Grid - CSS grid-like layout
50}
51
52/// Callback type for selection events (receives selected index).
53pub type SelectCallback = Rc<dyn Fn(usize)>;
54
55/// Callback type for text change events (receives new text).
56pub type ChangeCallback = Rc<dyn Fn(String)>;
57
58/// Callback type for toggle events (receives new state).
59pub type ToggleCallback = Rc<dyn Fn(bool)>;
60
61/// Callback type for cursor position change events (receives line, column).
62pub type CursorChangeCallback = Rc<dyn Fn(usize, usize)>;
63
64/// Callback type for cursor position change events in single-line inputs (receives position).
65pub type CursorPosCallback = Rc<dyn Fn(usize)>;
66
67/// Path to a node in a tree (indices at each level).
68pub type TreePath = Vec<usize>;
69
70/// Callback type for tree selection events (receives path to selected item).
71pub type TreeSelectCallback = Rc<dyn Fn(TreePath)>;
72
73/// Callback type for tree activation events (receives path to activated item).
74pub type TreeActivateCallback = Rc<dyn Fn(TreePath)>;
75
76/// Callback type for table sort events (receives column index and ascending flag).
77pub type SortCallback = Rc<dyn Fn(usize, bool)>;
78
79/// Callback type for table row activation events (receives row index).
80pub type RowActivateCallback = Rc<dyn Fn(usize)>;
81
82/// Callback type for command execution events (receives command ID).
83pub type CommandCallback = Rc<dyn Fn(&'static str)>;
84
85/// Callback type for canvas drawing (receives mutable draw context).
86pub type CanvasDrawCallback = Rc<dyn Fn(&mut crate::canvas::DrawContext)>;
87
88/// The core view type - a node in the UI tree.
89#[derive(Clone)]
90pub enum View {
91    /// A text node displaying a string.
92    Text(TextNode),
93    /// A vertical stack of child views.
94    VStack(VStackNode),
95    /// A horizontal stack of child views.
96    HStack(HStackNode),
97    /// A clickable button.
98    Button(ButtonNode),
99    /// A container with optional border, padding, and flex sizing.
100    Box(BoxNode),
101    /// Flexible space that expands to fill available space.
102    Spacer(SpacerNode),
103    /// A selectable list of items.
104    List(ListNode),
105    /// A single-line text input.
106    TextInput(TextInputNode),
107    /// A multi-line text area.
108    TextArea(TextAreaNode),
109    /// A checkbox (toggle).
110    Checkbox(CheckboxNode),
111    /// A group of radio buttons (mutually exclusive options).
112    RadioGroup(RadioGroupNode),
113    /// A modal dialog overlay.
114    Modal(ModalNode),
115    /// A split pane container with two resizable panels.
116    Split(SplitNode),
117    /// A tabbed interface container.
118    Tabs(TabsNode),
119    /// A hierarchical tree view.
120    Tree(TreeNode),
121    /// A data table with columns and rows.
122    Table(TableNode),
123    /// A progress bar showing completion status.
124    ProgressBar(ProgressBarNode),
125    /// A status bar displayed at the bottom of the screen.
126    StatusBar(StatusBarNode),
127    /// A command palette overlay for searching and executing commands.
128    CommandPalette(CommandPaletteNode),
129    /// A horizontal menu bar with dropdown menus.
130    MenuBar(MenuBarNode),
131    /// A container for toast notifications.
132    ToastContainer(ToastContainerNode),
133    /// A form container with validation support.
134    Form(FormNode),
135    /// A form field with label and error display.
136    FormField(FormFieldNode),
137    /// A pixel-level canvas using Kitty graphics protocol.
138    Canvas(CanvasNode),
139    /// An image display using Kitty graphics protocol.
140    Image(ImageNode),
141    /// An interactive PTY terminal emulator.
142    Terminal(TerminalNode),
143    /// An empty placeholder.
144    Empty,
145}
146
147impl std::fmt::Debug for View {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        match self {
150            View::Text(n) => f.debug_tuple("Text").field(n).finish(),
151            View::VStack(n) => f.debug_tuple("VStack").field(n).finish(),
152            View::HStack(n) => f.debug_tuple("HStack").field(n).finish(),
153            View::Button(n) => f
154                .debug_struct("Button")
155                .field("label", &n.label)
156                .field("on_press", &"<callback>")
157                .finish(),
158            View::Box(n) => f.debug_tuple("Box").field(n).finish(),
159            View::Spacer(n) => f.debug_tuple("Spacer").field(n).finish(),
160            View::List(n) => f
161                .debug_struct("List")
162                .field("items", &n.items.len())
163                .field("selected", &n.selected)
164                .finish(),
165            View::TextInput(n) => f
166                .debug_struct("TextInput")
167                .field("value", &n.value)
168                .finish(),
169            View::TextArea(n) => f
170                .debug_struct("TextArea")
171                .field("value", &n.value)
172                .field("cursor", &(n.cursor_line, n.cursor_col))
173                .finish(),
174            View::Checkbox(n) => f
175                .debug_struct("Checkbox")
176                .field("checked", &n.checked)
177                .field("label", &n.label)
178                .finish(),
179            View::RadioGroup(n) => f
180                .debug_struct("RadioGroup")
181                .field("selected", &n.selected)
182                .field("options", &n.options)
183                .finish(),
184            View::Modal(n) => f
185                .debug_struct("Modal")
186                .field("visible", &n.visible)
187                .field("title", &n.title)
188                .finish(),
189            View::Split(n) => f
190                .debug_struct("Split")
191                .field("orientation", &n.orientation)
192                .field("ratio", &n.ratio)
193                .finish(),
194            View::Tabs(n) => f
195                .debug_struct("Tabs")
196                .field("tabs", &n.tabs)
197                .field("active", &n.active)
198                .finish(),
199            View::Tree(n) => f
200                .debug_struct("Tree")
201                .field("items", &n.items.len())
202                .field("selected", &n.selected)
203                .finish(),
204            View::Table(n) => f
205                .debug_struct("Table")
206                .field("columns", &n.columns.len())
207                .field("rows", &n.rows.len())
208                .field("selected", &n.selected)
209                .finish(),
210            View::ProgressBar(n) => f
211                .debug_struct("ProgressBar")
212                .field("value", &n.value)
213                .field("label", &n.label)
214                .finish(),
215            View::StatusBar(n) => f
216                .debug_struct("StatusBar")
217                .field("left", &n.left)
218                .field("center", &n.center)
219                .field("right", &n.right)
220                .finish(),
221            View::CommandPalette(n) => f
222                .debug_struct("CommandPalette")
223                .field("visible", &n.visible)
224                .field("query", &n.query)
225                .field("selected", &n.selected)
226                .finish(),
227            View::MenuBar(n) => f
228                .debug_struct("MenuBar")
229                .field("menus", &n.menus.len())
230                .field("active_menu", &n.active_menu)
231                .finish(),
232            View::ToastContainer(n) => f
233                .debug_struct("ToastContainer")
234                .field("toasts", &n.toasts.len())
235                .finish(),
236            View::Form(n) => f
237                .debug_struct("Form")
238                .field("children", &n.children.len())
239                .finish(),
240            View::FormField(n) => f
241                .debug_struct("FormField")
242                .field("name", &n.name)
243                .field("label", &n.label)
244                .finish(),
245            View::Canvas(n) => f
246                .debug_struct("Canvas")
247                .field("width", &n.pixel_width)
248                .field("height", &n.pixel_height)
249                .finish(),
250            View::Image(n) => f
251                .debug_struct("Image")
252                .field("has_data", &n.source.is_some())
253                .finish(),
254            View::Terminal(n) => f
255                .debug_struct("Terminal")
256                .field("rows", &n.rows)
257                .field("cols", &n.cols)
258                .field("border", &n.border)
259                .finish(),
260            View::Empty => write!(f, "Empty"),
261        }
262    }
263}
264
265impl View {
266    /// Create a text view with the given content.
267    pub fn text(content: impl Into<String>) -> Self {
268        View::Text(TextNode {
269            content: content.into(),
270            color: None,
271            bg_color: None,
272            bold: false,
273            italic: false,
274            underline: false,
275            dim: false,
276        })
277    }
278
279    /// Create a styled text builder.
280    pub fn styled_text(content: impl Into<String>) -> TextBuilder {
281        TextBuilder::new(content)
282    }
283
284    /// Create a vertical stack builder.
285    pub fn vstack() -> VStackBuilder {
286        VStackBuilder::new()
287    }
288
289    /// Create a horizontal stack builder.
290    pub fn hstack() -> HStackBuilder {
291        HStackBuilder::new()
292    }
293
294    /// Create a button builder.
295    pub fn button() -> ButtonBuilder {
296        ButtonBuilder::new()
297    }
298
299    /// Create a box builder.
300    pub fn boxed() -> BoxBuilder {
301        BoxBuilder::new()
302    }
303
304    /// Create a spacer with flex factor 1 (expands to fill available space).
305    pub fn spacer() -> Self {
306        View::Spacer(SpacerNode { flex: 1, height: 0 })
307    }
308
309    /// Create a spacer with a specific flex factor.
310    pub fn spacer_flex(flex: u16) -> Self {
311        View::Spacer(SpacerNode { flex, height: 0 })
312    }
313
314    /// Create a fixed-height gap (blank lines).
315    ///
316    /// Unlike `spacer()` which expands to fill space, `gap()` is a fixed height.
317    ///
318    /// # Example
319    /// ```rust,ignore
320    /// View::vstack()
321    ///     .child(View::text("Header"))
322    ///     .child(View::gap(1))  // One blank line
323    ///     .child(View::text("Content"))
324    ///     .build()
325    /// ```
326    pub fn gap(height: u16) -> Self {
327        View::Spacer(SpacerNode { flex: 0, height })
328    }
329
330    /// Create a list builder.
331    pub fn list() -> ListBuilder {
332        ListBuilder::new()
333    }
334
335    /// Create a text input builder.
336    pub fn text_input() -> TextInputBuilder {
337        TextInputBuilder::new()
338    }
339
340    /// Create a checkbox builder.
341    pub fn checkbox() -> CheckboxBuilder {
342        CheckboxBuilder::new()
343    }
344
345    /// Create a radio group builder.
346    pub fn radio_group() -> RadioGroupBuilder {
347        RadioGroupBuilder::new()
348    }
349
350    /// Create a text area builder.
351    pub fn text_area() -> TextAreaBuilder {
352        TextAreaBuilder::new()
353    }
354
355    /// Create a modal dialog builder.
356    pub fn modal() -> ModalBuilder {
357        ModalBuilder::new()
358    }
359
360    /// Create a split pane builder.
361    pub fn split() -> SplitBuilder {
362        SplitBuilder::new()
363    }
364
365    /// Create a tabs builder.
366    pub fn tabs() -> TabsBuilder {
367        TabsBuilder::new()
368    }
369
370    /// Create a tree builder.
371    pub fn tree() -> TreeBuilder {
372        TreeBuilder::new()
373    }
374
375    /// Create a table builder.
376    pub fn table() -> TableBuilder {
377        TableBuilder::new()
378    }
379
380    /// Create a progress bar builder.
381    pub fn progress_bar() -> ProgressBarBuilder {
382        ProgressBarBuilder::new()
383    }
384
385    /// Create a status bar builder.
386    pub fn status_bar() -> StatusBarBuilder {
387        StatusBarBuilder::new()
388    }
389
390    /// Create a command palette builder.
391    pub fn command_palette() -> CommandPaletteBuilder {
392        CommandPaletteBuilder::new()
393    }
394
395    /// Create a menu bar builder.
396    pub fn menu_bar() -> MenuBarBuilder {
397        MenuBarBuilder::new()
398    }
399
400    /// Create a toast container builder.
401    pub fn toast_container() -> ToastContainerBuilder {
402        ToastContainerBuilder::new()
403    }
404
405    /// Create a form builder.
406    pub fn form() -> FormBuilder {
407        FormBuilder::new()
408    }
409
410    /// Create a form field builder.
411    pub fn form_field(name: impl Into<String>) -> FormFieldBuilder {
412        FormFieldBuilder::new(name)
413    }
414
415    /// Create a canvas builder for pixel-level drawing.
416    ///
417    /// **Experimental Feature**
418    ///
419    /// Canvas uses the Kitty graphics protocol for actual pixel rendering.
420    /// Requires a compatible terminal (Kitty, Ghostty, WezTerm).
421    /// Other terminals will show a placeholder message.
422    pub fn canvas() -> CanvasBuilder {
423        CanvasBuilder::new()
424    }
425
426    /// Create an image builder for displaying images.
427    ///
428    /// **Experimental Feature**
429    ///
430    /// Displays PNG, JPEG, or GIF images using the Kitty graphics protocol.
431    /// GIF animations are handled natively by Kitty.
432    /// Requires a compatible terminal (Kitty, Ghostty, WezTerm).
433    /// Other terminals will show alt text or a placeholder message.
434    pub fn image() -> ImageBuilder {
435        ImageBuilder::new()
436    }
437
438    /// Create a terminal builder for interactive PTY terminal emulation.
439    ///
440    /// **Status: Experimental Preview**
441    ///
442    /// Supports running shell commands (bash, vim, htop, etc.) with full
443    /// keyboard input and ANSI color/style rendering.
444    ///
445    /// # Known Limitations
446    ///
447    /// - No scrollback buffer
448    /// - No terminal resize support
449    /// - No copy/paste
450    /// - No mouse input
451    ///
452    /// Use for prototyping and experimentation. Breaking changes likely.
453    ///
454    /// # Example
455    /// ```rust,ignore
456    /// let terminal = cx.use_terminal();
457    /// if !terminal.is_started() {
458    ///     terminal.spawn("bash", &[], 80, 24);
459    /// }
460    /// View::terminal().handle(terminal).build()
461    /// ```
462    pub fn terminal() -> TerminalBuilder {
463        TerminalBuilder::new()
464    }
465
466    /// Create an empty view.
467    pub fn empty() -> Self {
468        View::Empty
469    }
470
471    /// Check if this view is focusable.
472    pub fn is_focusable(&self) -> bool {
473        match self {
474            View::Button(_) => true,
475            View::Box(node) => node.scroll,
476            View::List(_) => true,
477            View::TextInput(_) => true,
478            View::TextArea(_) => true,
479            View::Checkbox(_) => true,
480            View::RadioGroup(_) => true, // RadioGroup is focusable for option selection
481            View::Split(_) => false,     // Split is a layout container, not focusable itself
482            View::Tabs(_) => true,       // Tabs is focusable for tab switching
483            View::Tree(_) => true,       // Tree is focusable for navigation
484            View::Table(_) => true,      // Table is focusable for row selection
485            View::CommandPalette(_) => true, // Command palette captures all input when visible
486            View::MenuBar(_) => true,    // Menu bar is focusable for navigation
487            View::FormField(_) => true,  // Form fields are focusable for input
488            View::Terminal(_) => true,   // Terminal is focusable for PTY input
489            _ => false,
490        }
491    }
492
493    /// Get the flex factor of this view (for layout).
494    pub fn flex(&self) -> u16 {
495        match self {
496            View::Box(n) => n.flex,
497            View::Spacer(n) => n.flex,
498            _ => 0,
499        }
500    }
501
502    /// Get the minimum height constraint, if any.
503    pub fn min_height(&self) -> Option<u16> {
504        match self {
505            View::Box(n) => n.min_height,
506            _ => None,
507        }
508    }
509
510    /// Get the maximum height constraint, if any.
511    pub fn max_height(&self) -> Option<u16> {
512        match self {
513            View::Box(n) => n.max_height,
514            _ => None,
515        }
516    }
517
518    /// Get the minimum width constraint, if any.
519    pub fn min_width(&self) -> Option<u16> {
520        match self {
521            View::Box(n) => n.min_width,
522            _ => None,
523        }
524    }
525
526    /// Get the maximum width constraint, if any.
527    pub fn max_width(&self) -> Option<u16> {
528        match self {
529            View::Box(n) => n.max_width,
530            _ => None,
531        }
532    }
533
534    /// Calculate the intrinsic (natural) height of this view based on its content.
535    /// Returns None for views that have no intrinsic height (flexible).
536    pub fn intrinsic_height(&self) -> Option<u16> {
537        match self {
538            View::Text(n) => Some(n.content.lines().count().max(1) as u16),
539            View::Button(_) => Some(1),
540            View::Box(n) => {
541                let border = if n.border { 2 } else { 0 };
542                let padding = n.padding * 2;
543                let inner = n
544                    .child
545                    .as_ref()
546                    .and_then(|c| c.intrinsic_height())
547                    .unwrap_or(0);
548                Some(inner + border + padding)
549            }
550            View::VStack(n) => {
551                if n.children.is_empty() {
552                    return Some(0);
553                }
554                let spacing = if n.children.len() > 1 {
555                    n.spacing * (n.children.len() as u16 - 1)
556                } else {
557                    0
558                };
559                let children_height: u16 =
560                    n.children.iter().filter_map(|c| c.intrinsic_height()).sum();
561                Some(children_height + spacing)
562            }
563            View::HStack(n) => {
564                // HStack height is max of children heights
565                n.children
566                    .iter()
567                    .filter_map(|c| c.intrinsic_height())
568                    .max()
569                    .or(Some(1))
570            }
571            View::List(n) => Some(n.items.len().max(1) as u16),
572            View::TextInput(_) => Some(1),
573            View::TextArea(n) => Some(n.rows),
574            View::Checkbox(_) => Some(1),
575            View::RadioGroup(n) => Some(n.options.len() as u16), // One row per option
576            View::Modal(_) => None, // Modal is an overlay, no intrinsic size
577            View::Spacer(n) => {
578                if n.flex == 0 {
579                    Some(n.height) // Fixed-height gap
580                } else {
581                    None // Flexible spacer expands
582                }
583            }
584            View::Split(_) => None,          // Split fills available space
585            View::Tabs(_) => None,           // Tabs fills available space
586            View::Tree(_) => None,           // Tree fills available space
587            View::Table(_) => None,          // Table fills available space
588            View::ProgressBar(_) => Some(1), // Progress bar is 1 row tall
589            View::StatusBar(_) => Some(1),   // Status bar is 1 row tall
590            View::CommandPalette(_) => None, // Command palette is an overlay
591            View::MenuBar(_) => Some(1),     // Menu bar is 1 row tall
592            View::ToastContainer(_) => None, // Toast container is an overlay
593            View::Form(n) => {
594                // Form height is sum of children
595                let children_height: u16 =
596                    n.children.iter().filter_map(|c| c.intrinsic_height()).sum();
597                Some(children_height)
598            }
599            View::FormField(n) => {
600                // Label (1) + input (1) + error (1 if present) = 2-3 rows
601                let base_height = 2u16; // Label + input
602                let error_height = if n.error.is_some() { 1 } else { 0 };
603                Some(base_height + error_height)
604            }
605            View::Canvas(n) => {
606                // Canvas height in cells (pixels / cell_height)
607                // Approximate: assume ~20 pixels per cell height
608                Some((n.pixel_height / 20).max(1))
609            }
610            View::Image(n) => {
611                // Image height based on detected dimensions or default
612                n.cell_height.or(Some(5))
613            }
614            View::Terminal(n) => {
615                // Terminal height is rows + border
616                let border = if n.border { 2 } else { 0 };
617                Some(n.rows as u16 + border)
618            }
619            View::Empty => Some(0),
620        }
621    }
622
623    /// Calculate the intrinsic (natural) width of this view based on its content.
624    /// Returns None for views that have no intrinsic width (flexible).
625    pub fn intrinsic_width(&self) -> Option<u16> {
626        match self {
627            View::Text(n) => {
628                let max_line_width = n.content.lines().map(|l| l.len()).max().unwrap_or(0);
629                Some(max_line_width as u16)
630            }
631            View::Button(n) => {
632                // [ label ] = 4 chars for brackets + spaces + label
633                Some(n.label.len() as u16 + 4)
634            }
635            View::Box(n) => {
636                let border = if n.border { 2 } else { 0 };
637                let padding = n.padding * 2;
638                let inner = n
639                    .child
640                    .as_ref()
641                    .and_then(|c| c.intrinsic_width())
642                    .unwrap_or(0);
643                Some(inner + border + padding)
644            }
645            View::VStack(n) => {
646                // VStack width is max of children widths
647                n.children
648                    .iter()
649                    .filter_map(|c| c.intrinsic_width())
650                    .max()
651                    .or(Some(1))
652            }
653            View::HStack(n) => {
654                if n.children.is_empty() {
655                    return Some(0);
656                }
657                let spacing = if n.children.len() > 1 {
658                    n.spacing * (n.children.len() as u16 - 1)
659                } else {
660                    0
661                };
662                let children_width: u16 =
663                    n.children.iter().filter_map(|c| c.intrinsic_width()).sum();
664                Some(children_width + spacing)
665            }
666            View::List(n) => {
667                // "> " prefix + max item length
668                let max_item = n.items.iter().map(|i| i.len()).max().unwrap_or(0);
669                Some(max_item as u16 + 2)
670            }
671            View::TextInput(_) => {
672                // TextInput should be sized by its container, not by content
673                // Return None to allow flex/container sizing with internal scrolling
674                None
675            }
676            View::TextArea(_) => {
677                // TextArea should be sized by its container, not by content
678                // Return None to allow flex/container sizing with internal scrolling
679                None
680            }
681            View::Checkbox(n) => {
682                // "[x] " + label
683                Some(n.label.len() as u16 + 4)
684            }
685            View::RadioGroup(n) => {
686                // "(o) " + longest option
687                let max_option = n.options.iter().map(|o| o.len()).max().unwrap_or(0);
688                Some(max_option as u16 + 4)
689            }
690            View::Modal(_) => None, // Modal is an overlay
691            View::Spacer(n) => {
692                if n.flex == 0 {
693                    Some(0) // Fixed-height gap has no width requirement
694                } else {
695                    None // Flexible spacer expands
696                }
697            }
698            View::Split(_) => None, // Split fills available space
699            View::Tabs(_) => None,  // Tabs fills available space
700            View::Tree(_) => None,  // Tree fills available space
701            View::Table(_) => None, // Table fills available space
702            View::ProgressBar(n) => {
703                // Label + bar width + percentage
704                let label_width = n.label.as_ref().map(|l| l.len() + 1).unwrap_or(0);
705                let bar_width = n.width.unwrap_or(10) as usize;
706                let percentage_width = if n.show_percentage { 5 } else { 0 };
707                Some((label_width + bar_width + percentage_width) as u16)
708            }
709            View::StatusBar(n) => {
710                // Left + center + right sections
711                let left_width = n.left.len();
712                let center_width = n.center.as_ref().map(|c| c.len()).unwrap_or(0);
713                let right_width = n.right.as_ref().map(|r| r.len()).unwrap_or(0);
714                // Minimum spacing between sections
715                let spacing = if center_width > 0 || right_width > 0 {
716                    2
717                } else {
718                    0
719                };
720                Some((left_width + center_width + right_width + spacing) as u16)
721            }
722            View::CommandPalette(_) => None, // Command palette is an overlay
723            View::MenuBar(n) => {
724                // Sum of menu labels + separators
725                let labels_width: usize = n.menus.iter().map(|m| m.label.len() + 3).sum(); // " Label "
726                Some(labels_width as u16)
727            }
728            View::ToastContainer(_) => None, // Toast container is an overlay
729            View::Form(n) => {
730                // Form width is max of children
731                n.children.iter().filter_map(|c| c.intrinsic_width()).max()
732            }
733            View::FormField(n) => {
734                // Width is max of label and input
735                let label_width = n.label.len() as u16;
736                let input_width = 20u16; // Default minimum input width
737                Some(label_width.max(input_width))
738            }
739            View::Canvas(n) => {
740                // Canvas width in cells (pixels / cell_width)
741                // Approximate: assume ~10 pixels per cell width
742                Some((n.pixel_width / 10).max(1))
743            }
744            View::Image(n) => {
745                // Image width based on detected dimensions or default
746                n.cell_width.or(Some(10))
747            }
748            View::Terminal(n) => {
749                // Terminal width is cols + border
750                let border = if n.border { 2 } else { 0 };
751                Some(n.cols as u16 + border)
752            }
753            View::Empty => Some(0),
754        }
755    }
756}
757
758/// Orientation for split panes.
759#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
760pub enum Orientation {
761    /// Panes side by side: [first | second]
762    #[default]
763    Horizontal,
764    /// Panes stacked: [first] / [second]
765    Vertical,
766}
767
768/// Position of the tab bar.
769#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
770pub enum TabPosition {
771    /// Tab bar at the top (default).
772    #[default]
773    Top,
774    /// Tab bar at the bottom.
775    Bottom,
776}
777
778/// A text node containing string content with optional styling.
779#[derive(Debug, Clone)]
780pub struct TextNode {
781    pub content: String,
782    pub color: Option<crossterm::style::Color>,
783    pub bg_color: Option<crossterm::style::Color>,
784    pub bold: bool,
785    pub italic: bool,
786    pub underline: bool,
787    pub dim: bool,
788}
789
790/// A vertical stack container.
791#[derive(Debug, Clone)]
792pub struct VStackNode {
793    pub children: Vec<View>,
794    /// Spacing between children (in rows).
795    pub spacing: u16,
796    /// Justify content along main axis (vertical).
797    pub justify: Justify,
798    /// Align items along cross axis (horizontal).
799    pub align: Align,
800    /// Layout algorithm to use.
801    pub layout_mode: LayoutMode,
802}
803
804/// A horizontal stack container.
805#[derive(Debug, Clone)]
806pub struct HStackNode {
807    pub children: Vec<View>,
808    /// Spacing between children (in columns).
809    pub spacing: u16,
810    /// Justify content along main axis (horizontal).
811    pub justify: Justify,
812    /// Align items along cross axis (vertical).
813    pub align: Align,
814    /// Layout algorithm to use.
815    pub layout_mode: LayoutMode,
816}
817
818/// A container with optional border, padding, and flex sizing.
819#[derive(Debug, Clone)]
820pub struct BoxNode {
821    /// The child view inside the box.
822    pub child: Option<std::boxed::Box<View>>,
823    /// Whether to draw a border around the box.
824    pub border: bool,
825    /// Padding inside the box (all sides).
826    pub padding: u16,
827    /// Flex factor for layout (0 = fixed size, >0 = flexible).
828    pub flex: u16,
829    /// Whether this box is scrollable.
830    pub scroll: bool,
831    /// Automatically scroll to show bottom content (for chat-like UIs).
832    pub auto_scroll_bottom: bool,
833    /// Whether this box participates in focus navigation (default: true for scrollable boxes).
834    pub focusable: bool,
835    /// Minimum width constraint.
836    pub min_width: Option<u16>,
837    /// Maximum width constraint.
838    pub max_width: Option<u16>,
839    /// Minimum height constraint.
840    pub min_height: Option<u16>,
841    /// Maximum height constraint.
842    pub max_height: Option<u16>,
843}
844
845/// Flexible space that expands to fill available space.
846#[derive(Debug, Clone)]
847pub struct SpacerNode {
848    /// Flex factor (default 1). If 0, uses fixed height.
849    pub flex: u16,
850    /// Fixed height in rows (only used when flex is 0).
851    pub height: u16,
852}
853
854/// A button node.
855#[derive(Clone)]
856pub struct ButtonNode {
857    pub label: String,
858    pub on_press: Option<Callback>,
859}
860
861/// A selectable list node.
862#[derive(Clone)]
863pub struct ListNode {
864    /// The list items to display.
865    pub items: Vec<String>,
866    /// Currently selected index.
867    pub selected: usize,
868    /// Callback when selection changes.
869    pub on_select: Option<SelectCallback>,
870}
871
872/// A text input node.
873#[derive(Clone)]
874pub struct TextInputNode {
875    /// Current text value.
876    pub value: String,
877    /// Placeholder text shown when empty.
878    pub placeholder: String,
879    /// Callback when text changes.
880    pub on_change: Option<ChangeCallback>,
881    /// Callback when cursor position changes.
882    pub on_cursor_change: Option<CursorPosCallback>,
883    /// Callback when Enter is pressed (submit).
884    pub on_submit: Option<Callback>,
885    /// Callback when Up arrow is pressed.
886    pub on_key_up: Option<Callback>,
887    /// Callback when Down arrow is pressed.
888    pub on_key_down: Option<Callback>,
889    /// Cursor position within the text.
890    pub cursor_pos: usize,
891    /// Whether this input should have initial focus.
892    pub focused: bool,
893}
894
895/// A multi-line text area node.
896#[derive(Clone)]
897pub struct TextAreaNode {
898    /// Current text value (may contain newlines).
899    pub value: String,
900    /// Placeholder text shown when empty.
901    pub placeholder: String,
902    /// Callback when text changes.
903    pub on_change: Option<ChangeCallback>,
904    /// Callback when cursor position changes (line, column).
905    pub on_cursor_change: Option<CursorChangeCallback>,
906    /// Cursor line position.
907    pub cursor_line: usize,
908    /// Cursor column position.
909    pub cursor_col: usize,
910    /// Number of visible rows.
911    pub rows: u16,
912    /// Width at which to auto-wrap text (None = no wrap, text truncated at display edge).
913    pub wrap_width: Option<u16>,
914}
915
916/// A checkbox node.
917#[derive(Clone)]
918pub struct CheckboxNode {
919    /// Whether the checkbox is checked.
920    pub checked: bool,
921    /// Label displayed next to the checkbox.
922    pub label: String,
923    /// Callback when toggled.
924    pub on_toggle: Option<ToggleCallback>,
925}
926
927/// A radio group node (mutually exclusive options).
928#[derive(Clone)]
929pub struct RadioGroupNode {
930    /// The available options.
931    pub options: Vec<String>,
932    /// Index of the currently selected option.
933    pub selected: usize,
934    /// Optional label for the group.
935    pub label: Option<String>,
936    /// Callback when selection changes.
937    pub on_change: Option<SelectCallback>,
938}
939
940/// A modal dialog node.
941#[derive(Clone)]
942pub struct ModalNode {
943    /// Whether the modal is visible.
944    pub visible: bool,
945    /// Title of the modal (shown in border).
946    pub title: String,
947    /// The content view inside the modal.
948    pub child: Option<std::boxed::Box<View>>,
949    /// Callback when modal is dismissed (Escape key).
950    pub on_dismiss: Option<Callback>,
951    /// Width of the modal (percentage of screen, 0-100).
952    pub width_percent: u16,
953    /// Height of the modal (percentage of screen, 0-100).
954    pub height_percent: u16,
955}
956
957/// A split pane container node.
958#[derive(Clone)]
959pub struct SplitNode {
960    /// Orientation of the split (horizontal or vertical).
961    pub orientation: Orientation,
962    /// First pane content.
963    pub first: std::boxed::Box<View>,
964    /// Second pane content.
965    pub second: std::boxed::Box<View>,
966    /// Split ratio (0.0 to 1.0, where 0.5 is equal split).
967    pub ratio: f32,
968    /// Minimum size for first pane (in cells).
969    pub min_first: Option<u16>,
970    /// Minimum size for second pane (in cells).
971    pub min_second: Option<u16>,
972    /// Whether to show a divider line between panes.
973    pub show_divider: bool,
974}
975
976/// A tabbed interface container node.
977#[derive(Clone)]
978pub struct TabsNode {
979    /// Tab labels displayed in the tab bar.
980    pub tabs: Vec<String>,
981    /// Content views for each tab.
982    pub children: Vec<View>,
983    /// Currently active tab index.
984    pub active: usize,
985    /// Callback when tab changes.
986    pub on_change: Option<SelectCallback>,
987    /// Position of the tab bar (top or bottom).
988    pub position: TabPosition,
989}
990
991/// A single item in a tree view.
992#[derive(Clone, Debug)]
993pub struct TreeItem {
994    /// Display label for this item.
995    pub label: String,
996    /// Optional icon displayed before the label.
997    pub icon: Option<String>,
998    /// Child items (empty for leaf nodes).
999    pub children: Vec<TreeItem>,
1000    /// Whether this node is expanded (showing children).
1001    pub expanded: bool,
1002}
1003
1004impl TreeItem {
1005    /// Create a new tree item with the given label.
1006    pub fn new(label: impl Into<String>) -> Self {
1007        Self {
1008            label: label.into(),
1009            icon: None,
1010            children: Vec::new(),
1011            expanded: false,
1012        }
1013    }
1014
1015    /// Set the icon for this item.
1016    pub fn icon(mut self, icon: impl Into<String>) -> Self {
1017        self.icon = Some(icon.into());
1018        self
1019    }
1020
1021    /// Add a child item.
1022    pub fn child(mut self, child: TreeItem) -> Self {
1023        self.children.push(child);
1024        self
1025    }
1026
1027    /// Set whether this node is expanded.
1028    pub fn expanded(mut self, expanded: bool) -> Self {
1029        self.expanded = expanded;
1030        self
1031    }
1032
1033    /// Check if this is a leaf node (no children).
1034    pub fn is_leaf(&self) -> bool {
1035        self.children.is_empty()
1036    }
1037}
1038
1039/// A hierarchical tree view node.
1040#[derive(Clone)]
1041pub struct TreeNode {
1042    /// Root-level tree items.
1043    pub items: Vec<TreeItem>,
1044    /// Path to the currently selected item.
1045    pub selected: TreePath,
1046    /// Callback when selection changes.
1047    pub on_select: Option<TreeSelectCallback>,
1048    /// Callback when an item is activated (Enter/Space).
1049    pub on_activate: Option<TreeActivateCallback>,
1050}
1051
1052/// Text alignment for table columns.
1053#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1054pub enum TextAlign {
1055    #[default]
1056    Left,
1057    Center,
1058    Right,
1059}
1060
1061/// Column width specification for tables.
1062#[derive(Debug, Clone, Copy, PartialEq, Default)]
1063pub enum ColumnWidth {
1064    /// Size to fit content (default).
1065    #[default]
1066    Auto,
1067    /// Fixed width in characters.
1068    Fixed(u16),
1069    /// Flex factor for remaining space.
1070    Flex(u16),
1071}
1072
1073/// A column definition for a table.
1074#[derive(Debug, Clone)]
1075pub struct TableColumn {
1076    /// Header text for this column.
1077    pub header: String,
1078    /// Width specification.
1079    pub width: ColumnWidth,
1080    /// Whether this column is sortable.
1081    pub sortable: bool,
1082    /// Text alignment for this column.
1083    pub align: TextAlign,
1084}
1085
1086impl TableColumn {
1087    /// Create a new table column with the given header.
1088    pub fn new(header: impl Into<String>) -> Self {
1089        Self {
1090            header: header.into(),
1091            width: ColumnWidth::Auto,
1092            sortable: false,
1093            align: TextAlign::Left,
1094        }
1095    }
1096
1097    /// Set the width of this column.
1098    pub fn width(mut self, width: ColumnWidth) -> Self {
1099        self.width = width;
1100        self
1101    }
1102
1103    /// Make this column sortable.
1104    pub fn sortable(mut self, sortable: bool) -> Self {
1105        self.sortable = sortable;
1106        self
1107    }
1108
1109    /// Set the text alignment for this column.
1110    pub fn align(mut self, align: TextAlign) -> Self {
1111        self.align = align;
1112        self
1113    }
1114}
1115
1116/// A data table node with columns and rows.
1117#[derive(Clone)]
1118pub struct TableNode {
1119    /// Column definitions.
1120    pub columns: Vec<TableColumn>,
1121    /// Row data (each row is a Vec of cell strings).
1122    pub rows: Vec<Vec<String>>,
1123    /// Currently selected row index.
1124    pub selected: usize,
1125    /// Current sort state: (column_index, ascending).
1126    pub sort: Option<(usize, bool)>,
1127    /// Callback when selection changes.
1128    pub on_select: Option<SelectCallback>,
1129    /// Callback when sort changes.
1130    pub on_sort: Option<SortCallback>,
1131    /// Callback when a row is activated (Enter).
1132    pub on_activate: Option<RowActivateCallback>,
1133}
1134
1135/// A progress bar node.
1136#[derive(Clone)]
1137pub struct ProgressBarNode {
1138    /// Progress value from 0.0 to 1.0.
1139    pub value: f32,
1140    /// Optional label shown before the bar.
1141    pub label: Option<String>,
1142    /// Whether to show percentage after the bar.
1143    pub show_percentage: bool,
1144    /// Fixed width of the bar portion (None = expand to fill).
1145    pub width: Option<u16>,
1146    /// Character used for the filled portion.
1147    pub filled_char: char,
1148    /// Character used for the empty portion.
1149    pub empty_char: char,
1150}
1151
1152/// A status bar node displayed at the bottom of the screen.
1153#[derive(Clone)]
1154pub struct StatusBarNode {
1155    /// Content for the left section.
1156    pub left: String,
1157    /// Content for the center section (optional).
1158    pub center: Option<String>,
1159    /// Content for the right section (optional).
1160    pub right: Option<String>,
1161    /// Background color for the status bar.
1162    pub bg_color: Option<crossterm::style::Color>,
1163    /// Foreground color for the status bar.
1164    pub fg_color: Option<crossterm::style::Color>,
1165}
1166
1167/// Builder for VStack views.
1168#[derive(Debug, Default)]
1169pub struct VStackBuilder {
1170    children: Vec<View>,
1171    spacing: u16,
1172    justify: Justify,
1173    align: Align,
1174    layout_mode: LayoutMode,
1175}
1176
1177impl VStackBuilder {
1178    pub fn new() -> Self {
1179        Self::default()
1180    }
1181
1182    pub fn child(mut self, view: View) -> Self {
1183        self.children.push(view);
1184        self
1185    }
1186
1187    pub fn spacing(mut self, spacing: u16) -> Self {
1188        self.spacing = spacing;
1189        self
1190    }
1191
1192    /// Set justify (main axis alignment for VStack = vertical).
1193    pub fn justify(mut self, justify: Justify) -> Self {
1194        self.justify = justify;
1195        self
1196    }
1197
1198    /// Set align (cross axis alignment for VStack = horizontal).
1199    pub fn align(mut self, align: Align) -> Self {
1200        self.align = align;
1201        self
1202    }
1203
1204    /// Set layout mode (algorithm for distributing space).
1205    pub fn layout_mode(mut self, mode: LayoutMode) -> Self {
1206        self.layout_mode = mode;
1207        self
1208    }
1209
1210    pub fn build(self) -> View {
1211        View::VStack(VStackNode {
1212            children: self.children,
1213            spacing: self.spacing,
1214            justify: self.justify,
1215            align: self.align,
1216            layout_mode: self.layout_mode,
1217        })
1218    }
1219}
1220
1221/// Builder for HStack views.
1222#[derive(Debug, Default)]
1223pub struct HStackBuilder {
1224    children: Vec<View>,
1225    spacing: u16,
1226    justify: Justify,
1227    align: Align,
1228    layout_mode: LayoutMode,
1229}
1230
1231impl HStackBuilder {
1232    pub fn new() -> Self {
1233        Self::default()
1234    }
1235
1236    pub fn child(mut self, view: View) -> Self {
1237        self.children.push(view);
1238        self
1239    }
1240
1241    pub fn spacing(mut self, spacing: u16) -> Self {
1242        self.spacing = spacing;
1243        self
1244    }
1245
1246    /// Set justify (main axis alignment for HStack = horizontal).
1247    pub fn justify(mut self, justify: Justify) -> Self {
1248        self.justify = justify;
1249        self
1250    }
1251
1252    /// Set align (cross axis alignment for HStack = vertical).
1253    pub fn align(mut self, align: Align) -> Self {
1254        self.align = align;
1255        self
1256    }
1257
1258    /// Set layout mode (algorithm for distributing space).
1259    pub fn layout_mode(mut self, mode: LayoutMode) -> Self {
1260        self.layout_mode = mode;
1261        self
1262    }
1263
1264    pub fn build(self) -> View {
1265        View::HStack(HStackNode {
1266            children: self.children,
1267            spacing: self.spacing,
1268            justify: self.justify,
1269            align: self.align,
1270            layout_mode: self.layout_mode,
1271        })
1272    }
1273}
1274
1275/// Builder for Button views.
1276#[derive(Default)]
1277pub struct ButtonBuilder {
1278    label: String,
1279    on_press: Option<Callback>,
1280}
1281
1282impl ButtonBuilder {
1283    pub fn new() -> Self {
1284        Self::default()
1285    }
1286
1287    pub fn label(mut self, label: impl Into<String>) -> Self {
1288        self.label = label.into();
1289        self
1290    }
1291
1292    pub fn on_press(mut self, callback: impl Fn() + 'static) -> Self {
1293        self.on_press = Some(Rc::new(callback));
1294        self
1295    }
1296
1297    pub fn build(self) -> View {
1298        View::Button(ButtonNode {
1299            label: self.label,
1300            on_press: self.on_press,
1301        })
1302    }
1303}
1304
1305/// Builder for Box views.
1306#[derive(Default)]
1307pub struct BoxBuilder {
1308    child: Option<View>,
1309    border: bool,
1310    padding: u16,
1311    flex: u16,
1312    scroll: bool,
1313    auto_scroll_bottom: bool,
1314    focusable: Option<bool>,
1315    min_width: Option<u16>,
1316    max_width: Option<u16>,
1317    min_height: Option<u16>,
1318    max_height: Option<u16>,
1319}
1320
1321impl BoxBuilder {
1322    pub fn new() -> Self {
1323        Self::default()
1324    }
1325
1326    pub fn child(mut self, view: View) -> Self {
1327        self.child = Some(view);
1328        self
1329    }
1330
1331    pub fn border(mut self, border: bool) -> Self {
1332        self.border = border;
1333        self
1334    }
1335
1336    pub fn padding(mut self, padding: u16) -> Self {
1337        self.padding = padding;
1338        self
1339    }
1340
1341    pub fn flex(mut self, flex: u16) -> Self {
1342        self.flex = flex;
1343        self
1344    }
1345
1346    pub fn scroll(mut self, scroll: bool) -> Self {
1347        self.scroll = scroll;
1348        self
1349    }
1350
1351    /// Enable auto-scrolling to bottom (for chat-like UIs).
1352    pub fn auto_scroll_bottom(mut self, auto_scroll: bool) -> Self {
1353        self.auto_scroll_bottom = auto_scroll;
1354        self
1355    }
1356
1357    /// Set whether this box participates in focus navigation.
1358    /// By default, scrollable boxes are focusable. Use `focusable(false)` to
1359    /// disable focus for a scrollable box (e.g., auto-scroll chat messages).
1360    pub fn focusable(mut self, focusable: bool) -> Self {
1361        self.focusable = Some(focusable);
1362        self
1363    }
1364
1365    pub fn min_width(mut self, width: u16) -> Self {
1366        self.min_width = Some(width);
1367        self
1368    }
1369
1370    pub fn max_width(mut self, width: u16) -> Self {
1371        self.max_width = Some(width);
1372        self
1373    }
1374
1375    pub fn min_height(mut self, height: u16) -> Self {
1376        self.min_height = Some(height);
1377        self
1378    }
1379
1380    pub fn max_height(mut self, height: u16) -> Self {
1381        self.max_height = Some(height);
1382        self
1383    }
1384
1385    pub fn build(self) -> View {
1386        // Scrollable boxes are focusable by default so users can scroll back
1387        let default_focusable = self.scroll || self.auto_scroll_bottom;
1388        View::Box(BoxNode {
1389            child: self.child.map(std::boxed::Box::new),
1390            border: self.border,
1391            padding: self.padding,
1392            flex: self.flex,
1393            scroll: self.scroll,
1394            auto_scroll_bottom: self.auto_scroll_bottom,
1395            focusable: self.focusable.unwrap_or(default_focusable),
1396            min_width: self.min_width,
1397            max_width: self.max_width,
1398            min_height: self.min_height,
1399            max_height: self.max_height,
1400        })
1401    }
1402}
1403
1404/// Builder for List views.
1405#[derive(Default)]
1406pub struct ListBuilder {
1407    items: Vec<String>,
1408    selected: usize,
1409    on_select: Option<SelectCallback>,
1410}
1411
1412impl ListBuilder {
1413    pub fn new() -> Self {
1414        Self::default()
1415    }
1416
1417    pub fn items(mut self, items: Vec<String>) -> Self {
1418        self.items = items;
1419        self
1420    }
1421
1422    pub fn selected(mut self, selected: usize) -> Self {
1423        self.selected = selected;
1424        self
1425    }
1426
1427    pub fn on_select(mut self, callback: impl Fn(usize) + 'static) -> Self {
1428        self.on_select = Some(Rc::new(callback));
1429        self
1430    }
1431
1432    pub fn build(self) -> View {
1433        View::List(ListNode {
1434            items: self.items,
1435            selected: self.selected,
1436            on_select: self.on_select,
1437        })
1438    }
1439}
1440
1441/// Builder for TextInput views.
1442#[derive(Default)]
1443pub struct TextInputBuilder {
1444    value: String,
1445    placeholder: String,
1446    on_change: Option<ChangeCallback>,
1447    on_cursor_change: Option<CursorPosCallback>,
1448    on_submit: Option<Callback>,
1449    on_key_up: Option<Callback>,
1450    on_key_down: Option<Callback>,
1451    cursor_pos: usize,
1452    focused: bool,
1453}
1454
1455impl TextInputBuilder {
1456    pub fn new() -> Self {
1457        Self::default()
1458    }
1459
1460    pub fn value(mut self, value: impl Into<String>) -> Self {
1461        self.value = value.into();
1462        // Default cursor to end of value
1463        self.cursor_pos = self.value.len();
1464        self
1465    }
1466
1467    pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
1468        self.placeholder = placeholder.into();
1469        self
1470    }
1471
1472    pub fn on_change(mut self, callback: impl Fn(String) + 'static) -> Self {
1473        self.on_change = Some(Rc::new(callback));
1474        self
1475    }
1476
1477    pub fn on_cursor_change(mut self, callback: impl Fn(usize) + 'static) -> Self {
1478        self.on_cursor_change = Some(Rc::new(callback));
1479        self
1480    }
1481
1482    pub fn on_submit(mut self, callback: impl Fn() + 'static) -> Self {
1483        self.on_submit = Some(Rc::new(callback));
1484        self
1485    }
1486
1487    /// Set callback for when Up arrow is pressed (e.g., for command history).
1488    pub fn on_key_up(mut self, callback: impl Fn() + 'static) -> Self {
1489        self.on_key_up = Some(Rc::new(callback));
1490        self
1491    }
1492
1493    /// Set callback for when Down arrow is pressed (e.g., for command history).
1494    pub fn on_key_down(mut self, callback: impl Fn() + 'static) -> Self {
1495        self.on_key_down = Some(Rc::new(callback));
1496        self
1497    }
1498
1499    pub fn cursor(mut self, pos: usize) -> Self {
1500        self.cursor_pos = pos;
1501        self
1502    }
1503
1504    /// Set this input to have initial focus when the app starts.
1505    pub fn focused(mut self, focused: bool) -> Self {
1506        self.focused = focused;
1507        self
1508    }
1509
1510    pub fn build(self) -> View {
1511        View::TextInput(TextInputNode {
1512            value: self.value.clone(),
1513            placeholder: self.placeholder,
1514            on_change: self.on_change,
1515            on_cursor_change: self.on_cursor_change,
1516            on_submit: self.on_submit,
1517            on_key_up: self.on_key_up,
1518            on_key_down: self.on_key_down,
1519            cursor_pos: self.cursor_pos.min(self.value.len()),
1520            focused: self.focused,
1521        })
1522    }
1523}
1524
1525/// Builder for Checkbox views.
1526#[derive(Default)]
1527pub struct CheckboxBuilder {
1528    checked: bool,
1529    label: String,
1530    on_toggle: Option<ToggleCallback>,
1531}
1532
1533impl CheckboxBuilder {
1534    pub fn new() -> Self {
1535        Self::default()
1536    }
1537
1538    pub fn checked(mut self, checked: bool) -> Self {
1539        self.checked = checked;
1540        self
1541    }
1542
1543    pub fn label(mut self, label: impl Into<String>) -> Self {
1544        self.label = label.into();
1545        self
1546    }
1547
1548    pub fn on_toggle(mut self, callback: impl Fn(bool) + 'static) -> Self {
1549        self.on_toggle = Some(Rc::new(callback));
1550        self
1551    }
1552
1553    pub fn build(self) -> View {
1554        View::Checkbox(CheckboxNode {
1555            checked: self.checked,
1556            label: self.label,
1557            on_toggle: self.on_toggle,
1558        })
1559    }
1560}
1561
1562/// Builder for RadioGroup views.
1563#[derive(Default)]
1564pub struct RadioGroupBuilder {
1565    options: Vec<String>,
1566    selected: usize,
1567    label: Option<String>,
1568    on_change: Option<SelectCallback>,
1569}
1570
1571impl RadioGroupBuilder {
1572    pub fn new() -> Self {
1573        Self::default()
1574    }
1575
1576    /// Set the available options.
1577    pub fn options(mut self, options: Vec<impl Into<String>>) -> Self {
1578        self.options = options.into_iter().map(|s| s.into()).collect();
1579        self
1580    }
1581
1582    /// Add a single option.
1583    pub fn option(mut self, option: impl Into<String>) -> Self {
1584        self.options.push(option.into());
1585        self
1586    }
1587
1588    /// Set the currently selected option index.
1589    pub fn selected(mut self, selected: usize) -> Self {
1590        self.selected = selected;
1591        self
1592    }
1593
1594    /// Set an optional label for the group.
1595    pub fn label(mut self, label: impl Into<String>) -> Self {
1596        self.label = Some(label.into());
1597        self
1598    }
1599
1600    /// Set the callback when selection changes.
1601    pub fn on_change(mut self, callback: impl Fn(usize) + 'static) -> Self {
1602        self.on_change = Some(Rc::new(callback));
1603        self
1604    }
1605
1606    pub fn build(self) -> View {
1607        View::RadioGroup(RadioGroupNode {
1608            options: self.options,
1609            selected: self.selected,
1610            label: self.label,
1611            on_change: self.on_change,
1612        })
1613    }
1614}
1615
1616/// Builder for styled Text views.
1617#[derive(Debug, Default)]
1618pub struct TextBuilder {
1619    content: String,
1620    color: Option<crossterm::style::Color>,
1621    bg_color: Option<crossterm::style::Color>,
1622    bold: bool,
1623    italic: bool,
1624    underline: bool,
1625    dim: bool,
1626}
1627
1628impl TextBuilder {
1629    pub fn new(content: impl Into<String>) -> Self {
1630        Self {
1631            content: content.into(),
1632            ..Default::default()
1633        }
1634    }
1635
1636    /// Set the text color.
1637    pub fn color(mut self, color: crossterm::style::Color) -> Self {
1638        self.color = Some(color);
1639        self
1640    }
1641
1642    /// Set the background color.
1643    pub fn bg(mut self, color: crossterm::style::Color) -> Self {
1644        self.bg_color = Some(color);
1645        self
1646    }
1647
1648    /// Make the text bold.
1649    pub fn bold(mut self) -> Self {
1650        self.bold = true;
1651        self
1652    }
1653
1654    /// Make the text italic.
1655    pub fn italic(mut self) -> Self {
1656        self.italic = true;
1657        self
1658    }
1659
1660    /// Underline the text.
1661    pub fn underline(mut self) -> Self {
1662        self.underline = true;
1663        self
1664    }
1665
1666    /// Make the text dim/faded.
1667    pub fn dim(mut self) -> Self {
1668        self.dim = true;
1669        self
1670    }
1671
1672    pub fn build(self) -> View {
1673        View::Text(TextNode {
1674            content: self.content,
1675            color: self.color,
1676            bg_color: self.bg_color,
1677            bold: self.bold,
1678            italic: self.italic,
1679            underline: self.underline,
1680            dim: self.dim,
1681        })
1682    }
1683}
1684
1685/// Builder for TextArea views.
1686#[derive(Default)]
1687pub struct TextAreaBuilder {
1688    value: String,
1689    placeholder: String,
1690    on_change: Option<ChangeCallback>,
1691    on_cursor_change: Option<CursorChangeCallback>,
1692    cursor_line: usize,
1693    cursor_col: usize,
1694    rows: u16,
1695    wrap_width: Option<u16>,
1696}
1697
1698impl TextAreaBuilder {
1699    pub fn new() -> Self {
1700        Self {
1701            rows: 5, // Default to 5 rows
1702            ..Default::default()
1703        }
1704    }
1705
1706    /// Set the current text value.
1707    pub fn value(mut self, value: impl Into<String>) -> Self {
1708        self.value = value.into();
1709        self
1710    }
1711
1712    /// Set the placeholder text shown when empty.
1713    pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
1714        self.placeholder = placeholder.into();
1715        self
1716    }
1717
1718    /// Set the callback for when text changes.
1719    pub fn on_change(mut self, callback: impl Fn(String) + 'static) -> Self {
1720        self.on_change = Some(Rc::new(callback));
1721        self
1722    }
1723
1724    /// Set the callback for when cursor position changes.
1725    pub fn on_cursor_change(mut self, callback: impl Fn(usize, usize) + 'static) -> Self {
1726        self.on_cursor_change = Some(Rc::new(callback));
1727        self
1728    }
1729
1730    /// Set the cursor line position.
1731    pub fn cursor_line(mut self, line: usize) -> Self {
1732        self.cursor_line = line;
1733        self
1734    }
1735
1736    /// Set the cursor column position.
1737    pub fn cursor_col(mut self, col: usize) -> Self {
1738        self.cursor_col = col;
1739        self
1740    }
1741
1742    /// Set the number of visible rows.
1743    pub fn rows(mut self, rows: u16) -> Self {
1744        self.rows = rows;
1745        self
1746    }
1747
1748    /// Set the width at which text automatically wraps to the next line.
1749    /// If not set, text is truncated at the display edge without wrapping.
1750    pub fn wrap_width(mut self, width: u16) -> Self {
1751        self.wrap_width = Some(width);
1752        self
1753    }
1754
1755    pub fn build(self) -> View {
1756        View::TextArea(TextAreaNode {
1757            value: self.value,
1758            placeholder: self.placeholder,
1759            on_change: self.on_change,
1760            on_cursor_change: self.on_cursor_change,
1761            cursor_line: self.cursor_line,
1762            cursor_col: self.cursor_col,
1763            rows: self.rows,
1764            wrap_width: self.wrap_width,
1765        })
1766    }
1767}
1768
1769/// Builder for Modal views.
1770#[derive(Default)]
1771pub struct ModalBuilder {
1772    visible: bool,
1773    title: String,
1774    child: Option<View>,
1775    on_dismiss: Option<Callback>,
1776    width_percent: u16,
1777    height_percent: u16,
1778}
1779
1780impl ModalBuilder {
1781    pub fn new() -> Self {
1782        Self {
1783            width_percent: 60,
1784            height_percent: 50,
1785            ..Default::default()
1786        }
1787    }
1788
1789    /// Set whether the modal is visible.
1790    pub fn visible(mut self, visible: bool) -> Self {
1791        self.visible = visible;
1792        self
1793    }
1794
1795    /// Set the title shown in the modal border.
1796    pub fn title(mut self, title: impl Into<String>) -> Self {
1797        self.title = title.into();
1798        self
1799    }
1800
1801    /// Set the content of the modal.
1802    pub fn child(mut self, view: View) -> Self {
1803        self.child = Some(view);
1804        self
1805    }
1806
1807    /// Set the callback when modal is dismissed (Escape key).
1808    pub fn on_dismiss(mut self, callback: impl Fn() + 'static) -> Self {
1809        self.on_dismiss = Some(Rc::new(callback));
1810        self
1811    }
1812
1813    /// Set the width as percentage of screen (0-100).
1814    pub fn width(mut self, percent: u16) -> Self {
1815        self.width_percent = percent.min(100);
1816        self
1817    }
1818
1819    /// Set the height as percentage of screen (0-100).
1820    pub fn height(mut self, percent: u16) -> Self {
1821        self.height_percent = percent.min(100);
1822        self
1823    }
1824
1825    pub fn build(self) -> View {
1826        View::Modal(ModalNode {
1827            visible: self.visible,
1828            title: self.title,
1829            child: self.child.map(std::boxed::Box::new),
1830            on_dismiss: self.on_dismiss,
1831            width_percent: self.width_percent,
1832            height_percent: self.height_percent,
1833        })
1834    }
1835}
1836
1837/// Builder for Split pane views.
1838#[derive(Default)]
1839pub struct SplitBuilder {
1840    orientation: Orientation,
1841    first: Option<View>,
1842    second: Option<View>,
1843    ratio: f32,
1844    min_first: Option<u16>,
1845    min_second: Option<u16>,
1846    show_divider: bool,
1847}
1848
1849impl SplitBuilder {
1850    pub fn new() -> Self {
1851        Self {
1852            ratio: 0.5, // Default to equal split
1853            show_divider: true,
1854            ..Default::default()
1855        }
1856    }
1857
1858    /// Set the orientation to horizontal (side by side).
1859    pub fn horizontal(mut self) -> Self {
1860        self.orientation = Orientation::Horizontal;
1861        self
1862    }
1863
1864    /// Set the orientation to vertical (stacked).
1865    pub fn vertical(mut self) -> Self {
1866        self.orientation = Orientation::Vertical;
1867        self
1868    }
1869
1870    /// Set the first pane content.
1871    pub fn first(mut self, view: View) -> Self {
1872        self.first = Some(view);
1873        self
1874    }
1875
1876    /// Set the second pane content.
1877    pub fn second(mut self, view: View) -> Self {
1878        self.second = Some(view);
1879        self
1880    }
1881
1882    /// Set the split ratio (0.0 to 1.0, where 0.5 is equal split).
1883    pub fn ratio(mut self, ratio: f32) -> Self {
1884        self.ratio = ratio.clamp(0.0, 1.0);
1885        self
1886    }
1887
1888    /// Set the minimum size for the first pane (in cells).
1889    pub fn min_first(mut self, min: u16) -> Self {
1890        self.min_first = Some(min);
1891        self
1892    }
1893
1894    /// Set the minimum size for the second pane (in cells).
1895    pub fn min_second(mut self, min: u16) -> Self {
1896        self.min_second = Some(min);
1897        self
1898    }
1899
1900    /// Set whether to show a divider line between panes.
1901    pub fn show_divider(mut self, show: bool) -> Self {
1902        self.show_divider = show;
1903        self
1904    }
1905
1906    pub fn build(self) -> View {
1907        View::Split(SplitNode {
1908            orientation: self.orientation,
1909            first: std::boxed::Box::new(self.first.unwrap_or(View::Empty)),
1910            second: std::boxed::Box::new(self.second.unwrap_or(View::Empty)),
1911            ratio: self.ratio,
1912            min_first: self.min_first,
1913            min_second: self.min_second,
1914            show_divider: self.show_divider,
1915        })
1916    }
1917}
1918
1919/// Builder for Tabs views.
1920#[derive(Default)]
1921pub struct TabsBuilder {
1922    tabs: Vec<String>,
1923    children: Vec<View>,
1924    active: usize,
1925    on_change: Option<SelectCallback>,
1926    position: TabPosition,
1927}
1928
1929impl TabsBuilder {
1930    pub fn new() -> Self {
1931        Self::default()
1932    }
1933
1934    /// Add a tab with a label and content view.
1935    pub fn tab(mut self, label: impl Into<String>, content: View) -> Self {
1936        self.tabs.push(label.into());
1937        self.children.push(content);
1938        self
1939    }
1940
1941    /// Set the active tab index.
1942    pub fn active(mut self, index: usize) -> Self {
1943        self.active = index;
1944        self
1945    }
1946
1947    /// Set the callback when tab changes.
1948    pub fn on_change(mut self, callback: impl Fn(usize) + 'static) -> Self {
1949        self.on_change = Some(Rc::new(callback));
1950        self
1951    }
1952
1953    /// Set the position of the tab bar.
1954    pub fn position(mut self, position: TabPosition) -> Self {
1955        self.position = position;
1956        self
1957    }
1958
1959    pub fn build(self) -> View {
1960        View::Tabs(TabsNode {
1961            tabs: self.tabs,
1962            children: self.children,
1963            active: self.active,
1964            on_change: self.on_change,
1965            position: self.position,
1966        })
1967    }
1968}
1969
1970/// Builder for Tree views.
1971#[derive(Default)]
1972pub struct TreeBuilder {
1973    items: Vec<TreeItem>,
1974    selected: TreePath,
1975    on_select: Option<TreeSelectCallback>,
1976    on_activate: Option<TreeActivateCallback>,
1977}
1978
1979impl TreeBuilder {
1980    pub fn new() -> Self {
1981        Self::default()
1982    }
1983
1984    /// Set the tree items.
1985    pub fn items(mut self, items: Vec<TreeItem>) -> Self {
1986        self.items = items;
1987        self
1988    }
1989
1990    /// Add a single root item.
1991    pub fn item(mut self, item: TreeItem) -> Self {
1992        self.items.push(item);
1993        self
1994    }
1995
1996    /// Set the selected path.
1997    pub fn selected(mut self, path: TreePath) -> Self {
1998        self.selected = path;
1999        self
2000    }
2001
2002    /// Set the callback when selection changes.
2003    pub fn on_select(mut self, callback: impl Fn(TreePath) + 'static) -> Self {
2004        self.on_select = Some(Rc::new(callback));
2005        self
2006    }
2007
2008    /// Set the callback when an item is activated.
2009    pub fn on_activate(mut self, callback: impl Fn(TreePath) + 'static) -> Self {
2010        self.on_activate = Some(Rc::new(callback));
2011        self
2012    }
2013
2014    pub fn build(self) -> View {
2015        View::Tree(TreeNode {
2016            items: self.items,
2017            selected: self.selected,
2018            on_select: self.on_select,
2019            on_activate: self.on_activate,
2020        })
2021    }
2022}
2023
2024/// Builder for Table views.
2025#[derive(Default)]
2026pub struct TableBuilder {
2027    columns: Vec<TableColumn>,
2028    rows: Vec<Vec<String>>,
2029    selected: usize,
2030    sort: Option<(usize, bool)>,
2031    on_select: Option<SelectCallback>,
2032    on_sort: Option<SortCallback>,
2033    on_activate: Option<RowActivateCallback>,
2034}
2035
2036impl TableBuilder {
2037    pub fn new() -> Self {
2038        Self::default()
2039    }
2040
2041    /// Add a column with just a header (auto width, left aligned, not sortable).
2042    pub fn column(mut self, header: impl Into<String>) -> Self {
2043        self.columns.push(TableColumn::new(header));
2044        self
2045    }
2046
2047    /// Add a column with full configuration.
2048    pub fn column_with(mut self, column: TableColumn) -> Self {
2049        self.columns.push(column);
2050        self
2051    }
2052
2053    /// Set the row data.
2054    pub fn rows(mut self, rows: Vec<Vec<String>>) -> Self {
2055        self.rows = rows;
2056        self
2057    }
2058
2059    /// Add a single row.
2060    pub fn row(mut self, row: Vec<String>) -> Self {
2061        self.rows.push(row);
2062        self
2063    }
2064
2065    /// Set the selected row index.
2066    pub fn selected(mut self, index: usize) -> Self {
2067        self.selected = index;
2068        self
2069    }
2070
2071    /// Set the sort state (column index, ascending).
2072    pub fn sort(mut self, sort: Option<(usize, bool)>) -> Self {
2073        self.sort = sort;
2074        self
2075    }
2076
2077    /// Set the sort state with explicit column and direction.
2078    pub fn sort_by(mut self, column: usize, ascending: bool) -> Self {
2079        self.sort = Some((column, ascending));
2080        self
2081    }
2082
2083    /// Set the callback when selection changes.
2084    pub fn on_select(mut self, callback: impl Fn(usize) + 'static) -> Self {
2085        self.on_select = Some(Rc::new(callback));
2086        self
2087    }
2088
2089    /// Set the callback when sort changes.
2090    pub fn on_sort(mut self, callback: impl Fn(usize, bool) + 'static) -> Self {
2091        self.on_sort = Some(Rc::new(callback));
2092        self
2093    }
2094
2095    /// Set the callback when a row is activated.
2096    pub fn on_activate(mut self, callback: impl Fn(usize) + 'static) -> Self {
2097        self.on_activate = Some(Rc::new(callback));
2098        self
2099    }
2100
2101    pub fn build(self) -> View {
2102        View::Table(TableNode {
2103            columns: self.columns,
2104            rows: self.rows,
2105            selected: self.selected,
2106            sort: self.sort,
2107            on_select: self.on_select,
2108            on_sort: self.on_sort,
2109            on_activate: self.on_activate,
2110        })
2111    }
2112}
2113
2114/// Builder for ProgressBar views.
2115#[derive(Debug, Clone)]
2116pub struct ProgressBarBuilder {
2117    value: f32,
2118    label: Option<String>,
2119    show_percentage: bool,
2120    width: Option<u16>,
2121    filled_char: char,
2122    empty_char: char,
2123}
2124
2125impl Default for ProgressBarBuilder {
2126    fn default() -> Self {
2127        Self {
2128            value: 0.0,
2129            label: None,
2130            show_percentage: true,
2131            width: None,
2132            filled_char: 'â–ˆ',
2133            empty_char: 'â–‘',
2134        }
2135    }
2136}
2137
2138impl ProgressBarBuilder {
2139    pub fn new() -> Self {
2140        Self::default()
2141    }
2142
2143    /// Set the progress value (0.0 to 1.0).
2144    pub fn value(mut self, value: f32) -> Self {
2145        self.value = value.clamp(0.0, 1.0);
2146        self
2147    }
2148
2149    /// Set a label shown before the bar.
2150    pub fn label(mut self, label: impl Into<String>) -> Self {
2151        self.label = Some(label.into());
2152        self
2153    }
2154
2155    /// Set whether to show percentage after the bar (default: true).
2156    pub fn show_percentage(mut self, show: bool) -> Self {
2157        self.show_percentage = show;
2158        self
2159    }
2160
2161    /// Set a fixed width for the bar portion.
2162    /// If not set, the bar expands to fill available space.
2163    pub fn width(mut self, width: u16) -> Self {
2164        self.width = Some(width);
2165        self
2166    }
2167
2168    /// Set the character used for the filled portion (default: â–ˆ).
2169    pub fn filled_char(mut self, ch: char) -> Self {
2170        self.filled_char = ch;
2171        self
2172    }
2173
2174    /// Set the character used for the empty portion (default: â–‘).
2175    pub fn empty_char(mut self, ch: char) -> Self {
2176        self.empty_char = ch;
2177        self
2178    }
2179
2180    pub fn build(self) -> View {
2181        View::ProgressBar(ProgressBarNode {
2182            value: self.value,
2183            label: self.label,
2184            show_percentage: self.show_percentage,
2185            width: self.width,
2186            filled_char: self.filled_char,
2187            empty_char: self.empty_char,
2188        })
2189    }
2190}
2191
2192/// Builder for StatusBar views.
2193#[derive(Debug, Clone, Default)]
2194pub struct StatusBarBuilder {
2195    left: String,
2196    center: Option<String>,
2197    right: Option<String>,
2198    bg_color: Option<crossterm::style::Color>,
2199    fg_color: Option<crossterm::style::Color>,
2200}
2201
2202impl StatusBarBuilder {
2203    pub fn new() -> Self {
2204        Self::default()
2205    }
2206
2207    /// Set the left section content.
2208    pub fn left(mut self, content: impl Into<String>) -> Self {
2209        self.left = content.into();
2210        self
2211    }
2212
2213    /// Set the center section content.
2214    pub fn center(mut self, content: impl Into<String>) -> Self {
2215        self.center = Some(content.into());
2216        self
2217    }
2218
2219    /// Set the right section content.
2220    pub fn right(mut self, content: impl Into<String>) -> Self {
2221        self.right = Some(content.into());
2222        self
2223    }
2224
2225    /// Set the background color.
2226    pub fn bg(mut self, color: crossterm::style::Color) -> Self {
2227        self.bg_color = Some(color);
2228        self
2229    }
2230
2231    /// Set the foreground (text) color.
2232    pub fn fg(mut self, color: crossterm::style::Color) -> Self {
2233        self.fg_color = Some(color);
2234        self
2235    }
2236
2237    pub fn build(self) -> View {
2238        View::StatusBar(StatusBarNode {
2239            left: self.left,
2240            center: self.center,
2241            right: self.right,
2242            bg_color: self.bg_color,
2243            fg_color: self.fg_color,
2244        })
2245    }
2246}
2247
2248// =============================================================================
2249// Command Palette
2250// =============================================================================
2251
2252/// A command in the command palette.
2253#[derive(Clone)]
2254pub struct PaletteCommand {
2255    /// Unique identifier for the command.
2256    pub id: &'static str,
2257    /// Display label.
2258    pub label: String,
2259    /// Optional keyboard shortcut display (e.g., "Ctrl+S").
2260    pub shortcut: Option<String>,
2261    /// Optional category for grouping.
2262    pub category: Option<String>,
2263}
2264
2265impl PaletteCommand {
2266    /// Create a new palette command.
2267    pub fn new(id: &'static str, label: impl Into<String>) -> Self {
2268        Self {
2269            id,
2270            label: label.into(),
2271            shortcut: None,
2272            category: None,
2273        }
2274    }
2275
2276    /// Set the shortcut display string.
2277    pub fn shortcut(mut self, shortcut: impl Into<String>) -> Self {
2278        self.shortcut = Some(shortcut.into());
2279        self
2280    }
2281
2282    /// Set the category.
2283    pub fn category(mut self, category: impl Into<String>) -> Self {
2284        self.category = Some(category.into());
2285        self
2286    }
2287}
2288
2289/// A command palette overlay for searching and executing commands.
2290#[derive(Clone)]
2291pub struct CommandPaletteNode {
2292    /// Whether the palette is visible.
2293    pub visible: bool,
2294    /// Current search query.
2295    pub query: String,
2296    /// Available commands.
2297    pub commands: Vec<PaletteCommand>,
2298    /// Currently selected index in the filtered list.
2299    pub selected: usize,
2300    /// Callback when query changes.
2301    pub on_query_change: Option<ChangeCallback>,
2302    /// Callback when a command is selected (receives command ID).
2303    pub on_select: Option<CommandCallback>,
2304    /// Callback when the palette is dismissed.
2305    pub on_dismiss: Option<Callback>,
2306    /// Width percentage (0-100).
2307    pub width_percent: u16,
2308    /// Height percentage (0-100).
2309    pub height_percent: u16,
2310}
2311
2312/// Builder for CommandPalette views.
2313#[derive(Default)]
2314pub struct CommandPaletteBuilder {
2315    visible: bool,
2316    query: String,
2317    commands: Vec<PaletteCommand>,
2318    selected: usize,
2319    on_query_change: Option<ChangeCallback>,
2320    on_select: Option<CommandCallback>,
2321    on_dismiss: Option<Callback>,
2322    width_percent: u16,
2323    height_percent: u16,
2324}
2325
2326impl CommandPaletteBuilder {
2327    pub fn new() -> Self {
2328        Self {
2329            width_percent: 50,
2330            height_percent: 60,
2331            ..Default::default()
2332        }
2333    }
2334
2335    /// Set whether the palette is visible.
2336    pub fn visible(mut self, visible: bool) -> Self {
2337        self.visible = visible;
2338        self
2339    }
2340
2341    /// Set the current query.
2342    pub fn query(mut self, query: impl Into<String>) -> Self {
2343        self.query = query.into();
2344        self
2345    }
2346
2347    /// Set the available commands.
2348    pub fn commands(mut self, commands: Vec<PaletteCommand>) -> Self {
2349        self.commands = commands;
2350        self
2351    }
2352
2353    /// Add a single command.
2354    pub fn command(mut self, command: PaletteCommand) -> Self {
2355        self.commands.push(command);
2356        self
2357    }
2358
2359    /// Set the selected index.
2360    pub fn selected(mut self, selected: usize) -> Self {
2361        self.selected = selected;
2362        self
2363    }
2364
2365    /// Set the callback for query changes.
2366    pub fn on_query_change(mut self, callback: impl Fn(String) + 'static) -> Self {
2367        self.on_query_change = Some(Rc::new(callback));
2368        self
2369    }
2370
2371    /// Set the callback for command selection.
2372    pub fn on_select(mut self, callback: impl Fn(&'static str) + 'static) -> Self {
2373        self.on_select = Some(Rc::new(callback));
2374        self
2375    }
2376
2377    /// Set the callback when dismissed.
2378    pub fn on_dismiss(mut self, callback: impl Fn() + 'static) -> Self {
2379        self.on_dismiss = Some(Rc::new(callback));
2380        self
2381    }
2382
2383    /// Set the width as percentage of screen.
2384    pub fn width(mut self, percent: u16) -> Self {
2385        self.width_percent = percent.min(100);
2386        self
2387    }
2388
2389    /// Set the height as percentage of screen.
2390    pub fn height(mut self, percent: u16) -> Self {
2391        self.height_percent = percent.min(100);
2392        self
2393    }
2394
2395    pub fn build(self) -> View {
2396        View::CommandPalette(CommandPaletteNode {
2397            visible: self.visible,
2398            query: self.query,
2399            commands: self.commands,
2400            selected: self.selected,
2401            on_query_change: self.on_query_change,
2402            on_select: self.on_select,
2403            on_dismiss: self.on_dismiss,
2404            width_percent: self.width_percent,
2405            height_percent: self.height_percent,
2406        })
2407    }
2408}
2409
2410// =============================================================================
2411// Menu Bar
2412// =============================================================================
2413
2414/// A menu in the menu bar.
2415#[derive(Clone)]
2416pub struct Menu {
2417    /// Display label for the menu.
2418    pub label: String,
2419    /// Items in this menu.
2420    pub items: Vec<MenuItemNode>,
2421}
2422
2423impl Menu {
2424    /// Create a new menu with the given label.
2425    pub fn new(label: impl Into<String>) -> Self {
2426        Self {
2427            label: label.into(),
2428            items: Vec::new(),
2429        }
2430    }
2431
2432    /// Add an item to the menu.
2433    pub fn item(mut self, item: MenuItemNode) -> Self {
2434        self.items.push(item);
2435        self
2436    }
2437
2438    /// Add a command item.
2439    pub fn command(self, id: &'static str, label: impl Into<String>) -> Self {
2440        self.item(MenuItemNode::Command {
2441            id,
2442            label: label.into(),
2443            shortcut: None,
2444        })
2445    }
2446
2447    /// Add a command item with shortcut display.
2448    pub fn command_with_shortcut(
2449        self,
2450        id: &'static str,
2451        label: impl Into<String>,
2452        shortcut: impl Into<String>,
2453    ) -> Self {
2454        self.item(MenuItemNode::Command {
2455            id,
2456            label: label.into(),
2457            shortcut: Some(shortcut.into()),
2458        })
2459    }
2460
2461    /// Add a separator.
2462    pub fn separator(self) -> Self {
2463        self.item(MenuItemNode::Separator)
2464    }
2465}
2466
2467/// An item in a menu.
2468#[derive(Clone)]
2469pub enum MenuItemNode {
2470    /// A command with ID, label, and optional shortcut display.
2471    Command {
2472        id: &'static str,
2473        label: String,
2474        shortcut: Option<String>,
2475    },
2476    /// A visual separator.
2477    Separator,
2478}
2479
2480/// A horizontal menu bar with dropdown menus.
2481#[derive(Clone)]
2482pub struct MenuBarNode {
2483    /// The menus in the menu bar.
2484    pub menus: Vec<Menu>,
2485    /// Currently active (open) menu index, if any.
2486    pub active_menu: Option<usize>,
2487    /// Currently highlighted menu index (for keyboard navigation when no menu is open).
2488    pub highlighted_menu: usize,
2489    /// Currently selected item in the active menu.
2490    pub selected_item: usize,
2491    /// Callback when a command is selected.
2492    pub on_select: Option<CommandCallback>,
2493    /// Callback when the active menu changes (opens/closes).
2494    pub on_menu_change: Option<SelectCallback>,
2495    /// Callback when the highlighted menu changes (arrow key navigation).
2496    pub on_highlight_change: Option<SelectCallback>,
2497    /// Callback when the selected item within a menu changes.
2498    pub on_item_change: Option<SelectCallback>,
2499}
2500
2501/// Builder for MenuBar views.
2502#[derive(Default)]
2503pub struct MenuBarBuilder {
2504    menus: Vec<Menu>,
2505    active_menu: Option<usize>,
2506    highlighted_menu: usize,
2507    selected_item: usize,
2508    on_select: Option<CommandCallback>,
2509    on_menu_change: Option<SelectCallback>,
2510    on_highlight_change: Option<SelectCallback>,
2511    on_item_change: Option<SelectCallback>,
2512}
2513
2514impl MenuBarBuilder {
2515    pub fn new() -> Self {
2516        Self::default()
2517    }
2518
2519    /// Add a menu to the menu bar.
2520    pub fn menu(mut self, menu: Menu) -> Self {
2521        self.menus.push(menu);
2522        self
2523    }
2524
2525    /// Set the active menu index (which menu has its dropdown open).
2526    pub fn active_menu(mut self, index: Option<usize>) -> Self {
2527        self.active_menu = index;
2528        self
2529    }
2530
2531    /// Set the highlighted menu index (for keyboard navigation).
2532    pub fn highlighted_menu(mut self, index: usize) -> Self {
2533        self.highlighted_menu = index;
2534        self
2535    }
2536
2537    /// Set the selected item in the active menu.
2538    pub fn selected_item(mut self, index: usize) -> Self {
2539        self.selected_item = index;
2540        self
2541    }
2542
2543    /// Set the callback for command selection.
2544    pub fn on_select(mut self, callback: impl Fn(&'static str) + 'static) -> Self {
2545        self.on_select = Some(Rc::new(callback));
2546        self
2547    }
2548
2549    /// Set the callback for menu changes (opens/closes dropdown).
2550    pub fn on_menu_change(mut self, callback: impl Fn(usize) + 'static) -> Self {
2551        self.on_menu_change = Some(Rc::new(callback));
2552        self
2553    }
2554
2555    /// Set the callback for highlight changes (arrow key navigation).
2556    pub fn on_highlight_change(mut self, callback: impl Fn(usize) + 'static) -> Self {
2557        self.on_highlight_change = Some(Rc::new(callback));
2558        self
2559    }
2560
2561    /// Set the callback for item selection changes within a menu.
2562    pub fn on_item_change(mut self, callback: impl Fn(usize) + 'static) -> Self {
2563        self.on_item_change = Some(Rc::new(callback));
2564        self
2565    }
2566
2567    pub fn build(self) -> View {
2568        View::MenuBar(MenuBarNode {
2569            menus: self.menus,
2570            active_menu: self.active_menu,
2571            highlighted_menu: self.highlighted_menu,
2572            selected_item: self.selected_item,
2573            on_select: self.on_select,
2574            on_menu_change: self.on_menu_change,
2575            on_highlight_change: self.on_highlight_change,
2576            on_item_change: self.on_item_change,
2577        })
2578    }
2579}
2580
2581// =============================================================================
2582// Toast Container
2583// =============================================================================
2584
2585/// Position for toast notifications.
2586#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
2587pub enum ToastPosition {
2588    /// Top-right corner.
2589    TopRight,
2590    /// Top-left corner.
2591    TopLeft,
2592    /// Bottom-right corner (default).
2593    #[default]
2594    BottomRight,
2595    /// Bottom-left corner.
2596    BottomLeft,
2597}
2598
2599/// Severity level for visual rendering of toasts.
2600#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
2601pub enum ToastLevelView {
2602    /// Informational (default).
2603    #[default]
2604    Info,
2605    /// Success.
2606    Success,
2607    /// Warning.
2608    Warning,
2609    /// Error.
2610    Error,
2611}
2612
2613/// A toast item for rendering.
2614#[derive(Clone)]
2615pub struct ToastItem {
2616    /// The message to display.
2617    pub message: String,
2618    /// Severity level.
2619    pub level: ToastLevelView,
2620    /// Progress (0.0 to 1.0) for fade-out animation.
2621    pub progress: f32,
2622}
2623
2624/// A container for displaying toast notifications.
2625#[derive(Clone)]
2626pub struct ToastContainerNode {
2627    /// The toasts to display.
2628    pub toasts: Vec<ToastItem>,
2629    /// Position of the toast container.
2630    pub position: ToastPosition,
2631    /// Maximum number of visible toasts.
2632    pub max_visible: usize,
2633    /// Width of each toast (in characters).
2634    pub width: u16,
2635}
2636
2637/// Builder for ToastContainer views.
2638#[derive(Default)]
2639pub struct ToastContainerBuilder {
2640    toasts: Vec<ToastItem>,
2641    position: ToastPosition,
2642    max_visible: usize,
2643    width: u16,
2644}
2645
2646impl ToastContainerBuilder {
2647    pub fn new() -> Self {
2648        Self {
2649            toasts: Vec::new(),
2650            position: ToastPosition::BottomRight,
2651            max_visible: 5,
2652            width: 40,
2653        }
2654    }
2655
2656    /// Set the toasts to display.
2657    pub fn toasts(mut self, toasts: Vec<ToastItem>) -> Self {
2658        self.toasts = toasts;
2659        self
2660    }
2661
2662    /// Add a toast from the toast queue system.
2663    pub fn from_queue(mut self, queue: &crate::toast::ToastQueue) -> Self {
2664        let toasts = queue.collect();
2665        self.toasts = toasts
2666            .into_iter()
2667            .map(|t| {
2668                let progress = t.remaining_fraction();
2669                let level = match t.level {
2670                    crate::toast::ToastLevel::Info => ToastLevelView::Info,
2671                    crate::toast::ToastLevel::Success => ToastLevelView::Success,
2672                    crate::toast::ToastLevel::Warning => ToastLevelView::Warning,
2673                    crate::toast::ToastLevel::Error => ToastLevelView::Error,
2674                };
2675                ToastItem {
2676                    message: t.message,
2677                    level,
2678                    progress,
2679                }
2680            })
2681            .collect();
2682        self
2683    }
2684
2685    /// Set the position of the toast container.
2686    pub fn position(mut self, position: ToastPosition) -> Self {
2687        self.position = position;
2688        self
2689    }
2690
2691    /// Set the maximum number of visible toasts.
2692    pub fn max_visible(mut self, max: usize) -> Self {
2693        self.max_visible = max;
2694        self
2695    }
2696
2697    /// Set the width of each toast.
2698    pub fn width(mut self, width: u16) -> Self {
2699        self.width = width;
2700        self
2701    }
2702
2703    pub fn build(self) -> View {
2704        View::ToastContainer(ToastContainerNode {
2705            toasts: self.toasts,
2706            position: self.position,
2707            max_visible: self.max_visible,
2708            width: self.width,
2709        })
2710    }
2711}
2712
2713// =============================================================================
2714// Form
2715// =============================================================================
2716
2717/// Callback type for form submission (receives all field values).
2718pub type FormSubmitCallback = Rc<dyn Fn(std::collections::HashMap<String, String>)>;
2719
2720/// A form container that manages field validation.
2721#[derive(Clone)]
2722pub struct FormNode {
2723    /// Child views (typically FormField nodes).
2724    pub children: Vec<View>,
2725    /// Callback when form is submitted (all fields valid).
2726    pub on_submit: Option<FormSubmitCallback>,
2727    /// Spacing between children.
2728    pub spacing: u16,
2729}
2730
2731/// Builder for Form views.
2732#[derive(Default)]
2733pub struct FormBuilder {
2734    children: Vec<View>,
2735    on_submit: Option<FormSubmitCallback>,
2736    spacing: u16,
2737}
2738
2739impl FormBuilder {
2740    pub fn new() -> Self {
2741        Self {
2742            spacing: 1,
2743            ..Default::default()
2744        }
2745    }
2746
2747    /// Add a child view (typically a FormField).
2748    pub fn child(mut self, view: View) -> Self {
2749        self.children.push(view);
2750        self
2751    }
2752
2753    /// Set spacing between children.
2754    pub fn spacing(mut self, spacing: u16) -> Self {
2755        self.spacing = spacing;
2756        self
2757    }
2758
2759    /// Set the submit callback.
2760    pub fn on_submit(
2761        mut self,
2762        callback: impl Fn(std::collections::HashMap<String, String>) + 'static,
2763    ) -> Self {
2764        self.on_submit = Some(Rc::new(callback));
2765        self
2766    }
2767
2768    pub fn build(self) -> View {
2769        View::Form(FormNode {
2770            children: self.children,
2771            on_submit: self.on_submit,
2772            spacing: self.spacing,
2773        })
2774    }
2775}
2776
2777// =============================================================================
2778// Form Field
2779// =============================================================================
2780
2781/// A form field with label, input, and error display.
2782#[derive(Clone)]
2783pub struct FormFieldNode {
2784    /// Field name (identifier).
2785    pub name: String,
2786    /// Display label.
2787    pub label: String,
2788    /// Current value.
2789    pub value: String,
2790    /// Placeholder text.
2791    pub placeholder: String,
2792    /// Error message (if validation failed).
2793    pub error: Option<String>,
2794    /// Whether this is a password field (mask input).
2795    pub password: bool,
2796    /// Callback when value changes.
2797    pub on_change: Option<ChangeCallback>,
2798    /// Callback when field loses focus (for validation).
2799    pub on_blur: Option<Callback>,
2800    /// Cursor position.
2801    pub cursor_pos: usize,
2802}
2803
2804/// Builder for FormField views.
2805#[derive(Default)]
2806pub struct FormFieldBuilder {
2807    name: String,
2808    label: String,
2809    value: String,
2810    placeholder: String,
2811    error: Option<String>,
2812    password: bool,
2813    on_change: Option<ChangeCallback>,
2814    on_blur: Option<Callback>,
2815    cursor_pos: usize,
2816}
2817
2818impl FormFieldBuilder {
2819    pub fn new(name: impl Into<String>) -> Self {
2820        let name = name.into();
2821        Self {
2822            label: name.clone(),
2823            name,
2824            ..Default::default()
2825        }
2826    }
2827
2828    /// Set the display label.
2829    pub fn label(mut self, label: impl Into<String>) -> Self {
2830        self.label = label.into();
2831        self
2832    }
2833
2834    /// Set the current value.
2835    pub fn value(mut self, value: impl Into<String>) -> Self {
2836        self.value = value.into();
2837        self.cursor_pos = self.value.len();
2838        self
2839    }
2840
2841    /// Set the placeholder text.
2842    pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
2843        self.placeholder = placeholder.into();
2844        self
2845    }
2846
2847    /// Set the error message.
2848    pub fn error(mut self, error: Option<String>) -> Self {
2849        self.error = error;
2850        self
2851    }
2852
2853    /// Set whether this is a password field.
2854    pub fn password(mut self, password: bool) -> Self {
2855        self.password = password;
2856        self
2857    }
2858
2859    /// Set the change callback.
2860    pub fn on_change(mut self, callback: impl Fn(String) + 'static) -> Self {
2861        self.on_change = Some(Rc::new(callback));
2862        self
2863    }
2864
2865    /// Set the blur callback.
2866    pub fn on_blur(mut self, callback: impl Fn() + 'static) -> Self {
2867        self.on_blur = Some(Rc::new(callback));
2868        self
2869    }
2870
2871    /// Set the cursor position.
2872    pub fn cursor(mut self, pos: usize) -> Self {
2873        self.cursor_pos = pos;
2874        self
2875    }
2876
2877    pub fn build(self) -> View {
2878        View::FormField(FormFieldNode {
2879            name: self.name,
2880            label: self.label,
2881            value: self.value.clone(),
2882            placeholder: self.placeholder,
2883            error: self.error,
2884            password: self.password,
2885            on_change: self.on_change,
2886            on_blur: self.on_blur,
2887            cursor_pos: self.cursor_pos.min(self.value.len()),
2888        })
2889    }
2890}
2891
2892// =============================================================================
2893// Canvas Widget
2894// =============================================================================
2895
2896/// A canvas node for pixel-level drawing using Kitty graphics protocol.
2897#[derive(Clone)]
2898pub struct CanvasNode {
2899    /// Width in pixels.
2900    pub pixel_width: u16,
2901    /// Height in pixels.
2902    pub pixel_height: u16,
2903    /// Callback to draw on the canvas.
2904    pub on_draw: Option<CanvasDrawCallback>,
2905    /// Unique ID for this canvas (for Kitty image caching).
2906    pub id: u32,
2907}
2908
2909/// Builder for Canvas views.
2910pub struct CanvasBuilder {
2911    pixel_width: u16,
2912    pixel_height: u16,
2913    on_draw: Option<CanvasDrawCallback>,
2914    id: u32,
2915}
2916
2917impl Default for CanvasBuilder {
2918    fn default() -> Self {
2919        use std::sync::atomic::{AtomicU32, Ordering};
2920        static NEXT_ID: AtomicU32 = AtomicU32::new(1);
2921
2922        Self {
2923            pixel_width: 100,
2924            pixel_height: 50,
2925            on_draw: None,
2926            id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
2927        }
2928    }
2929}
2930
2931impl CanvasBuilder {
2932    pub fn new() -> Self {
2933        Self::default()
2934    }
2935
2936    /// Set the canvas width in pixels.
2937    pub fn width(mut self, width: u16) -> Self {
2938        self.pixel_width = width;
2939        self
2940    }
2941
2942    /// Set the canvas height in pixels.
2943    pub fn height(mut self, height: u16) -> Self {
2944        self.pixel_height = height;
2945        self
2946    }
2947
2948    /// Set the draw callback.
2949    ///
2950    /// This callback receives a `DrawContext` and is called each render
2951    /// to draw the canvas content.
2952    pub fn on_draw<F>(mut self, callback: F) -> Self
2953    where
2954        F: Fn(&mut crate::canvas::DrawContext) + 'static,
2955    {
2956        self.on_draw = Some(Rc::new(callback));
2957        self
2958    }
2959
2960    /// Set a specific canvas ID (for manual caching control).
2961    pub fn id(mut self, id: u32) -> Self {
2962        self.id = id;
2963        self
2964    }
2965
2966    pub fn build(self) -> View {
2967        View::Canvas(CanvasNode {
2968            pixel_width: self.pixel_width,
2969            pixel_height: self.pixel_height,
2970            on_draw: self.on_draw,
2971            id: self.id,
2972        })
2973    }
2974}
2975
2976// =============================================================================
2977// Image Widget
2978// =============================================================================
2979
2980/// An image node for displaying images using Kitty graphics protocol.
2981#[derive(Clone)]
2982pub struct ImageNode {
2983    /// Image data source (bytes or file path).
2984    pub source: Option<crate::image::ImageSource>,
2985    /// Unique ID for this image (for Kitty caching).
2986    pub id: u32,
2987    /// Explicit width in cells (overrides auto-detection).
2988    pub cell_width: Option<u16>,
2989    /// Explicit height in cells (overrides auto-detection).
2990    pub cell_height: Option<u16>,
2991    /// Alt text for accessibility / fallback display.
2992    pub alt: Option<String>,
2993}
2994
2995/// Builder for Image views.
2996pub struct ImageBuilder {
2997    source: Option<crate::image::ImageSource>,
2998    id: u32,
2999    cell_width: Option<u16>,
3000    cell_height: Option<u16>,
3001    alt: Option<String>,
3002}
3003
3004impl Default for ImageBuilder {
3005    fn default() -> Self {
3006        Self {
3007            source: None,
3008            id: crate::image::next_image_id(),
3009            cell_width: None,
3010            cell_height: None,
3011            alt: None,
3012        }
3013    }
3014}
3015
3016impl ImageBuilder {
3017    pub fn new() -> Self {
3018        Self::default()
3019    }
3020
3021    /// Set the image data from raw bytes.
3022    ///
3023    /// Supports PNG, JPEG, and GIF formats. Kitty auto-detects the format.
3024    /// GIF animations are handled natively by Kitty.
3025    ///
3026    /// # Example
3027    /// ```rust,ignore
3028    /// View::image()
3029    ///     .data(include_bytes!("logo.png"))
3030    ///     .build()
3031    /// ```
3032    pub fn data(mut self, bytes: &[u8]) -> Self {
3033        self.source = Some(crate::image::ImageSource::Data(bytes.to_vec()));
3034
3035        // Try to detect dimensions for layout
3036        if let Some((w, h)) = crate::image::detect_image_dimensions(bytes) {
3037            let (cw, ch) = crate::image::pixels_to_cells(w, h);
3038            if self.cell_width.is_none() {
3039                self.cell_width = Some(cw);
3040            }
3041            if self.cell_height.is_none() {
3042                self.cell_height = Some(ch);
3043            }
3044        }
3045
3046        self
3047    }
3048
3049    /// Set the image source from a file path.
3050    ///
3051    /// The file is loaded at render time.
3052    ///
3053    /// # Example
3054    /// ```rust,ignore
3055    /// View::image()
3056    ///     .file("assets/animation.gif")
3057    ///     .build()
3058    /// ```
3059    pub fn file(mut self, path: impl Into<String>) -> Self {
3060        self.source = Some(crate::image::ImageSource::File(path.into()));
3061        self
3062    }
3063
3064    /// Set explicit width in terminal cells.
3065    pub fn width(mut self, cells: u16) -> Self {
3066        self.cell_width = Some(cells);
3067        self
3068    }
3069
3070    /// Set explicit height in terminal cells.
3071    pub fn height(mut self, cells: u16) -> Self {
3072        self.cell_height = Some(cells);
3073        self
3074    }
3075
3076    /// Set a specific image ID (for manual caching control).
3077    pub fn id(mut self, id: u32) -> Self {
3078        self.id = id;
3079        self
3080    }
3081
3082    /// Set alt text for accessibility or fallback display.
3083    pub fn alt(mut self, text: impl Into<String>) -> Self {
3084        self.alt = Some(text.into());
3085        self
3086    }
3087
3088    pub fn build(self) -> View {
3089        View::Image(ImageNode {
3090            source: self.source,
3091            id: self.id,
3092            cell_width: self.cell_width,
3093            cell_height: self.cell_height,
3094            alt: self.alt,
3095        })
3096    }
3097}
3098
3099/// Node representing an interactive PTY terminal emulator.
3100///
3101/// **Experimental Preview** - See `View::terminal()` for limitations.
3102#[derive(Clone)]
3103pub struct TerminalNode {
3104    /// Handle to the running PTY process.
3105    pub handle: crate::terminal_state::TerminalHandle,
3106    /// Visible rows (defaults to 24).
3107    pub rows: usize,
3108    /// Visible columns (defaults to 80).
3109    pub cols: usize,
3110    /// Show border around terminal.
3111    pub border: bool,
3112    /// Title displayed in border (if border is enabled).
3113    pub title: Option<String>,
3114    /// Callback invoked when the PTY process exits.
3115    pub on_exit: Option<Callback>,
3116}
3117
3118/// Builder for Terminal views.
3119pub struct TerminalBuilder {
3120    handle: Option<crate::terminal_state::TerminalHandle>,
3121    rows: usize,
3122    cols: usize,
3123    border: bool,
3124    title: Option<String>,
3125    on_exit: Option<Callback>,
3126}
3127
3128impl Default for TerminalBuilder {
3129    fn default() -> Self {
3130        Self {
3131            handle: None,
3132            rows: 24,
3133            cols: 80,
3134            border: true,
3135            title: Some("Terminal".to_string()),
3136            on_exit: None,
3137        }
3138    }
3139}
3140
3141impl TerminalBuilder {
3142    pub fn new() -> Self {
3143        Self::default()
3144    }
3145
3146    /// Set the terminal handle (required).
3147    ///
3148    /// Get a handle from `cx.use_terminal()` in your component.
3149    pub fn handle(mut self, handle: crate::terminal_state::TerminalHandle) -> Self {
3150        self.handle = Some(handle);
3151        self
3152    }
3153
3154    /// Set the number of visible rows (default: 24).
3155    pub fn rows(mut self, rows: usize) -> Self {
3156        self.rows = rows;
3157        self
3158    }
3159
3160    /// Set the number of visible columns (default: 80).
3161    pub fn cols(mut self, cols: usize) -> Self {
3162        self.cols = cols;
3163        self
3164    }
3165
3166    /// Enable or disable border (default: true).
3167    pub fn border(mut self, border: bool) -> Self {
3168        self.border = border;
3169        self
3170    }
3171
3172    /// Set the border title (default: "Terminal").
3173    pub fn title(mut self, title: impl Into<String>) -> Self {
3174        self.title = Some(title.into());
3175        self
3176    }
3177
3178    /// Set a callback to be invoked when the PTY process exits.
3179    pub fn on_exit(mut self, callback: impl Fn() + 'static) -> Self {
3180        self.on_exit = Some(Rc::new(callback));
3181        self
3182    }
3183
3184    pub fn build(self) -> View {
3185        View::Terminal(TerminalNode {
3186            handle: self.handle.expect("Terminal requires a handle (from cx.use_terminal())"),
3187            rows: self.rows,
3188            cols: self.cols,
3189            border: self.border,
3190            title: self.title,
3191            on_exit: self.on_exit,
3192        })
3193    }
3194}