1use std::cell::RefCell;
2use std::rc::Rc;
3
4use crate::widget::Widget;
5
6pub type Callback = Rc<dyn Fn()>;
8
9#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
11pub enum Justify {
12 #[default]
14 Start,
15 End,
17 Center,
19 SpaceBetween,
21 SpaceAround,
23}
24
25#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
27pub enum Align {
28 Start,
30 End,
32 Center,
34 #[default]
36 Stretch,
37}
38
39#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
45pub enum LayoutMode {
46 #[default]
50 Flex,
51 }
54
55pub type SelectCallback = Rc<dyn Fn(usize)>;
57
58pub type ChangeCallback = Rc<dyn Fn(String)>;
60
61pub type ToggleCallback = Rc<dyn Fn(bool)>;
63
64pub type CursorChangeCallback = Rc<dyn Fn(usize, usize)>;
66
67pub type CursorPosCallback = Rc<dyn Fn(usize)>;
69
70pub type TreePath = Vec<usize>;
72
73pub type TreeSelectCallback = Rc<dyn Fn(TreePath)>;
75
76pub type TreeActivateCallback = Rc<dyn Fn(TreePath)>;
78
79pub type SortCallback = Rc<dyn Fn(usize, bool)>;
81
82pub type RowActivateCallback = Rc<dyn Fn(usize)>;
84
85pub type CommandCallback = Rc<dyn Fn(&'static str)>;
87
88pub type CanvasDrawCallback = Rc<dyn Fn(&mut crate::canvas::DrawContext)>;
90
91pub type SliderCallback = Rc<dyn Fn(f64)>;
93
94#[derive(Clone)]
96pub enum View {
97 Text(TextNode),
99 VStack(VStackNode),
101 HStack(HStackNode),
103 Button(ButtonNode),
105 Box(BoxNode),
107 Spacer(SpacerNode),
109 List(ListNode),
111 TextInput(TextInputNode),
113 TextArea(TextAreaNode),
115 Checkbox(CheckboxNode),
117 RadioGroup(RadioGroupNode),
119 Modal(ModalNode),
121 Split(SplitNode),
123 Tabs(TabsNode),
125 Tree(TreeNode),
127 Table(TableNode),
129 ProgressBar(ProgressBarNode),
131 StatusBar(StatusBarNode),
133 CommandPalette(CommandPaletteNode),
135 MenuBar(MenuBarNode),
137 ToastContainer(ToastContainerNode),
139 Form(FormNode),
141 FormField(FormFieldNode),
143 Canvas(CanvasNode),
145 Image(ImageNode),
147 Terminal(TerminalNode),
149 ErrorBoundary(ErrorBoundaryNode),
151 Custom(CustomNode),
153 Slider(SliderNode),
155 Empty,
157}
158
159impl std::fmt::Debug for View {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 match self {
162 View::Text(n) => f.debug_tuple("Text").field(n).finish(),
163 View::VStack(n) => f.debug_tuple("VStack").field(n).finish(),
164 View::HStack(n) => f.debug_tuple("HStack").field(n).finish(),
165 View::Button(n) => f
166 .debug_struct("Button")
167 .field("label", &n.label)
168 .field("on_press", &"<callback>")
169 .finish(),
170 View::Box(n) => f.debug_tuple("Box").field(n).finish(),
171 View::Spacer(n) => f.debug_tuple("Spacer").field(n).finish(),
172 View::List(n) => f
173 .debug_struct("List")
174 .field("items", &n.items.len())
175 .field("selected", &n.selected)
176 .finish(),
177 View::TextInput(n) => f
178 .debug_struct("TextInput")
179 .field("value", &n.value)
180 .finish(),
181 View::TextArea(n) => f
182 .debug_struct("TextArea")
183 .field("value", &n.value)
184 .field("cursor", &(n.cursor_line, n.cursor_col))
185 .finish(),
186 View::Checkbox(n) => f
187 .debug_struct("Checkbox")
188 .field("checked", &n.checked)
189 .field("label", &n.label)
190 .finish(),
191 View::RadioGroup(n) => f
192 .debug_struct("RadioGroup")
193 .field("selected", &n.selected)
194 .field("options", &n.options)
195 .finish(),
196 View::Modal(n) => f
197 .debug_struct("Modal")
198 .field("visible", &n.visible)
199 .field("title", &n.title)
200 .finish(),
201 View::Split(n) => f
202 .debug_struct("Split")
203 .field("orientation", &n.orientation)
204 .field("ratio", &n.ratio)
205 .finish(),
206 View::Tabs(n) => f
207 .debug_struct("Tabs")
208 .field("tabs", &n.tabs)
209 .field("active", &n.active)
210 .finish(),
211 View::Tree(n) => f
212 .debug_struct("Tree")
213 .field("items", &n.items.len())
214 .field("selected", &n.selected)
215 .finish(),
216 View::Table(n) => f
217 .debug_struct("Table")
218 .field("columns", &n.columns.len())
219 .field("rows", &n.rows.len())
220 .field("selected", &n.selected)
221 .finish(),
222 View::ProgressBar(n) => f
223 .debug_struct("ProgressBar")
224 .field("value", &n.value)
225 .field("label", &n.label)
226 .finish(),
227 View::StatusBar(n) => f
228 .debug_struct("StatusBar")
229 .field("left", &n.left)
230 .field("center", &n.center)
231 .field("right", &n.right)
232 .finish(),
233 View::CommandPalette(n) => f
234 .debug_struct("CommandPalette")
235 .field("visible", &n.visible)
236 .field("query", &n.query)
237 .field("selected", &n.selected)
238 .finish(),
239 View::MenuBar(n) => f
240 .debug_struct("MenuBar")
241 .field("menus", &n.menus.len())
242 .field("active_menu", &n.active_menu)
243 .finish(),
244 View::ToastContainer(n) => f
245 .debug_struct("ToastContainer")
246 .field("toasts", &n.toasts.len())
247 .finish(),
248 View::Form(n) => f
249 .debug_struct("Form")
250 .field("children", &n.children.len())
251 .finish(),
252 View::FormField(n) => f
253 .debug_struct("FormField")
254 .field("name", &n.name)
255 .field("label", &n.label)
256 .finish(),
257 View::Canvas(n) => f
258 .debug_struct("Canvas")
259 .field("width", &n.pixel_width)
260 .field("height", &n.pixel_height)
261 .finish(),
262 View::Image(n) => f
263 .debug_struct("Image")
264 .field("has_data", &n.source.is_some())
265 .finish(),
266 View::Terminal(n) => f
267 .debug_struct("Terminal")
268 .field("rows", &n.rows)
269 .field("cols", &n.cols)
270 .field("border", &n.border)
271 .finish(),
272 View::ErrorBoundary(_) => f.debug_struct("ErrorBoundary").finish(),
273 View::Custom(_) => f.debug_struct("Custom").finish(),
274 View::Slider(n) => f
275 .debug_struct("Slider")
276 .field("min", &n.min)
277 .field("max", &n.max)
278 .field("value", &n.value)
279 .field("step", &n.step)
280 .finish(),
281 View::Empty => write!(f, "Empty"),
282 }
283 }
284}
285
286impl View {
287 pub fn text(content: impl Into<String>) -> Self {
289 View::Text(TextNode {
290 content: content.into(),
291 color: None,
292 bg_color: None,
293 bold: false,
294 italic: false,
295 underline: false,
296 dim: false,
297 })
298 }
299
300 pub fn styled_text(content: impl Into<String>) -> TextBuilder {
302 TextBuilder::new(content)
303 }
304
305 pub fn vstack() -> VStackBuilder {
307 VStackBuilder::new()
308 }
309
310 pub fn hstack() -> HStackBuilder {
312 HStackBuilder::new()
313 }
314
315 pub fn button() -> ButtonBuilder {
317 ButtonBuilder::new()
318 }
319
320 pub fn boxed() -> BoxBuilder {
322 BoxBuilder::new()
323 }
324
325 pub fn spacer() -> Self {
327 View::Spacer(SpacerNode { flex: 1, height: 0 })
328 }
329
330 pub fn spacer_flex(flex: u16) -> Self {
332 View::Spacer(SpacerNode { flex, height: 0 })
333 }
334
335 pub fn gap(height: u16) -> Self {
348 View::Spacer(SpacerNode { flex: 0, height })
349 }
350
351 pub fn list() -> ListBuilder {
353 ListBuilder::new()
354 }
355
356 pub fn text_input() -> TextInputBuilder {
358 TextInputBuilder::new()
359 }
360
361 pub fn checkbox() -> CheckboxBuilder {
363 CheckboxBuilder::new()
364 }
365
366 pub fn radio_group() -> RadioGroupBuilder {
368 RadioGroupBuilder::new()
369 }
370
371 pub fn text_area() -> TextAreaBuilder {
373 TextAreaBuilder::new()
374 }
375
376 pub fn modal() -> ModalBuilder {
378 ModalBuilder::new()
379 }
380
381 pub fn split() -> SplitBuilder {
383 SplitBuilder::new()
384 }
385
386 pub fn tabs() -> TabsBuilder {
388 TabsBuilder::new()
389 }
390
391 pub fn tree() -> TreeBuilder {
393 TreeBuilder::new()
394 }
395
396 pub fn table() -> TableBuilder {
398 TableBuilder::new()
399 }
400
401 pub fn progress_bar() -> ProgressBarBuilder {
403 ProgressBarBuilder::new()
404 }
405
406 pub fn status_bar() -> StatusBarBuilder {
408 StatusBarBuilder::new()
409 }
410
411 pub fn command_palette() -> CommandPaletteBuilder {
413 CommandPaletteBuilder::new()
414 }
415
416 pub fn menu_bar() -> MenuBarBuilder {
418 MenuBarBuilder::new()
419 }
420
421 pub fn toast_container() -> ToastContainerBuilder {
423 ToastContainerBuilder::new()
424 }
425
426 pub fn form() -> FormBuilder {
428 FormBuilder::new()
429 }
430
431 pub fn form_field(name: impl Into<String>) -> FormFieldBuilder {
433 FormFieldBuilder::new(name)
434 }
435
436 pub fn canvas() -> CanvasBuilder {
444 CanvasBuilder::new()
445 }
446
447 pub fn image() -> ImageBuilder {
456 ImageBuilder::new()
457 }
458
459 pub fn terminal() -> TerminalBuilder {
484 TerminalBuilder::new()
485 }
486
487 pub fn custom(widget: Rc<RefCell<dyn Widget>>) -> Self {
499 View::Custom(CustomNode { widget })
500 }
501
502 pub fn slider() -> SliderBuilder {
516 SliderBuilder::new()
517 }
518
519 pub fn error_boundary() -> ErrorBoundaryBuilder {
532 ErrorBoundaryBuilder::new()
533 }
534
535 pub fn empty() -> Self {
537 View::Empty
538 }
539
540 pub fn is_focusable(&self) -> bool {
542 match self {
543 View::Button(_) => true,
544 View::Box(node) => node.scroll,
545 View::List(_) => true,
546 View::TextInput(_) => true,
547 View::TextArea(_) => true,
548 View::Checkbox(_) => true,
549 View::RadioGroup(_) => true, View::Split(_) => false, View::Tabs(_) => true, View::Tree(_) => true, View::Table(_) => true, View::CommandPalette(_) => true, View::MenuBar(_) => true, View::FormField(_) => true, View::Terminal(_) => true, View::Slider(_) => true, _ => false,
560 }
561 }
562
563 pub fn flex(&self) -> u16 {
565 match self {
566 View::Box(n) => n.flex,
567 View::Spacer(n) => n.flex,
568 _ => 0,
569 }
570 }
571
572 pub fn min_height(&self) -> Option<u16> {
574 match self {
575 View::Box(n) => n.min_height,
576 _ => None,
577 }
578 }
579
580 pub fn max_height(&self) -> Option<u16> {
582 match self {
583 View::Box(n) => n.max_height,
584 _ => None,
585 }
586 }
587
588 pub fn min_width(&self) -> Option<u16> {
590 match self {
591 View::Box(n) => n.min_width,
592 _ => None,
593 }
594 }
595
596 pub fn max_width(&self) -> Option<u16> {
598 match self {
599 View::Box(n) => n.max_width,
600 _ => None,
601 }
602 }
603
604 pub fn intrinsic_height(&self) -> Option<u16> {
607 match self {
608 View::Text(n) => Some(n.content.lines().count().max(1) as u16),
609 View::Button(_) => Some(1),
610 View::Box(n) => {
611 let border = if n.border { 2 } else { 0 };
612 let padding = n.padding * 2;
613 let inner = n
614 .child
615 .as_ref()
616 .and_then(|c| c.intrinsic_height())
617 .unwrap_or(0);
618 Some(inner + border + padding)
619 }
620 View::VStack(n) => {
621 if n.children.is_empty() {
622 return Some(0);
623 }
624 let spacing = if n.children.len() > 1 {
625 n.spacing * (n.children.len() as u16 - 1)
626 } else {
627 0
628 };
629 let children_height: u16 =
630 n.children.iter().filter_map(|c| c.intrinsic_height()).sum();
631 Some(children_height + spacing)
632 }
633 View::HStack(n) => {
634 n.children
636 .iter()
637 .filter_map(|c| c.intrinsic_height())
638 .max()
639 .or(Some(1))
640 }
641 View::List(n) => Some(n.items.len().max(1) as u16),
642 View::TextInput(_) => Some(1),
643 View::TextArea(n) => Some(n.rows),
644 View::Checkbox(_) => Some(1),
645 View::RadioGroup(n) => Some(n.options.len() as u16), View::Modal(_) => None, View::Spacer(n) => {
648 if n.flex == 0 {
649 Some(n.height) } else {
651 None }
653 }
654 View::Split(_) => None, View::Tabs(_) => None, View::Tree(_) => None, View::Table(_) => None, View::ProgressBar(_) => Some(1), View::StatusBar(_) => Some(1), View::CommandPalette(_) => None, View::MenuBar(_) => Some(1), View::ToastContainer(_) => None, View::Form(n) => {
664 let children_height: u16 =
666 n.children.iter().filter_map(|c| c.intrinsic_height()).sum();
667 Some(children_height)
668 }
669 View::FormField(n) => {
670 let base_height = 2u16; let error_height = if n.error.is_some() { 1 } else { 0 };
673 Some(base_height + error_height)
674 }
675 View::Canvas(n) => {
676 Some((n.pixel_height / 20).max(1))
679 }
680 View::Image(n) => {
681 n.cell_height.or(Some(5))
683 }
684 View::Terminal(n) => {
685 let border = if n.border { 2 } else { 0 };
687 Some(n.rows as u16 + border)
688 }
689 View::ErrorBoundary(n) => n.child.intrinsic_height(),
690 View::Custom(n) => n.widget.borrow().height_hint(80), View::Slider(_) => Some(1), View::Empty => Some(0),
693 }
694 }
695
696 pub fn intrinsic_width(&self) -> Option<u16> {
699 match self {
700 View::Text(n) => {
701 let max_line_width = n.content.lines().map(|l| l.len()).max().unwrap_or(0);
702 Some(max_line_width as u16)
703 }
704 View::Button(n) => {
705 Some(n.label.len() as u16 + 4)
707 }
708 View::Box(n) => {
709 let border = if n.border { 2 } else { 0 };
710 let padding = n.padding * 2;
711 let inner = n
712 .child
713 .as_ref()
714 .and_then(|c| c.intrinsic_width())
715 .unwrap_or(0);
716 Some(inner + border + padding)
717 }
718 View::VStack(n) => {
719 n.children
721 .iter()
722 .filter_map(|c| c.intrinsic_width())
723 .max()
724 .or(Some(1))
725 }
726 View::HStack(n) => {
727 if n.children.is_empty() {
728 return Some(0);
729 }
730 let spacing = if n.children.len() > 1 {
731 n.spacing * (n.children.len() as u16 - 1)
732 } else {
733 0
734 };
735 let children_width: u16 =
736 n.children.iter().filter_map(|c| c.intrinsic_width()).sum();
737 Some(children_width + spacing)
738 }
739 View::List(n) => {
740 let max_item = n.items.iter().map(|i| i.len()).max().unwrap_or(0);
742 Some(max_item as u16 + 2)
743 }
744 View::TextInput(_) => {
745 None
748 }
749 View::TextArea(_) => {
750 None
753 }
754 View::Checkbox(n) => {
755 Some(n.label.len() as u16 + 4)
757 }
758 View::RadioGroup(n) => {
759 let max_option = n.options.iter().map(|o| o.len()).max().unwrap_or(0);
761 Some(max_option as u16 + 4)
762 }
763 View::Modal(_) => None, View::Spacer(n) => {
765 if n.flex == 0 {
766 Some(0) } else {
768 None }
770 }
771 View::Split(_) => None, View::Tabs(_) => None, View::Tree(_) => None, View::Table(_) => None, View::ProgressBar(n) => {
776 let label_width = n.label.as_ref().map(|l| l.len() + 1).unwrap_or(0);
778 let bar_width = n.width.unwrap_or(10) as usize;
779 let percentage_width = if n.show_percentage { 5 } else { 0 };
780 Some((label_width + bar_width + percentage_width) as u16)
781 }
782 View::StatusBar(n) => {
783 let left_width = n.left.len();
785 let center_width = n.center.as_ref().map(|c| c.len()).unwrap_or(0);
786 let right_width = n.right.as_ref().map(|r| r.len()).unwrap_or(0);
787 let spacing = if center_width > 0 || right_width > 0 {
789 2
790 } else {
791 0
792 };
793 Some((left_width + center_width + right_width + spacing) as u16)
794 }
795 View::CommandPalette(_) => None, View::MenuBar(n) => {
797 let labels_width: usize = n.menus.iter().map(|m| m.label.len() + 3).sum(); Some(labels_width as u16)
800 }
801 View::ToastContainer(_) => None, View::Form(n) => {
803 n.children.iter().filter_map(|c| c.intrinsic_width()).max()
805 }
806 View::FormField(n) => {
807 let label_width = n.label.len() as u16;
809 let input_width = 20u16; Some(label_width.max(input_width))
811 }
812 View::Canvas(n) => {
813 Some((n.pixel_width / 10).max(1))
816 }
817 View::Image(n) => {
818 n.cell_width.or(Some(10))
820 }
821 View::Terminal(n) => {
822 let border = if n.border { 2 } else { 0 };
824 Some(n.cols as u16 + border)
825 }
826 View::ErrorBoundary(n) => n.child.intrinsic_width(),
827 View::Custom(n) => n.widget.borrow().width_hint(),
828 View::Slider(n) => {
829 let label_len = n.label.as_ref().map(|l| l.len() + 1).unwrap_or(0) as u16;
831 Some(label_len + 20) }
833 View::Empty => Some(0),
834 }
835 }
836}
837
838#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
840pub enum Orientation {
841 #[default]
843 Horizontal,
844 Vertical,
846}
847
848#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
850pub enum TabPosition {
851 #[default]
853 Top,
854 Bottom,
856}
857
858#[derive(Debug, Clone)]
860pub struct TextNode {
861 pub content: String,
862 pub color: Option<crossterm::style::Color>,
863 pub bg_color: Option<crossterm::style::Color>,
864 pub bold: bool,
865 pub italic: bool,
866 pub underline: bool,
867 pub dim: bool,
868}
869
870#[derive(Debug, Clone)]
872pub struct VStackNode {
873 pub children: Vec<View>,
874 pub spacing: u16,
876 pub justify: Justify,
878 pub align: Align,
880 pub layout_mode: LayoutMode,
882}
883
884#[derive(Debug, Clone)]
886pub struct HStackNode {
887 pub children: Vec<View>,
888 pub spacing: u16,
890 pub justify: Justify,
892 pub align: Align,
894 pub layout_mode: LayoutMode,
896}
897
898#[derive(Debug, Clone)]
900pub struct BoxNode {
901 pub child: Option<std::boxed::Box<View>>,
903 pub border: bool,
905 pub padding: u16,
907 pub flex: u16,
909 pub scroll: bool,
911 pub auto_scroll_bottom: bool,
913 pub focusable: bool,
915 pub min_width: Option<u16>,
917 pub max_width: Option<u16>,
919 pub min_height: Option<u16>,
921 pub max_height: Option<u16>,
923}
924
925#[derive(Debug, Clone)]
927pub struct SpacerNode {
928 pub flex: u16,
930 pub height: u16,
932}
933
934#[derive(Clone)]
936pub struct ButtonNode {
937 pub label: String,
938 pub on_press: Option<Callback>,
939}
940
941#[derive(Clone)]
943pub struct ListNode {
944 pub items: Vec<String>,
946 pub selected: usize,
948 pub on_select: Option<SelectCallback>,
950}
951
952#[derive(Clone)]
954pub struct TextInputNode {
955 pub value: String,
957 pub placeholder: String,
959 pub on_change: Option<ChangeCallback>,
961 pub on_cursor_change: Option<CursorPosCallback>,
963 pub on_submit: Option<Callback>,
965 pub on_key_up: Option<Callback>,
967 pub on_key_down: Option<Callback>,
969 pub cursor_pos: usize,
971 pub focused: bool,
973}
974
975#[derive(Clone)]
977pub struct TextAreaNode {
978 pub value: String,
980 pub placeholder: String,
982 pub on_change: Option<ChangeCallback>,
984 pub on_cursor_change: Option<CursorChangeCallback>,
986 pub cursor_line: usize,
988 pub cursor_col: usize,
990 pub rows: u16,
992 pub wrap_width: Option<u16>,
994}
995
996#[derive(Clone)]
998pub struct CheckboxNode {
999 pub checked: bool,
1001 pub label: String,
1003 pub on_toggle: Option<ToggleCallback>,
1005}
1006
1007#[derive(Clone)]
1009pub struct RadioGroupNode {
1010 pub options: Vec<String>,
1012 pub selected: usize,
1014 pub label: Option<String>,
1016 pub on_change: Option<SelectCallback>,
1018}
1019
1020#[derive(Clone)]
1022pub struct ModalNode {
1023 pub visible: bool,
1025 pub title: String,
1027 pub child: Option<std::boxed::Box<View>>,
1029 pub on_dismiss: Option<Callback>,
1031 pub width_percent: u16,
1033 pub height_percent: u16,
1035}
1036
1037#[derive(Clone)]
1039pub struct SplitNode {
1040 pub orientation: Orientation,
1042 pub first: std::boxed::Box<View>,
1044 pub second: std::boxed::Box<View>,
1046 pub ratio: f32,
1048 pub min_first: Option<u16>,
1050 pub min_second: Option<u16>,
1052 pub show_divider: bool,
1054}
1055
1056#[derive(Clone)]
1058pub struct TabsNode {
1059 pub tabs: Vec<String>,
1061 pub children: Vec<View>,
1063 pub active: usize,
1065 pub on_change: Option<SelectCallback>,
1067 pub position: TabPosition,
1069}
1070
1071#[derive(Clone, Debug)]
1073pub struct TreeItem {
1074 pub label: String,
1076 pub icon: Option<String>,
1078 pub children: Vec<TreeItem>,
1080 pub expanded: bool,
1082}
1083
1084impl TreeItem {
1085 pub fn new(label: impl Into<String>) -> Self {
1087 Self {
1088 label: label.into(),
1089 icon: None,
1090 children: Vec::new(),
1091 expanded: false,
1092 }
1093 }
1094
1095 pub fn icon(mut self, icon: impl Into<String>) -> Self {
1097 self.icon = Some(icon.into());
1098 self
1099 }
1100
1101 pub fn child(mut self, child: TreeItem) -> Self {
1103 self.children.push(child);
1104 self
1105 }
1106
1107 pub fn expanded(mut self, expanded: bool) -> Self {
1109 self.expanded = expanded;
1110 self
1111 }
1112
1113 pub fn is_leaf(&self) -> bool {
1115 self.children.is_empty()
1116 }
1117}
1118
1119#[derive(Clone)]
1121pub struct TreeNode {
1122 pub items: Vec<TreeItem>,
1124 pub selected: TreePath,
1126 pub on_select: Option<TreeSelectCallback>,
1128 pub on_activate: Option<TreeActivateCallback>,
1130}
1131
1132#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1134pub enum TextAlign {
1135 #[default]
1136 Left,
1137 Center,
1138 Right,
1139}
1140
1141#[derive(Debug, Clone, Copy, PartialEq, Default)]
1143pub enum ColumnWidth {
1144 #[default]
1146 Auto,
1147 Fixed(u16),
1149 Flex(u16),
1151}
1152
1153#[derive(Debug, Clone)]
1155pub struct TableColumn {
1156 pub header: String,
1158 pub width: ColumnWidth,
1160 pub sortable: bool,
1162 pub align: TextAlign,
1164}
1165
1166impl TableColumn {
1167 pub fn new(header: impl Into<String>) -> Self {
1169 Self {
1170 header: header.into(),
1171 width: ColumnWidth::Auto,
1172 sortable: false,
1173 align: TextAlign::Left,
1174 }
1175 }
1176
1177 pub fn width(mut self, width: ColumnWidth) -> Self {
1179 self.width = width;
1180 self
1181 }
1182
1183 pub fn sortable(mut self, sortable: bool) -> Self {
1185 self.sortable = sortable;
1186 self
1187 }
1188
1189 pub fn align(mut self, align: TextAlign) -> Self {
1191 self.align = align;
1192 self
1193 }
1194}
1195
1196#[derive(Clone)]
1198pub struct TableNode {
1199 pub columns: Vec<TableColumn>,
1201 pub rows: Vec<Vec<String>>,
1203 pub selected: usize,
1205 pub sort: Option<(usize, bool)>,
1207 pub on_select: Option<SelectCallback>,
1209 pub on_sort: Option<SortCallback>,
1211 pub on_activate: Option<RowActivateCallback>,
1213}
1214
1215#[derive(Clone)]
1217pub struct ProgressBarNode {
1218 pub value: f32,
1220 pub label: Option<String>,
1222 pub show_percentage: bool,
1224 pub width: Option<u16>,
1226 pub filled_char: char,
1228 pub empty_char: char,
1230}
1231
1232#[derive(Clone)]
1234pub struct StatusBarNode {
1235 pub left: String,
1237 pub center: Option<String>,
1239 pub right: Option<String>,
1241 pub bg_color: Option<crossterm::style::Color>,
1243 pub fg_color: Option<crossterm::style::Color>,
1245}
1246
1247#[derive(Debug, Default)]
1249pub struct VStackBuilder {
1250 children: Vec<View>,
1251 spacing: u16,
1252 justify: Justify,
1253 align: Align,
1254 layout_mode: LayoutMode,
1255}
1256
1257impl VStackBuilder {
1258 pub fn new() -> Self {
1259 Self::default()
1260 }
1261
1262 pub fn child(mut self, view: View) -> Self {
1263 self.children.push(view);
1264 self
1265 }
1266
1267 pub fn spacing(mut self, spacing: u16) -> Self {
1268 self.spacing = spacing;
1269 self
1270 }
1271
1272 pub fn justify(mut self, justify: Justify) -> Self {
1274 self.justify = justify;
1275 self
1276 }
1277
1278 pub fn align(mut self, align: Align) -> Self {
1280 self.align = align;
1281 self
1282 }
1283
1284 pub fn layout_mode(mut self, mode: LayoutMode) -> Self {
1286 self.layout_mode = mode;
1287 self
1288 }
1289
1290 pub fn build(self) -> View {
1291 View::VStack(VStackNode {
1292 children: self.children,
1293 spacing: self.spacing,
1294 justify: self.justify,
1295 align: self.align,
1296 layout_mode: self.layout_mode,
1297 })
1298 }
1299}
1300
1301#[derive(Debug, Default)]
1303pub struct HStackBuilder {
1304 children: Vec<View>,
1305 spacing: u16,
1306 justify: Justify,
1307 align: Align,
1308 layout_mode: LayoutMode,
1309}
1310
1311impl HStackBuilder {
1312 pub fn new() -> Self {
1313 Self::default()
1314 }
1315
1316 pub fn child(mut self, view: View) -> Self {
1317 self.children.push(view);
1318 self
1319 }
1320
1321 pub fn spacing(mut self, spacing: u16) -> Self {
1322 self.spacing = spacing;
1323 self
1324 }
1325
1326 pub fn justify(mut self, justify: Justify) -> Self {
1328 self.justify = justify;
1329 self
1330 }
1331
1332 pub fn align(mut self, align: Align) -> Self {
1334 self.align = align;
1335 self
1336 }
1337
1338 pub fn layout_mode(mut self, mode: LayoutMode) -> Self {
1340 self.layout_mode = mode;
1341 self
1342 }
1343
1344 pub fn build(self) -> View {
1345 View::HStack(HStackNode {
1346 children: self.children,
1347 spacing: self.spacing,
1348 justify: self.justify,
1349 align: self.align,
1350 layout_mode: self.layout_mode,
1351 })
1352 }
1353}
1354
1355#[derive(Default)]
1357pub struct ButtonBuilder {
1358 label: String,
1359 on_press: Option<Callback>,
1360}
1361
1362impl ButtonBuilder {
1363 pub fn new() -> Self {
1364 Self::default()
1365 }
1366
1367 pub fn label(mut self, label: impl Into<String>) -> Self {
1368 self.label = label.into();
1369 self
1370 }
1371
1372 pub fn on_press(mut self, callback: impl Fn() + 'static) -> Self {
1373 self.on_press = Some(Rc::new(callback));
1374 self
1375 }
1376
1377 pub fn build(self) -> View {
1378 View::Button(ButtonNode {
1379 label: self.label,
1380 on_press: self.on_press,
1381 })
1382 }
1383}
1384
1385#[derive(Default)]
1387pub struct BoxBuilder {
1388 child: Option<View>,
1389 border: bool,
1390 padding: u16,
1391 flex: u16,
1392 scroll: bool,
1393 auto_scroll_bottom: bool,
1394 focusable: Option<bool>,
1395 min_width: Option<u16>,
1396 max_width: Option<u16>,
1397 min_height: Option<u16>,
1398 max_height: Option<u16>,
1399}
1400
1401impl BoxBuilder {
1402 pub fn new() -> Self {
1403 Self::default()
1404 }
1405
1406 pub fn child(mut self, view: View) -> Self {
1407 self.child = Some(view);
1408 self
1409 }
1410
1411 pub fn border(mut self, border: bool) -> Self {
1412 self.border = border;
1413 self
1414 }
1415
1416 pub fn padding(mut self, padding: u16) -> Self {
1417 self.padding = padding;
1418 self
1419 }
1420
1421 pub fn flex(mut self, flex: u16) -> Self {
1422 self.flex = flex;
1423 self
1424 }
1425
1426 pub fn scroll(mut self, scroll: bool) -> Self {
1427 self.scroll = scroll;
1428 self
1429 }
1430
1431 pub fn auto_scroll_bottom(mut self, auto_scroll: bool) -> Self {
1433 self.auto_scroll_bottom = auto_scroll;
1434 self
1435 }
1436
1437 pub fn focusable(mut self, focusable: bool) -> Self {
1441 self.focusable = Some(focusable);
1442 self
1443 }
1444
1445 pub fn min_width(mut self, width: u16) -> Self {
1446 self.min_width = Some(width);
1447 self
1448 }
1449
1450 pub fn max_width(mut self, width: u16) -> Self {
1451 self.max_width = Some(width);
1452 self
1453 }
1454
1455 pub fn min_height(mut self, height: u16) -> Self {
1456 self.min_height = Some(height);
1457 self
1458 }
1459
1460 pub fn max_height(mut self, height: u16) -> Self {
1461 self.max_height = Some(height);
1462 self
1463 }
1464
1465 pub fn build(self) -> View {
1466 let default_focusable = self.scroll || self.auto_scroll_bottom;
1468 View::Box(BoxNode {
1469 child: self.child.map(std::boxed::Box::new),
1470 border: self.border,
1471 padding: self.padding,
1472 flex: self.flex,
1473 scroll: self.scroll,
1474 auto_scroll_bottom: self.auto_scroll_bottom,
1475 focusable: self.focusable.unwrap_or(default_focusable),
1476 min_width: self.min_width,
1477 max_width: self.max_width,
1478 min_height: self.min_height,
1479 max_height: self.max_height,
1480 })
1481 }
1482}
1483
1484#[derive(Default)]
1486pub struct ListBuilder {
1487 items: Vec<String>,
1488 selected: usize,
1489 on_select: Option<SelectCallback>,
1490}
1491
1492impl ListBuilder {
1493 pub fn new() -> Self {
1494 Self::default()
1495 }
1496
1497 pub fn items(mut self, items: Vec<String>) -> Self {
1498 self.items = items;
1499 self
1500 }
1501
1502 pub fn selected(mut self, selected: usize) -> Self {
1503 self.selected = selected;
1504 self
1505 }
1506
1507 pub fn on_select(mut self, callback: impl Fn(usize) + 'static) -> Self {
1508 self.on_select = Some(Rc::new(callback));
1509 self
1510 }
1511
1512 pub fn build(self) -> View {
1513 View::List(ListNode {
1514 items: self.items,
1515 selected: self.selected,
1516 on_select: self.on_select,
1517 })
1518 }
1519}
1520
1521#[derive(Default)]
1523pub struct TextInputBuilder {
1524 value: String,
1525 placeholder: String,
1526 on_change: Option<ChangeCallback>,
1527 on_cursor_change: Option<CursorPosCallback>,
1528 on_submit: Option<Callback>,
1529 on_key_up: Option<Callback>,
1530 on_key_down: Option<Callback>,
1531 cursor_pos: usize,
1532 focused: bool,
1533}
1534
1535impl TextInputBuilder {
1536 pub fn new() -> Self {
1537 Self::default()
1538 }
1539
1540 pub fn value(mut self, value: impl Into<String>) -> Self {
1541 self.value = value.into();
1542 self.cursor_pos = self.value.len();
1544 self
1545 }
1546
1547 pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
1548 self.placeholder = placeholder.into();
1549 self
1550 }
1551
1552 pub fn on_change(mut self, callback: impl Fn(String) + 'static) -> Self {
1553 self.on_change = Some(Rc::new(callback));
1554 self
1555 }
1556
1557 pub fn on_cursor_change(mut self, callback: impl Fn(usize) + 'static) -> Self {
1558 self.on_cursor_change = Some(Rc::new(callback));
1559 self
1560 }
1561
1562 pub fn on_submit(mut self, callback: impl Fn() + 'static) -> Self {
1563 self.on_submit = Some(Rc::new(callback));
1564 self
1565 }
1566
1567 pub fn on_key_up(mut self, callback: impl Fn() + 'static) -> Self {
1569 self.on_key_up = Some(Rc::new(callback));
1570 self
1571 }
1572
1573 pub fn on_key_down(mut self, callback: impl Fn() + 'static) -> Self {
1575 self.on_key_down = Some(Rc::new(callback));
1576 self
1577 }
1578
1579 pub fn cursor(mut self, pos: usize) -> Self {
1580 self.cursor_pos = pos;
1581 self
1582 }
1583
1584 pub fn focused(mut self, focused: bool) -> Self {
1586 self.focused = focused;
1587 self
1588 }
1589
1590 pub fn build(self) -> View {
1591 View::TextInput(TextInputNode {
1592 value: self.value.clone(),
1593 placeholder: self.placeholder,
1594 on_change: self.on_change,
1595 on_cursor_change: self.on_cursor_change,
1596 on_submit: self.on_submit,
1597 on_key_up: self.on_key_up,
1598 on_key_down: self.on_key_down,
1599 cursor_pos: self.cursor_pos.min(self.value.len()),
1600 focused: self.focused,
1601 })
1602 }
1603}
1604
1605#[derive(Default)]
1607pub struct CheckboxBuilder {
1608 checked: bool,
1609 label: String,
1610 on_toggle: Option<ToggleCallback>,
1611}
1612
1613impl CheckboxBuilder {
1614 pub fn new() -> Self {
1615 Self::default()
1616 }
1617
1618 pub fn checked(mut self, checked: bool) -> Self {
1619 self.checked = checked;
1620 self
1621 }
1622
1623 pub fn label(mut self, label: impl Into<String>) -> Self {
1624 self.label = label.into();
1625 self
1626 }
1627
1628 pub fn on_toggle(mut self, callback: impl Fn(bool) + 'static) -> Self {
1629 self.on_toggle = Some(Rc::new(callback));
1630 self
1631 }
1632
1633 pub fn build(self) -> View {
1634 View::Checkbox(CheckboxNode {
1635 checked: self.checked,
1636 label: self.label,
1637 on_toggle: self.on_toggle,
1638 })
1639 }
1640}
1641
1642#[derive(Default)]
1644pub struct RadioGroupBuilder {
1645 options: Vec<String>,
1646 selected: usize,
1647 label: Option<String>,
1648 on_change: Option<SelectCallback>,
1649}
1650
1651impl RadioGroupBuilder {
1652 pub fn new() -> Self {
1653 Self::default()
1654 }
1655
1656 pub fn options(mut self, options: Vec<impl Into<String>>) -> Self {
1658 self.options = options.into_iter().map(|s| s.into()).collect();
1659 self
1660 }
1661
1662 pub fn option(mut self, option: impl Into<String>) -> Self {
1664 self.options.push(option.into());
1665 self
1666 }
1667
1668 pub fn selected(mut self, selected: usize) -> Self {
1670 self.selected = selected;
1671 self
1672 }
1673
1674 pub fn label(mut self, label: impl Into<String>) -> Self {
1676 self.label = Some(label.into());
1677 self
1678 }
1679
1680 pub fn on_change(mut self, callback: impl Fn(usize) + 'static) -> Self {
1682 self.on_change = Some(Rc::new(callback));
1683 self
1684 }
1685
1686 pub fn build(self) -> View {
1687 View::RadioGroup(RadioGroupNode {
1688 options: self.options,
1689 selected: self.selected,
1690 label: self.label,
1691 on_change: self.on_change,
1692 })
1693 }
1694}
1695
1696#[derive(Debug, Default)]
1698pub struct TextBuilder {
1699 content: String,
1700 color: Option<crossterm::style::Color>,
1701 bg_color: Option<crossterm::style::Color>,
1702 bold: bool,
1703 italic: bool,
1704 underline: bool,
1705 dim: bool,
1706}
1707
1708impl TextBuilder {
1709 pub fn new(content: impl Into<String>) -> Self {
1710 Self {
1711 content: content.into(),
1712 ..Default::default()
1713 }
1714 }
1715
1716 pub fn color(mut self, color: crossterm::style::Color) -> Self {
1718 self.color = Some(color);
1719 self
1720 }
1721
1722 pub fn bg(mut self, color: crossterm::style::Color) -> Self {
1724 self.bg_color = Some(color);
1725 self
1726 }
1727
1728 pub fn bold(mut self) -> Self {
1730 self.bold = true;
1731 self
1732 }
1733
1734 pub fn italic(mut self) -> Self {
1736 self.italic = true;
1737 self
1738 }
1739
1740 pub fn underline(mut self) -> Self {
1742 self.underline = true;
1743 self
1744 }
1745
1746 pub fn dim(mut self) -> Self {
1748 self.dim = true;
1749 self
1750 }
1751
1752 pub fn build(self) -> View {
1753 View::Text(TextNode {
1754 content: self.content,
1755 color: self.color,
1756 bg_color: self.bg_color,
1757 bold: self.bold,
1758 italic: self.italic,
1759 underline: self.underline,
1760 dim: self.dim,
1761 })
1762 }
1763}
1764
1765#[derive(Default)]
1767pub struct TextAreaBuilder {
1768 value: String,
1769 placeholder: String,
1770 on_change: Option<ChangeCallback>,
1771 on_cursor_change: Option<CursorChangeCallback>,
1772 cursor_line: usize,
1773 cursor_col: usize,
1774 rows: u16,
1775 wrap_width: Option<u16>,
1776}
1777
1778impl TextAreaBuilder {
1779 pub fn new() -> Self {
1780 Self {
1781 rows: 5, ..Default::default()
1783 }
1784 }
1785
1786 pub fn value(mut self, value: impl Into<String>) -> Self {
1788 self.value = value.into();
1789 self
1790 }
1791
1792 pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
1794 self.placeholder = placeholder.into();
1795 self
1796 }
1797
1798 pub fn on_change(mut self, callback: impl Fn(String) + 'static) -> Self {
1800 self.on_change = Some(Rc::new(callback));
1801 self
1802 }
1803
1804 pub fn on_cursor_change(mut self, callback: impl Fn(usize, usize) + 'static) -> Self {
1806 self.on_cursor_change = Some(Rc::new(callback));
1807 self
1808 }
1809
1810 pub fn cursor_line(mut self, line: usize) -> Self {
1812 self.cursor_line = line;
1813 self
1814 }
1815
1816 pub fn cursor_col(mut self, col: usize) -> Self {
1818 self.cursor_col = col;
1819 self
1820 }
1821
1822 pub fn rows(mut self, rows: u16) -> Self {
1824 self.rows = rows;
1825 self
1826 }
1827
1828 pub fn wrap_width(mut self, width: u16) -> Self {
1831 self.wrap_width = Some(width);
1832 self
1833 }
1834
1835 pub fn build(self) -> View {
1836 View::TextArea(TextAreaNode {
1837 value: self.value,
1838 placeholder: self.placeholder,
1839 on_change: self.on_change,
1840 on_cursor_change: self.on_cursor_change,
1841 cursor_line: self.cursor_line,
1842 cursor_col: self.cursor_col,
1843 rows: self.rows,
1844 wrap_width: self.wrap_width,
1845 })
1846 }
1847}
1848
1849#[derive(Default)]
1851pub struct ModalBuilder {
1852 visible: bool,
1853 title: String,
1854 child: Option<View>,
1855 on_dismiss: Option<Callback>,
1856 width_percent: u16,
1857 height_percent: u16,
1858}
1859
1860impl ModalBuilder {
1861 pub fn new() -> Self {
1862 Self {
1863 width_percent: 60,
1864 height_percent: 50,
1865 ..Default::default()
1866 }
1867 }
1868
1869 pub fn visible(mut self, visible: bool) -> Self {
1871 self.visible = visible;
1872 self
1873 }
1874
1875 pub fn title(mut self, title: impl Into<String>) -> Self {
1877 self.title = title.into();
1878 self
1879 }
1880
1881 pub fn child(mut self, view: View) -> Self {
1883 self.child = Some(view);
1884 self
1885 }
1886
1887 pub fn on_dismiss(mut self, callback: impl Fn() + 'static) -> Self {
1889 self.on_dismiss = Some(Rc::new(callback));
1890 self
1891 }
1892
1893 pub fn width(mut self, percent: u16) -> Self {
1895 self.width_percent = percent.min(100);
1896 self
1897 }
1898
1899 pub fn height(mut self, percent: u16) -> Self {
1901 self.height_percent = percent.min(100);
1902 self
1903 }
1904
1905 pub fn build(self) -> View {
1906 View::Modal(ModalNode {
1907 visible: self.visible,
1908 title: self.title,
1909 child: self.child.map(std::boxed::Box::new),
1910 on_dismiss: self.on_dismiss,
1911 width_percent: self.width_percent,
1912 height_percent: self.height_percent,
1913 })
1914 }
1915}
1916
1917#[derive(Default)]
1919pub struct SplitBuilder {
1920 orientation: Orientation,
1921 first: Option<View>,
1922 second: Option<View>,
1923 ratio: f32,
1924 min_first: Option<u16>,
1925 min_second: Option<u16>,
1926 show_divider: bool,
1927}
1928
1929impl SplitBuilder {
1930 pub fn new() -> Self {
1931 Self {
1932 ratio: 0.5, show_divider: true,
1934 ..Default::default()
1935 }
1936 }
1937
1938 pub fn horizontal(mut self) -> Self {
1940 self.orientation = Orientation::Horizontal;
1941 self
1942 }
1943
1944 pub fn vertical(mut self) -> Self {
1946 self.orientation = Orientation::Vertical;
1947 self
1948 }
1949
1950 pub fn first(mut self, view: View) -> Self {
1952 self.first = Some(view);
1953 self
1954 }
1955
1956 pub fn second(mut self, view: View) -> Self {
1958 self.second = Some(view);
1959 self
1960 }
1961
1962 pub fn ratio(mut self, ratio: f32) -> Self {
1964 self.ratio = ratio.clamp(0.0, 1.0);
1965 self
1966 }
1967
1968 pub fn min_first(mut self, min: u16) -> Self {
1970 self.min_first = Some(min);
1971 self
1972 }
1973
1974 pub fn min_second(mut self, min: u16) -> Self {
1976 self.min_second = Some(min);
1977 self
1978 }
1979
1980 pub fn show_divider(mut self, show: bool) -> Self {
1982 self.show_divider = show;
1983 self
1984 }
1985
1986 pub fn build(self) -> View {
1987 View::Split(SplitNode {
1988 orientation: self.orientation,
1989 first: std::boxed::Box::new(self.first.unwrap_or(View::Empty)),
1990 second: std::boxed::Box::new(self.second.unwrap_or(View::Empty)),
1991 ratio: self.ratio,
1992 min_first: self.min_first,
1993 min_second: self.min_second,
1994 show_divider: self.show_divider,
1995 })
1996 }
1997}
1998
1999#[derive(Default)]
2001pub struct TabsBuilder {
2002 tabs: Vec<String>,
2003 children: Vec<View>,
2004 active: usize,
2005 on_change: Option<SelectCallback>,
2006 position: TabPosition,
2007}
2008
2009impl TabsBuilder {
2010 pub fn new() -> Self {
2011 Self::default()
2012 }
2013
2014 pub fn tab(mut self, label: impl Into<String>, content: View) -> Self {
2016 self.tabs.push(label.into());
2017 self.children.push(content);
2018 self
2019 }
2020
2021 pub fn active(mut self, index: usize) -> Self {
2023 self.active = index;
2024 self
2025 }
2026
2027 pub fn on_change(mut self, callback: impl Fn(usize) + 'static) -> Self {
2029 self.on_change = Some(Rc::new(callback));
2030 self
2031 }
2032
2033 pub fn position(mut self, position: TabPosition) -> Self {
2035 self.position = position;
2036 self
2037 }
2038
2039 pub fn build(self) -> View {
2040 View::Tabs(TabsNode {
2041 tabs: self.tabs,
2042 children: self.children,
2043 active: self.active,
2044 on_change: self.on_change,
2045 position: self.position,
2046 })
2047 }
2048}
2049
2050#[derive(Default)]
2052pub struct TreeBuilder {
2053 items: Vec<TreeItem>,
2054 selected: TreePath,
2055 on_select: Option<TreeSelectCallback>,
2056 on_activate: Option<TreeActivateCallback>,
2057}
2058
2059impl TreeBuilder {
2060 pub fn new() -> Self {
2061 Self::default()
2062 }
2063
2064 pub fn items(mut self, items: Vec<TreeItem>) -> Self {
2066 self.items = items;
2067 self
2068 }
2069
2070 pub fn item(mut self, item: TreeItem) -> Self {
2072 self.items.push(item);
2073 self
2074 }
2075
2076 pub fn selected(mut self, path: TreePath) -> Self {
2078 self.selected = path;
2079 self
2080 }
2081
2082 pub fn on_select(mut self, callback: impl Fn(TreePath) + 'static) -> Self {
2084 self.on_select = Some(Rc::new(callback));
2085 self
2086 }
2087
2088 pub fn on_activate(mut self, callback: impl Fn(TreePath) + 'static) -> Self {
2090 self.on_activate = Some(Rc::new(callback));
2091 self
2092 }
2093
2094 pub fn build(self) -> View {
2095 View::Tree(TreeNode {
2096 items: self.items,
2097 selected: self.selected,
2098 on_select: self.on_select,
2099 on_activate: self.on_activate,
2100 })
2101 }
2102}
2103
2104#[derive(Default)]
2106pub struct TableBuilder {
2107 columns: Vec<TableColumn>,
2108 rows: Vec<Vec<String>>,
2109 selected: usize,
2110 sort: Option<(usize, bool)>,
2111 on_select: Option<SelectCallback>,
2112 on_sort: Option<SortCallback>,
2113 on_activate: Option<RowActivateCallback>,
2114}
2115
2116impl TableBuilder {
2117 pub fn new() -> Self {
2118 Self::default()
2119 }
2120
2121 pub fn column(mut self, header: impl Into<String>) -> Self {
2123 self.columns.push(TableColumn::new(header));
2124 self
2125 }
2126
2127 pub fn column_with(mut self, column: TableColumn) -> Self {
2129 self.columns.push(column);
2130 self
2131 }
2132
2133 pub fn rows(mut self, rows: Vec<Vec<String>>) -> Self {
2135 self.rows = rows;
2136 self
2137 }
2138
2139 pub fn row(mut self, row: Vec<String>) -> Self {
2141 self.rows.push(row);
2142 self
2143 }
2144
2145 pub fn selected(mut self, index: usize) -> Self {
2147 self.selected = index;
2148 self
2149 }
2150
2151 pub fn sort(mut self, sort: Option<(usize, bool)>) -> Self {
2153 self.sort = sort;
2154 self
2155 }
2156
2157 pub fn sort_by(mut self, column: usize, ascending: bool) -> Self {
2159 self.sort = Some((column, ascending));
2160 self
2161 }
2162
2163 pub fn on_select(mut self, callback: impl Fn(usize) + 'static) -> Self {
2165 self.on_select = Some(Rc::new(callback));
2166 self
2167 }
2168
2169 pub fn on_sort(mut self, callback: impl Fn(usize, bool) + 'static) -> Self {
2171 self.on_sort = Some(Rc::new(callback));
2172 self
2173 }
2174
2175 pub fn on_activate(mut self, callback: impl Fn(usize) + 'static) -> Self {
2177 self.on_activate = Some(Rc::new(callback));
2178 self
2179 }
2180
2181 pub fn build(self) -> View {
2182 View::Table(TableNode {
2183 columns: self.columns,
2184 rows: self.rows,
2185 selected: self.selected,
2186 sort: self.sort,
2187 on_select: self.on_select,
2188 on_sort: self.on_sort,
2189 on_activate: self.on_activate,
2190 })
2191 }
2192}
2193
2194#[derive(Debug, Clone)]
2196pub struct ProgressBarBuilder {
2197 value: f32,
2198 label: Option<String>,
2199 show_percentage: bool,
2200 width: Option<u16>,
2201 filled_char: char,
2202 empty_char: char,
2203}
2204
2205impl Default for ProgressBarBuilder {
2206 fn default() -> Self {
2207 Self {
2208 value: 0.0,
2209 label: None,
2210 show_percentage: true,
2211 width: None,
2212 filled_char: 'â–ˆ',
2213 empty_char: 'â–‘',
2214 }
2215 }
2216}
2217
2218impl ProgressBarBuilder {
2219 pub fn new() -> Self {
2220 Self::default()
2221 }
2222
2223 pub fn value(mut self, value: f32) -> Self {
2225 self.value = value.clamp(0.0, 1.0);
2226 self
2227 }
2228
2229 pub fn label(mut self, label: impl Into<String>) -> Self {
2231 self.label = Some(label.into());
2232 self
2233 }
2234
2235 pub fn show_percentage(mut self, show: bool) -> Self {
2237 self.show_percentage = show;
2238 self
2239 }
2240
2241 pub fn width(mut self, width: u16) -> Self {
2244 self.width = Some(width);
2245 self
2246 }
2247
2248 pub fn filled_char(mut self, ch: char) -> Self {
2250 self.filled_char = ch;
2251 self
2252 }
2253
2254 pub fn empty_char(mut self, ch: char) -> Self {
2256 self.empty_char = ch;
2257 self
2258 }
2259
2260 pub fn build(self) -> View {
2261 View::ProgressBar(ProgressBarNode {
2262 value: self.value,
2263 label: self.label,
2264 show_percentage: self.show_percentage,
2265 width: self.width,
2266 filled_char: self.filled_char,
2267 empty_char: self.empty_char,
2268 })
2269 }
2270}
2271
2272#[derive(Debug, Clone, Default)]
2274pub struct StatusBarBuilder {
2275 left: String,
2276 center: Option<String>,
2277 right: Option<String>,
2278 bg_color: Option<crossterm::style::Color>,
2279 fg_color: Option<crossterm::style::Color>,
2280}
2281
2282impl StatusBarBuilder {
2283 pub fn new() -> Self {
2284 Self::default()
2285 }
2286
2287 pub fn left(mut self, content: impl Into<String>) -> Self {
2289 self.left = content.into();
2290 self
2291 }
2292
2293 pub fn center(mut self, content: impl Into<String>) -> Self {
2295 self.center = Some(content.into());
2296 self
2297 }
2298
2299 pub fn right(mut self, content: impl Into<String>) -> Self {
2301 self.right = Some(content.into());
2302 self
2303 }
2304
2305 pub fn bg(mut self, color: crossterm::style::Color) -> Self {
2307 self.bg_color = Some(color);
2308 self
2309 }
2310
2311 pub fn fg(mut self, color: crossterm::style::Color) -> Self {
2313 self.fg_color = Some(color);
2314 self
2315 }
2316
2317 pub fn build(self) -> View {
2318 View::StatusBar(StatusBarNode {
2319 left: self.left,
2320 center: self.center,
2321 right: self.right,
2322 bg_color: self.bg_color,
2323 fg_color: self.fg_color,
2324 })
2325 }
2326}
2327
2328#[derive(Clone)]
2334pub struct PaletteCommand {
2335 pub id: &'static str,
2337 pub label: String,
2339 pub shortcut: Option<String>,
2341 pub category: Option<String>,
2343}
2344
2345impl PaletteCommand {
2346 pub fn new(id: &'static str, label: impl Into<String>) -> Self {
2348 Self {
2349 id,
2350 label: label.into(),
2351 shortcut: None,
2352 category: None,
2353 }
2354 }
2355
2356 pub fn shortcut(mut self, shortcut: impl Into<String>) -> Self {
2358 self.shortcut = Some(shortcut.into());
2359 self
2360 }
2361
2362 pub fn category(mut self, category: impl Into<String>) -> Self {
2364 self.category = Some(category.into());
2365 self
2366 }
2367}
2368
2369#[derive(Clone)]
2371pub struct CommandPaletteNode {
2372 pub visible: bool,
2374 pub query: String,
2376 pub commands: Vec<PaletteCommand>,
2378 pub selected: usize,
2380 pub on_query_change: Option<ChangeCallback>,
2382 pub on_select: Option<CommandCallback>,
2384 pub on_dismiss: Option<Callback>,
2386 pub width_percent: u16,
2388 pub height_percent: u16,
2390}
2391
2392#[derive(Default)]
2394pub struct CommandPaletteBuilder {
2395 visible: bool,
2396 query: String,
2397 commands: Vec<PaletteCommand>,
2398 selected: usize,
2399 on_query_change: Option<ChangeCallback>,
2400 on_select: Option<CommandCallback>,
2401 on_dismiss: Option<Callback>,
2402 width_percent: u16,
2403 height_percent: u16,
2404}
2405
2406impl CommandPaletteBuilder {
2407 pub fn new() -> Self {
2408 Self {
2409 width_percent: 50,
2410 height_percent: 60,
2411 ..Default::default()
2412 }
2413 }
2414
2415 pub fn visible(mut self, visible: bool) -> Self {
2417 self.visible = visible;
2418 self
2419 }
2420
2421 pub fn query(mut self, query: impl Into<String>) -> Self {
2423 self.query = query.into();
2424 self
2425 }
2426
2427 pub fn commands(mut self, commands: Vec<PaletteCommand>) -> Self {
2429 self.commands = commands;
2430 self
2431 }
2432
2433 pub fn command(mut self, command: PaletteCommand) -> Self {
2435 self.commands.push(command);
2436 self
2437 }
2438
2439 pub fn selected(mut self, selected: usize) -> Self {
2441 self.selected = selected;
2442 self
2443 }
2444
2445 pub fn on_query_change(mut self, callback: impl Fn(String) + 'static) -> Self {
2447 self.on_query_change = Some(Rc::new(callback));
2448 self
2449 }
2450
2451 pub fn on_select(mut self, callback: impl Fn(&'static str) + 'static) -> Self {
2453 self.on_select = Some(Rc::new(callback));
2454 self
2455 }
2456
2457 pub fn on_dismiss(mut self, callback: impl Fn() + 'static) -> Self {
2459 self.on_dismiss = Some(Rc::new(callback));
2460 self
2461 }
2462
2463 pub fn width(mut self, percent: u16) -> Self {
2465 self.width_percent = percent.min(100);
2466 self
2467 }
2468
2469 pub fn height(mut self, percent: u16) -> Self {
2471 self.height_percent = percent.min(100);
2472 self
2473 }
2474
2475 pub fn build(self) -> View {
2476 View::CommandPalette(CommandPaletteNode {
2477 visible: self.visible,
2478 query: self.query,
2479 commands: self.commands,
2480 selected: self.selected,
2481 on_query_change: self.on_query_change,
2482 on_select: self.on_select,
2483 on_dismiss: self.on_dismiss,
2484 width_percent: self.width_percent,
2485 height_percent: self.height_percent,
2486 })
2487 }
2488}
2489
2490#[derive(Clone)]
2496pub struct Menu {
2497 pub label: String,
2499 pub items: Vec<MenuItemNode>,
2501}
2502
2503impl Menu {
2504 pub fn new(label: impl Into<String>) -> Self {
2506 Self {
2507 label: label.into(),
2508 items: Vec::new(),
2509 }
2510 }
2511
2512 pub fn item(mut self, item: MenuItemNode) -> Self {
2514 self.items.push(item);
2515 self
2516 }
2517
2518 pub fn command(self, id: &'static str, label: impl Into<String>) -> Self {
2520 self.item(MenuItemNode::Command {
2521 id,
2522 label: label.into(),
2523 shortcut: None,
2524 })
2525 }
2526
2527 pub fn command_with_shortcut(
2529 self,
2530 id: &'static str,
2531 label: impl Into<String>,
2532 shortcut: impl Into<String>,
2533 ) -> Self {
2534 self.item(MenuItemNode::Command {
2535 id,
2536 label: label.into(),
2537 shortcut: Some(shortcut.into()),
2538 })
2539 }
2540
2541 pub fn separator(self) -> Self {
2543 self.item(MenuItemNode::Separator)
2544 }
2545}
2546
2547#[derive(Clone)]
2549pub enum MenuItemNode {
2550 Command {
2552 id: &'static str,
2553 label: String,
2554 shortcut: Option<String>,
2555 },
2556 Separator,
2558}
2559
2560#[derive(Clone)]
2562pub struct MenuBarNode {
2563 pub menus: Vec<Menu>,
2565 pub active_menu: Option<usize>,
2567 pub highlighted_menu: usize,
2569 pub selected_item: usize,
2571 pub on_select: Option<CommandCallback>,
2573 pub on_menu_change: Option<SelectCallback>,
2575 pub on_highlight_change: Option<SelectCallback>,
2577 pub on_item_change: Option<SelectCallback>,
2579}
2580
2581#[derive(Default)]
2583pub struct MenuBarBuilder {
2584 menus: Vec<Menu>,
2585 active_menu: Option<usize>,
2586 highlighted_menu: usize,
2587 selected_item: usize,
2588 on_select: Option<CommandCallback>,
2589 on_menu_change: Option<SelectCallback>,
2590 on_highlight_change: Option<SelectCallback>,
2591 on_item_change: Option<SelectCallback>,
2592}
2593
2594impl MenuBarBuilder {
2595 pub fn new() -> Self {
2596 Self::default()
2597 }
2598
2599 pub fn menu(mut self, menu: Menu) -> Self {
2601 self.menus.push(menu);
2602 self
2603 }
2604
2605 pub fn active_menu(mut self, index: Option<usize>) -> Self {
2607 self.active_menu = index;
2608 self
2609 }
2610
2611 pub fn highlighted_menu(mut self, index: usize) -> Self {
2613 self.highlighted_menu = index;
2614 self
2615 }
2616
2617 pub fn selected_item(mut self, index: usize) -> Self {
2619 self.selected_item = index;
2620 self
2621 }
2622
2623 pub fn on_select(mut self, callback: impl Fn(&'static str) + 'static) -> Self {
2625 self.on_select = Some(Rc::new(callback));
2626 self
2627 }
2628
2629 pub fn on_menu_change(mut self, callback: impl Fn(usize) + 'static) -> Self {
2631 self.on_menu_change = Some(Rc::new(callback));
2632 self
2633 }
2634
2635 pub fn on_highlight_change(mut self, callback: impl Fn(usize) + 'static) -> Self {
2637 self.on_highlight_change = Some(Rc::new(callback));
2638 self
2639 }
2640
2641 pub fn on_item_change(mut self, callback: impl Fn(usize) + 'static) -> Self {
2643 self.on_item_change = Some(Rc::new(callback));
2644 self
2645 }
2646
2647 pub fn build(self) -> View {
2648 View::MenuBar(MenuBarNode {
2649 menus: self.menus,
2650 active_menu: self.active_menu,
2651 highlighted_menu: self.highlighted_menu,
2652 selected_item: self.selected_item,
2653 on_select: self.on_select,
2654 on_menu_change: self.on_menu_change,
2655 on_highlight_change: self.on_highlight_change,
2656 on_item_change: self.on_item_change,
2657 })
2658 }
2659}
2660
2661#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
2667pub enum ToastPosition {
2668 TopRight,
2670 TopLeft,
2672 #[default]
2674 BottomRight,
2675 BottomLeft,
2677}
2678
2679#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
2681pub enum ToastLevelView {
2682 #[default]
2684 Info,
2685 Success,
2687 Warning,
2689 Error,
2691}
2692
2693#[derive(Clone)]
2695pub struct ToastItem {
2696 pub message: String,
2698 pub level: ToastLevelView,
2700 pub progress: f32,
2702}
2703
2704#[derive(Clone)]
2706pub struct ToastContainerNode {
2707 pub toasts: Vec<ToastItem>,
2709 pub position: ToastPosition,
2711 pub max_visible: usize,
2713 pub width: u16,
2715}
2716
2717#[derive(Default)]
2719pub struct ToastContainerBuilder {
2720 toasts: Vec<ToastItem>,
2721 position: ToastPosition,
2722 max_visible: usize,
2723 width: u16,
2724}
2725
2726impl ToastContainerBuilder {
2727 pub fn new() -> Self {
2728 Self {
2729 toasts: Vec::new(),
2730 position: ToastPosition::BottomRight,
2731 max_visible: 5,
2732 width: 40,
2733 }
2734 }
2735
2736 pub fn toasts(mut self, toasts: Vec<ToastItem>) -> Self {
2738 self.toasts = toasts;
2739 self
2740 }
2741
2742 pub fn from_queue(mut self, queue: &crate::toast::ToastQueue) -> Self {
2744 let toasts = queue.collect();
2745 self.toasts = toasts
2746 .into_iter()
2747 .map(|t| {
2748 let progress = t.remaining_fraction();
2749 let level = match t.level {
2750 crate::toast::ToastLevel::Info => ToastLevelView::Info,
2751 crate::toast::ToastLevel::Success => ToastLevelView::Success,
2752 crate::toast::ToastLevel::Warning => ToastLevelView::Warning,
2753 crate::toast::ToastLevel::Error => ToastLevelView::Error,
2754 };
2755 ToastItem {
2756 message: t.message,
2757 level,
2758 progress,
2759 }
2760 })
2761 .collect();
2762 self
2763 }
2764
2765 pub fn position(mut self, position: ToastPosition) -> Self {
2767 self.position = position;
2768 self
2769 }
2770
2771 pub fn max_visible(mut self, max: usize) -> Self {
2773 self.max_visible = max;
2774 self
2775 }
2776
2777 pub fn width(mut self, width: u16) -> Self {
2779 self.width = width;
2780 self
2781 }
2782
2783 pub fn build(self) -> View {
2784 View::ToastContainer(ToastContainerNode {
2785 toasts: self.toasts,
2786 position: self.position,
2787 max_visible: self.max_visible,
2788 width: self.width,
2789 })
2790 }
2791}
2792
2793pub type FormSubmitCallback = Rc<dyn Fn(std::collections::HashMap<String, String>)>;
2799
2800#[derive(Clone)]
2802pub struct FormNode {
2803 pub children: Vec<View>,
2805 pub on_submit: Option<FormSubmitCallback>,
2807 pub spacing: u16,
2809}
2810
2811#[derive(Default)]
2813pub struct FormBuilder {
2814 children: Vec<View>,
2815 on_submit: Option<FormSubmitCallback>,
2816 spacing: u16,
2817}
2818
2819impl FormBuilder {
2820 pub fn new() -> Self {
2821 Self {
2822 spacing: 1,
2823 ..Default::default()
2824 }
2825 }
2826
2827 pub fn child(mut self, view: View) -> Self {
2829 self.children.push(view);
2830 self
2831 }
2832
2833 pub fn spacing(mut self, spacing: u16) -> Self {
2835 self.spacing = spacing;
2836 self
2837 }
2838
2839 pub fn on_submit(
2841 mut self,
2842 callback: impl Fn(std::collections::HashMap<String, String>) + 'static,
2843 ) -> Self {
2844 self.on_submit = Some(Rc::new(callback));
2845 self
2846 }
2847
2848 pub fn build(self) -> View {
2849 View::Form(FormNode {
2850 children: self.children,
2851 on_submit: self.on_submit,
2852 spacing: self.spacing,
2853 })
2854 }
2855}
2856
2857#[derive(Clone)]
2863pub struct FormFieldNode {
2864 pub name: String,
2866 pub label: String,
2868 pub value: String,
2870 pub placeholder: String,
2872 pub error: Option<String>,
2874 pub password: bool,
2876 pub on_change: Option<ChangeCallback>,
2878 pub on_blur: Option<Callback>,
2880 pub cursor_pos: usize,
2882}
2883
2884#[derive(Default)]
2886pub struct FormFieldBuilder {
2887 name: String,
2888 label: String,
2889 value: String,
2890 placeholder: String,
2891 error: Option<String>,
2892 password: bool,
2893 on_change: Option<ChangeCallback>,
2894 on_blur: Option<Callback>,
2895 cursor_pos: usize,
2896}
2897
2898impl FormFieldBuilder {
2899 pub fn new(name: impl Into<String>) -> Self {
2900 let name = name.into();
2901 Self {
2902 label: name.clone(),
2903 name,
2904 ..Default::default()
2905 }
2906 }
2907
2908 pub fn label(mut self, label: impl Into<String>) -> Self {
2910 self.label = label.into();
2911 self
2912 }
2913
2914 pub fn value(mut self, value: impl Into<String>) -> Self {
2916 self.value = value.into();
2917 self.cursor_pos = self.value.len();
2918 self
2919 }
2920
2921 pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
2923 self.placeholder = placeholder.into();
2924 self
2925 }
2926
2927 pub fn error(mut self, error: Option<String>) -> Self {
2929 self.error = error;
2930 self
2931 }
2932
2933 pub fn password(mut self, password: bool) -> Self {
2935 self.password = password;
2936 self
2937 }
2938
2939 pub fn on_change(mut self, callback: impl Fn(String) + 'static) -> Self {
2941 self.on_change = Some(Rc::new(callback));
2942 self
2943 }
2944
2945 pub fn on_blur(mut self, callback: impl Fn() + 'static) -> Self {
2947 self.on_blur = Some(Rc::new(callback));
2948 self
2949 }
2950
2951 pub fn cursor(mut self, pos: usize) -> Self {
2953 self.cursor_pos = pos;
2954 self
2955 }
2956
2957 pub fn build(self) -> View {
2958 View::FormField(FormFieldNode {
2959 name: self.name,
2960 label: self.label,
2961 value: self.value.clone(),
2962 placeholder: self.placeholder,
2963 error: self.error,
2964 password: self.password,
2965 on_change: self.on_change,
2966 on_blur: self.on_blur,
2967 cursor_pos: self.cursor_pos.min(self.value.len()),
2968 })
2969 }
2970}
2971
2972#[derive(Clone)]
2978pub struct CanvasNode {
2979 pub pixel_width: u16,
2981 pub pixel_height: u16,
2983 pub on_draw: Option<CanvasDrawCallback>,
2985 pub id: u32,
2987}
2988
2989pub struct CanvasBuilder {
2991 pixel_width: u16,
2992 pixel_height: u16,
2993 on_draw: Option<CanvasDrawCallback>,
2994 id: u32,
2995}
2996
2997impl Default for CanvasBuilder {
2998 fn default() -> Self {
2999 use std::sync::atomic::{AtomicU32, Ordering};
3000 static NEXT_ID: AtomicU32 = AtomicU32::new(1);
3001
3002 Self {
3003 pixel_width: 100,
3004 pixel_height: 50,
3005 on_draw: None,
3006 id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
3007 }
3008 }
3009}
3010
3011impl CanvasBuilder {
3012 pub fn new() -> Self {
3013 Self::default()
3014 }
3015
3016 pub fn width(mut self, width: u16) -> Self {
3018 self.pixel_width = width;
3019 self
3020 }
3021
3022 pub fn height(mut self, height: u16) -> Self {
3024 self.pixel_height = height;
3025 self
3026 }
3027
3028 pub fn on_draw<F>(mut self, callback: F) -> Self
3033 where
3034 F: Fn(&mut crate::canvas::DrawContext) + 'static,
3035 {
3036 self.on_draw = Some(Rc::new(callback));
3037 self
3038 }
3039
3040 pub fn id(mut self, id: u32) -> Self {
3042 self.id = id;
3043 self
3044 }
3045
3046 pub fn build(self) -> View {
3047 View::Canvas(CanvasNode {
3048 pixel_width: self.pixel_width,
3049 pixel_height: self.pixel_height,
3050 on_draw: self.on_draw,
3051 id: self.id,
3052 })
3053 }
3054}
3055
3056#[derive(Clone)]
3062pub struct ImageNode {
3063 pub source: Option<crate::image::ImageSource>,
3065 pub id: u32,
3067 pub cell_width: Option<u16>,
3069 pub cell_height: Option<u16>,
3071 pub alt: Option<String>,
3073}
3074
3075pub struct ImageBuilder {
3077 source: Option<crate::image::ImageSource>,
3078 id: u32,
3079 cell_width: Option<u16>,
3080 cell_height: Option<u16>,
3081 alt: Option<String>,
3082}
3083
3084impl Default for ImageBuilder {
3085 fn default() -> Self {
3086 Self {
3087 source: None,
3088 id: crate::image::next_image_id(),
3089 cell_width: None,
3090 cell_height: None,
3091 alt: None,
3092 }
3093 }
3094}
3095
3096impl ImageBuilder {
3097 pub fn new() -> Self {
3098 Self::default()
3099 }
3100
3101 pub fn data(mut self, bytes: &[u8]) -> Self {
3113 self.source = Some(crate::image::ImageSource::Data(bytes.to_vec()));
3114
3115 if let Some((w, h)) = crate::image::detect_image_dimensions(bytes) {
3117 let (cw, ch) = crate::image::pixels_to_cells(w, h);
3118 if self.cell_width.is_none() {
3119 self.cell_width = Some(cw);
3120 }
3121 if self.cell_height.is_none() {
3122 self.cell_height = Some(ch);
3123 }
3124 }
3125
3126 self
3127 }
3128
3129 pub fn file(mut self, path: impl Into<String>) -> Self {
3140 self.source = Some(crate::image::ImageSource::File(path.into()));
3141 self
3142 }
3143
3144 pub fn width(mut self, cells: u16) -> Self {
3146 self.cell_width = Some(cells);
3147 self
3148 }
3149
3150 pub fn height(mut self, cells: u16) -> Self {
3152 self.cell_height = Some(cells);
3153 self
3154 }
3155
3156 pub fn id(mut self, id: u32) -> Self {
3158 self.id = id;
3159 self
3160 }
3161
3162 pub fn alt(mut self, text: impl Into<String>) -> Self {
3164 self.alt = Some(text.into());
3165 self
3166 }
3167
3168 pub fn build(self) -> View {
3169 View::Image(ImageNode {
3170 source: self.source,
3171 id: self.id,
3172 cell_width: self.cell_width,
3173 cell_height: self.cell_height,
3174 alt: self.alt,
3175 })
3176 }
3177}
3178
3179#[derive(Clone)]
3183pub struct TerminalNode {
3184 pub handle: crate::terminal_state::TerminalHandle,
3186 pub rows: usize,
3188 pub cols: usize,
3190 pub border: bool,
3192 pub title: Option<String>,
3194 pub on_exit: Option<Callback>,
3196}
3197
3198pub struct TerminalBuilder {
3200 handle: Option<crate::terminal_state::TerminalHandle>,
3201 rows: usize,
3202 cols: usize,
3203 border: bool,
3204 title: Option<String>,
3205 on_exit: Option<Callback>,
3206}
3207
3208impl Default for TerminalBuilder {
3209 fn default() -> Self {
3210 Self {
3211 handle: None,
3212 rows: 24,
3213 cols: 80,
3214 border: true,
3215 title: Some("Terminal".to_string()),
3216 on_exit: None,
3217 }
3218 }
3219}
3220
3221impl TerminalBuilder {
3222 pub fn new() -> Self {
3223 Self::default()
3224 }
3225
3226 pub fn handle(mut self, handle: crate::terminal_state::TerminalHandle) -> Self {
3230 self.handle = Some(handle);
3231 self
3232 }
3233
3234 pub fn rows(mut self, rows: usize) -> Self {
3236 self.rows = rows;
3237 self
3238 }
3239
3240 pub fn cols(mut self, cols: usize) -> Self {
3242 self.cols = cols;
3243 self
3244 }
3245
3246 pub fn border(mut self, border: bool) -> Self {
3248 self.border = border;
3249 self
3250 }
3251
3252 pub fn title(mut self, title: impl Into<String>) -> Self {
3254 self.title = Some(title.into());
3255 self
3256 }
3257
3258 pub fn on_exit(mut self, callback: impl Fn() + 'static) -> Self {
3260 self.on_exit = Some(Rc::new(callback));
3261 self
3262 }
3263
3264 pub fn build(self) -> View {
3265 View::Terminal(TerminalNode {
3266 handle: self.handle.expect("Terminal requires a handle (from cx.use_terminal())"),
3267 rows: self.rows,
3268 cols: self.cols,
3269 border: self.border,
3270 title: self.title,
3271 on_exit: self.on_exit,
3272 })
3273 }
3274}
3275
3276#[derive(Clone)]
3284pub struct ErrorBoundaryNode {
3285 pub child: Box<View>,
3287 pub fallback: Box<View>,
3289}
3290
3291#[derive(Default)]
3293pub struct ErrorBoundaryBuilder {
3294 child: Option<View>,
3295 fallback: Option<View>,
3296}
3297
3298impl ErrorBoundaryBuilder {
3299 pub fn new() -> Self {
3300 Self {
3301 child: None,
3302 fallback: None,
3303 }
3304 }
3305
3306 pub fn child(mut self, child: View) -> Self {
3308 self.child = Some(child);
3309 self
3310 }
3311
3312 pub fn fallback(mut self, fallback: View) -> Self {
3314 self.fallback = Some(fallback);
3315 self
3316 }
3317
3318 pub fn build(self) -> View {
3319 View::ErrorBoundary(ErrorBoundaryNode {
3320 child: Box::new(self.child.unwrap_or(View::Empty)),
3321 fallback: Box::new(
3322 self.fallback
3323 .unwrap_or_else(|| View::text("[error boundary: child panicked]")),
3324 ),
3325 })
3326 }
3327}
3328
3329#[derive(Clone)]
3337pub struct CustomNode {
3338 pub widget: Rc<RefCell<dyn Widget>>,
3339}
3340
3341#[derive(Clone)]
3345pub struct SliderNode {
3346 pub min: f64,
3347 pub max: f64,
3348 pub value: f64,
3349 pub step: f64,
3350 pub label: Option<String>,
3351 pub on_change: Option<SliderCallback>,
3352 pub color: Option<crossterm::style::Color>,
3353}
3354
3355#[derive(Default)]
3357pub struct SliderBuilder {
3358 min: f64,
3359 max: f64,
3360 value: f64,
3361 step: f64,
3362 label: Option<String>,
3363 on_change: Option<SliderCallback>,
3364 color: Option<crossterm::style::Color>,
3365}
3366
3367impl SliderBuilder {
3368 pub fn new() -> Self {
3369 Self {
3370 min: 0.0,
3371 max: 100.0,
3372 value: 0.0,
3373 step: 1.0,
3374 label: None,
3375 on_change: None,
3376 color: None,
3377 }
3378 }
3379
3380 pub fn min(mut self, min: f64) -> Self {
3381 self.min = min;
3382 self
3383 }
3384
3385 pub fn max(mut self, max: f64) -> Self {
3386 self.max = max;
3387 self
3388 }
3389
3390 pub fn value(mut self, value: f64) -> Self {
3391 self.value = value;
3392 self
3393 }
3394
3395 pub fn step(mut self, step: f64) -> Self {
3396 self.step = step;
3397 self
3398 }
3399
3400 pub fn label(mut self, label: impl Into<String>) -> Self {
3401 self.label = Some(label.into());
3402 self
3403 }
3404
3405 pub fn on_change(mut self, callback: impl Fn(f64) + 'static) -> Self {
3406 self.on_change = Some(Rc::new(callback));
3407 self
3408 }
3409
3410 pub fn color(mut self, color: crossterm::style::Color) -> Self {
3411 self.color = Some(color);
3412 self
3413 }
3414
3415 pub fn build(self) -> View {
3416 View::Slider(SliderNode {
3417 min: self.min,
3418 max: self.max,
3419 value: self.value.clamp(self.min, self.max),
3420 step: self.step,
3421 label: self.label,
3422 on_change: self.on_change,
3423 color: self.color,
3424 })
3425 }
3426}