1use std::rc::Rc;
2
3pub type Callback = Rc<dyn Fn()>;
5
6#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
8pub enum Justify {
9 #[default]
11 Start,
12 End,
14 Center,
16 SpaceBetween,
18 SpaceAround,
20}
21
22#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
24pub enum Align {
25 Start,
27 End,
29 Center,
31 #[default]
33 Stretch,
34}
35
36#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
42pub enum LayoutMode {
43 #[default]
47 Flex,
48 }
51
52pub type SelectCallback = Rc<dyn Fn(usize)>;
54
55pub type ChangeCallback = Rc<dyn Fn(String)>;
57
58pub type ToggleCallback = Rc<dyn Fn(bool)>;
60
61pub type CursorChangeCallback = Rc<dyn Fn(usize, usize)>;
63
64pub type CursorPosCallback = Rc<dyn Fn(usize)>;
66
67pub type TreePath = Vec<usize>;
69
70pub type TreeSelectCallback = Rc<dyn Fn(TreePath)>;
72
73pub type TreeActivateCallback = Rc<dyn Fn(TreePath)>;
75
76pub type SortCallback = Rc<dyn Fn(usize, bool)>;
78
79pub type RowActivateCallback = Rc<dyn Fn(usize)>;
81
82pub type CommandCallback = Rc<dyn Fn(&'static str)>;
84
85pub type CanvasDrawCallback = Rc<dyn Fn(&mut crate::canvas::DrawContext)>;
87
88#[derive(Clone)]
90pub enum View {
91 Text(TextNode),
93 VStack(VStackNode),
95 HStack(HStackNode),
97 Button(ButtonNode),
99 Box(BoxNode),
101 Spacer(SpacerNode),
103 List(ListNode),
105 TextInput(TextInputNode),
107 TextArea(TextAreaNode),
109 Checkbox(CheckboxNode),
111 RadioGroup(RadioGroupNode),
113 Modal(ModalNode),
115 Split(SplitNode),
117 Tabs(TabsNode),
119 Tree(TreeNode),
121 Table(TableNode),
123 ProgressBar(ProgressBarNode),
125 StatusBar(StatusBarNode),
127 CommandPalette(CommandPaletteNode),
129 MenuBar(MenuBarNode),
131 ToastContainer(ToastContainerNode),
133 Form(FormNode),
135 FormField(FormFieldNode),
137 Canvas(CanvasNode),
139 Image(ImageNode),
141 Terminal(TerminalNode),
143 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 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 pub fn styled_text(content: impl Into<String>) -> TextBuilder {
281 TextBuilder::new(content)
282 }
283
284 pub fn vstack() -> VStackBuilder {
286 VStackBuilder::new()
287 }
288
289 pub fn hstack() -> HStackBuilder {
291 HStackBuilder::new()
292 }
293
294 pub fn button() -> ButtonBuilder {
296 ButtonBuilder::new()
297 }
298
299 pub fn boxed() -> BoxBuilder {
301 BoxBuilder::new()
302 }
303
304 pub fn spacer() -> Self {
306 View::Spacer(SpacerNode { flex: 1, height: 0 })
307 }
308
309 pub fn spacer_flex(flex: u16) -> Self {
311 View::Spacer(SpacerNode { flex, height: 0 })
312 }
313
314 pub fn gap(height: u16) -> Self {
327 View::Spacer(SpacerNode { flex: 0, height })
328 }
329
330 pub fn list() -> ListBuilder {
332 ListBuilder::new()
333 }
334
335 pub fn text_input() -> TextInputBuilder {
337 TextInputBuilder::new()
338 }
339
340 pub fn checkbox() -> CheckboxBuilder {
342 CheckboxBuilder::new()
343 }
344
345 pub fn radio_group() -> RadioGroupBuilder {
347 RadioGroupBuilder::new()
348 }
349
350 pub fn text_area() -> TextAreaBuilder {
352 TextAreaBuilder::new()
353 }
354
355 pub fn modal() -> ModalBuilder {
357 ModalBuilder::new()
358 }
359
360 pub fn split() -> SplitBuilder {
362 SplitBuilder::new()
363 }
364
365 pub fn tabs() -> TabsBuilder {
367 TabsBuilder::new()
368 }
369
370 pub fn tree() -> TreeBuilder {
372 TreeBuilder::new()
373 }
374
375 pub fn table() -> TableBuilder {
377 TableBuilder::new()
378 }
379
380 pub fn progress_bar() -> ProgressBarBuilder {
382 ProgressBarBuilder::new()
383 }
384
385 pub fn status_bar() -> StatusBarBuilder {
387 StatusBarBuilder::new()
388 }
389
390 pub fn command_palette() -> CommandPaletteBuilder {
392 CommandPaletteBuilder::new()
393 }
394
395 pub fn menu_bar() -> MenuBarBuilder {
397 MenuBarBuilder::new()
398 }
399
400 pub fn toast_container() -> ToastContainerBuilder {
402 ToastContainerBuilder::new()
403 }
404
405 pub fn form() -> FormBuilder {
407 FormBuilder::new()
408 }
409
410 pub fn form_field(name: impl Into<String>) -> FormFieldBuilder {
412 FormFieldBuilder::new(name)
413 }
414
415 pub fn canvas() -> CanvasBuilder {
423 CanvasBuilder::new()
424 }
425
426 pub fn image() -> ImageBuilder {
435 ImageBuilder::new()
436 }
437
438 pub fn terminal() -> TerminalBuilder {
463 TerminalBuilder::new()
464 }
465
466 pub fn empty() -> Self {
468 View::Empty
469 }
470
471 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, View::Split(_) => false, View::Tabs(_) => true, View::Tree(_) => true, View::Table(_) => true, View::CommandPalette(_) => true, View::MenuBar(_) => true, View::FormField(_) => true, View::Terminal(_) => true, _ => false,
490 }
491 }
492
493 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 pub fn min_height(&self) -> Option<u16> {
504 match self {
505 View::Box(n) => n.min_height,
506 _ => None,
507 }
508 }
509
510 pub fn max_height(&self) -> Option<u16> {
512 match self {
513 View::Box(n) => n.max_height,
514 _ => None,
515 }
516 }
517
518 pub fn min_width(&self) -> Option<u16> {
520 match self {
521 View::Box(n) => n.min_width,
522 _ => None,
523 }
524 }
525
526 pub fn max_width(&self) -> Option<u16> {
528 match self {
529 View::Box(n) => n.max_width,
530 _ => None,
531 }
532 }
533
534 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 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), View::Modal(_) => None, View::Spacer(n) => {
578 if n.flex == 0 {
579 Some(n.height) } else {
581 None }
583 }
584 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) => {
594 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 let base_height = 2u16; let error_height = if n.error.is_some() { 1 } else { 0 };
603 Some(base_height + error_height)
604 }
605 View::Canvas(n) => {
606 Some((n.pixel_height / 20).max(1))
609 }
610 View::Image(n) => {
611 n.cell_height.or(Some(5))
613 }
614 View::Terminal(n) => {
615 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 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 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 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 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 None
675 }
676 View::TextArea(_) => {
677 None
680 }
681 View::Checkbox(n) => {
682 Some(n.label.len() as u16 + 4)
684 }
685 View::RadioGroup(n) => {
686 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, View::Spacer(n) => {
692 if n.flex == 0 {
693 Some(0) } else {
695 None }
697 }
698 View::Split(_) => None, View::Tabs(_) => None, View::Tree(_) => None, View::Table(_) => None, View::ProgressBar(n) => {
703 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 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 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, View::MenuBar(n) => {
724 let labels_width: usize = n.menus.iter().map(|m| m.label.len() + 3).sum(); Some(labels_width as u16)
727 }
728 View::ToastContainer(_) => None, View::Form(n) => {
730 n.children.iter().filter_map(|c| c.intrinsic_width()).max()
732 }
733 View::FormField(n) => {
734 let label_width = n.label.len() as u16;
736 let input_width = 20u16; Some(label_width.max(input_width))
738 }
739 View::Canvas(n) => {
740 Some((n.pixel_width / 10).max(1))
743 }
744 View::Image(n) => {
745 n.cell_width.or(Some(10))
747 }
748 View::Terminal(n) => {
749 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#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
760pub enum Orientation {
761 #[default]
763 Horizontal,
764 Vertical,
766}
767
768#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
770pub enum TabPosition {
771 #[default]
773 Top,
774 Bottom,
776}
777
778#[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#[derive(Debug, Clone)]
792pub struct VStackNode {
793 pub children: Vec<View>,
794 pub spacing: u16,
796 pub justify: Justify,
798 pub align: Align,
800 pub layout_mode: LayoutMode,
802}
803
804#[derive(Debug, Clone)]
806pub struct HStackNode {
807 pub children: Vec<View>,
808 pub spacing: u16,
810 pub justify: Justify,
812 pub align: Align,
814 pub layout_mode: LayoutMode,
816}
817
818#[derive(Debug, Clone)]
820pub struct BoxNode {
821 pub child: Option<std::boxed::Box<View>>,
823 pub border: bool,
825 pub padding: u16,
827 pub flex: u16,
829 pub scroll: bool,
831 pub auto_scroll_bottom: bool,
833 pub focusable: bool,
835 pub min_width: Option<u16>,
837 pub max_width: Option<u16>,
839 pub min_height: Option<u16>,
841 pub max_height: Option<u16>,
843}
844
845#[derive(Debug, Clone)]
847pub struct SpacerNode {
848 pub flex: u16,
850 pub height: u16,
852}
853
854#[derive(Clone)]
856pub struct ButtonNode {
857 pub label: String,
858 pub on_press: Option<Callback>,
859}
860
861#[derive(Clone)]
863pub struct ListNode {
864 pub items: Vec<String>,
866 pub selected: usize,
868 pub on_select: Option<SelectCallback>,
870}
871
872#[derive(Clone)]
874pub struct TextInputNode {
875 pub value: String,
877 pub placeholder: String,
879 pub on_change: Option<ChangeCallback>,
881 pub on_cursor_change: Option<CursorPosCallback>,
883 pub on_submit: Option<Callback>,
885 pub on_key_up: Option<Callback>,
887 pub on_key_down: Option<Callback>,
889 pub cursor_pos: usize,
891 pub focused: bool,
893}
894
895#[derive(Clone)]
897pub struct TextAreaNode {
898 pub value: String,
900 pub placeholder: String,
902 pub on_change: Option<ChangeCallback>,
904 pub on_cursor_change: Option<CursorChangeCallback>,
906 pub cursor_line: usize,
908 pub cursor_col: usize,
910 pub rows: u16,
912 pub wrap_width: Option<u16>,
914}
915
916#[derive(Clone)]
918pub struct CheckboxNode {
919 pub checked: bool,
921 pub label: String,
923 pub on_toggle: Option<ToggleCallback>,
925}
926
927#[derive(Clone)]
929pub struct RadioGroupNode {
930 pub options: Vec<String>,
932 pub selected: usize,
934 pub label: Option<String>,
936 pub on_change: Option<SelectCallback>,
938}
939
940#[derive(Clone)]
942pub struct ModalNode {
943 pub visible: bool,
945 pub title: String,
947 pub child: Option<std::boxed::Box<View>>,
949 pub on_dismiss: Option<Callback>,
951 pub width_percent: u16,
953 pub height_percent: u16,
955}
956
957#[derive(Clone)]
959pub struct SplitNode {
960 pub orientation: Orientation,
962 pub first: std::boxed::Box<View>,
964 pub second: std::boxed::Box<View>,
966 pub ratio: f32,
968 pub min_first: Option<u16>,
970 pub min_second: Option<u16>,
972 pub show_divider: bool,
974}
975
976#[derive(Clone)]
978pub struct TabsNode {
979 pub tabs: Vec<String>,
981 pub children: Vec<View>,
983 pub active: usize,
985 pub on_change: Option<SelectCallback>,
987 pub position: TabPosition,
989}
990
991#[derive(Clone, Debug)]
993pub struct TreeItem {
994 pub label: String,
996 pub icon: Option<String>,
998 pub children: Vec<TreeItem>,
1000 pub expanded: bool,
1002}
1003
1004impl TreeItem {
1005 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 pub fn icon(mut self, icon: impl Into<String>) -> Self {
1017 self.icon = Some(icon.into());
1018 self
1019 }
1020
1021 pub fn child(mut self, child: TreeItem) -> Self {
1023 self.children.push(child);
1024 self
1025 }
1026
1027 pub fn expanded(mut self, expanded: bool) -> Self {
1029 self.expanded = expanded;
1030 self
1031 }
1032
1033 pub fn is_leaf(&self) -> bool {
1035 self.children.is_empty()
1036 }
1037}
1038
1039#[derive(Clone)]
1041pub struct TreeNode {
1042 pub items: Vec<TreeItem>,
1044 pub selected: TreePath,
1046 pub on_select: Option<TreeSelectCallback>,
1048 pub on_activate: Option<TreeActivateCallback>,
1050}
1051
1052#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1054pub enum TextAlign {
1055 #[default]
1056 Left,
1057 Center,
1058 Right,
1059}
1060
1061#[derive(Debug, Clone, Copy, PartialEq, Default)]
1063pub enum ColumnWidth {
1064 #[default]
1066 Auto,
1067 Fixed(u16),
1069 Flex(u16),
1071}
1072
1073#[derive(Debug, Clone)]
1075pub struct TableColumn {
1076 pub header: String,
1078 pub width: ColumnWidth,
1080 pub sortable: bool,
1082 pub align: TextAlign,
1084}
1085
1086impl TableColumn {
1087 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 pub fn width(mut self, width: ColumnWidth) -> Self {
1099 self.width = width;
1100 self
1101 }
1102
1103 pub fn sortable(mut self, sortable: bool) -> Self {
1105 self.sortable = sortable;
1106 self
1107 }
1108
1109 pub fn align(mut self, align: TextAlign) -> Self {
1111 self.align = align;
1112 self
1113 }
1114}
1115
1116#[derive(Clone)]
1118pub struct TableNode {
1119 pub columns: Vec<TableColumn>,
1121 pub rows: Vec<Vec<String>>,
1123 pub selected: usize,
1125 pub sort: Option<(usize, bool)>,
1127 pub on_select: Option<SelectCallback>,
1129 pub on_sort: Option<SortCallback>,
1131 pub on_activate: Option<RowActivateCallback>,
1133}
1134
1135#[derive(Clone)]
1137pub struct ProgressBarNode {
1138 pub value: f32,
1140 pub label: Option<String>,
1142 pub show_percentage: bool,
1144 pub width: Option<u16>,
1146 pub filled_char: char,
1148 pub empty_char: char,
1150}
1151
1152#[derive(Clone)]
1154pub struct StatusBarNode {
1155 pub left: String,
1157 pub center: Option<String>,
1159 pub right: Option<String>,
1161 pub bg_color: Option<crossterm::style::Color>,
1163 pub fg_color: Option<crossterm::style::Color>,
1165}
1166
1167#[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 pub fn justify(mut self, justify: Justify) -> Self {
1194 self.justify = justify;
1195 self
1196 }
1197
1198 pub fn align(mut self, align: Align) -> Self {
1200 self.align = align;
1201 self
1202 }
1203
1204 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#[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 pub fn justify(mut self, justify: Justify) -> Self {
1248 self.justify = justify;
1249 self
1250 }
1251
1252 pub fn align(mut self, align: Align) -> Self {
1254 self.align = align;
1255 self
1256 }
1257
1258 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#[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#[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 pub fn auto_scroll_bottom(mut self, auto_scroll: bool) -> Self {
1353 self.auto_scroll_bottom = auto_scroll;
1354 self
1355 }
1356
1357 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 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#[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#[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 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 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 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 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#[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#[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 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 pub fn option(mut self, option: impl Into<String>) -> Self {
1584 self.options.push(option.into());
1585 self
1586 }
1587
1588 pub fn selected(mut self, selected: usize) -> Self {
1590 self.selected = selected;
1591 self
1592 }
1593
1594 pub fn label(mut self, label: impl Into<String>) -> Self {
1596 self.label = Some(label.into());
1597 self
1598 }
1599
1600 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#[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 pub fn color(mut self, color: crossterm::style::Color) -> Self {
1638 self.color = Some(color);
1639 self
1640 }
1641
1642 pub fn bg(mut self, color: crossterm::style::Color) -> Self {
1644 self.bg_color = Some(color);
1645 self
1646 }
1647
1648 pub fn bold(mut self) -> Self {
1650 self.bold = true;
1651 self
1652 }
1653
1654 pub fn italic(mut self) -> Self {
1656 self.italic = true;
1657 self
1658 }
1659
1660 pub fn underline(mut self) -> Self {
1662 self.underline = true;
1663 self
1664 }
1665
1666 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#[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::default()
1703 }
1704 }
1705
1706 pub fn value(mut self, value: impl Into<String>) -> Self {
1708 self.value = value.into();
1709 self
1710 }
1711
1712 pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
1714 self.placeholder = placeholder.into();
1715 self
1716 }
1717
1718 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 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 pub fn cursor_line(mut self, line: usize) -> Self {
1732 self.cursor_line = line;
1733 self
1734 }
1735
1736 pub fn cursor_col(mut self, col: usize) -> Self {
1738 self.cursor_col = col;
1739 self
1740 }
1741
1742 pub fn rows(mut self, rows: u16) -> Self {
1744 self.rows = rows;
1745 self
1746 }
1747
1748 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#[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 pub fn visible(mut self, visible: bool) -> Self {
1791 self.visible = visible;
1792 self
1793 }
1794
1795 pub fn title(mut self, title: impl Into<String>) -> Self {
1797 self.title = title.into();
1798 self
1799 }
1800
1801 pub fn child(mut self, view: View) -> Self {
1803 self.child = Some(view);
1804 self
1805 }
1806
1807 pub fn on_dismiss(mut self, callback: impl Fn() + 'static) -> Self {
1809 self.on_dismiss = Some(Rc::new(callback));
1810 self
1811 }
1812
1813 pub fn width(mut self, percent: u16) -> Self {
1815 self.width_percent = percent.min(100);
1816 self
1817 }
1818
1819 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#[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, show_divider: true,
1854 ..Default::default()
1855 }
1856 }
1857
1858 pub fn horizontal(mut self) -> Self {
1860 self.orientation = Orientation::Horizontal;
1861 self
1862 }
1863
1864 pub fn vertical(mut self) -> Self {
1866 self.orientation = Orientation::Vertical;
1867 self
1868 }
1869
1870 pub fn first(mut self, view: View) -> Self {
1872 self.first = Some(view);
1873 self
1874 }
1875
1876 pub fn second(mut self, view: View) -> Self {
1878 self.second = Some(view);
1879 self
1880 }
1881
1882 pub fn ratio(mut self, ratio: f32) -> Self {
1884 self.ratio = ratio.clamp(0.0, 1.0);
1885 self
1886 }
1887
1888 pub fn min_first(mut self, min: u16) -> Self {
1890 self.min_first = Some(min);
1891 self
1892 }
1893
1894 pub fn min_second(mut self, min: u16) -> Self {
1896 self.min_second = Some(min);
1897 self
1898 }
1899
1900 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#[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 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 pub fn active(mut self, index: usize) -> Self {
1943 self.active = index;
1944 self
1945 }
1946
1947 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 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#[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 pub fn items(mut self, items: Vec<TreeItem>) -> Self {
1986 self.items = items;
1987 self
1988 }
1989
1990 pub fn item(mut self, item: TreeItem) -> Self {
1992 self.items.push(item);
1993 self
1994 }
1995
1996 pub fn selected(mut self, path: TreePath) -> Self {
1998 self.selected = path;
1999 self
2000 }
2001
2002 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 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#[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 pub fn column(mut self, header: impl Into<String>) -> Self {
2043 self.columns.push(TableColumn::new(header));
2044 self
2045 }
2046
2047 pub fn column_with(mut self, column: TableColumn) -> Self {
2049 self.columns.push(column);
2050 self
2051 }
2052
2053 pub fn rows(mut self, rows: Vec<Vec<String>>) -> Self {
2055 self.rows = rows;
2056 self
2057 }
2058
2059 pub fn row(mut self, row: Vec<String>) -> Self {
2061 self.rows.push(row);
2062 self
2063 }
2064
2065 pub fn selected(mut self, index: usize) -> Self {
2067 self.selected = index;
2068 self
2069 }
2070
2071 pub fn sort(mut self, sort: Option<(usize, bool)>) -> Self {
2073 self.sort = sort;
2074 self
2075 }
2076
2077 pub fn sort_by(mut self, column: usize, ascending: bool) -> Self {
2079 self.sort = Some((column, ascending));
2080 self
2081 }
2082
2083 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 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 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#[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 pub fn value(mut self, value: f32) -> Self {
2145 self.value = value.clamp(0.0, 1.0);
2146 self
2147 }
2148
2149 pub fn label(mut self, label: impl Into<String>) -> Self {
2151 self.label = Some(label.into());
2152 self
2153 }
2154
2155 pub fn show_percentage(mut self, show: bool) -> Self {
2157 self.show_percentage = show;
2158 self
2159 }
2160
2161 pub fn width(mut self, width: u16) -> Self {
2164 self.width = Some(width);
2165 self
2166 }
2167
2168 pub fn filled_char(mut self, ch: char) -> Self {
2170 self.filled_char = ch;
2171 self
2172 }
2173
2174 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#[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 pub fn left(mut self, content: impl Into<String>) -> Self {
2209 self.left = content.into();
2210 self
2211 }
2212
2213 pub fn center(mut self, content: impl Into<String>) -> Self {
2215 self.center = Some(content.into());
2216 self
2217 }
2218
2219 pub fn right(mut self, content: impl Into<String>) -> Self {
2221 self.right = Some(content.into());
2222 self
2223 }
2224
2225 pub fn bg(mut self, color: crossterm::style::Color) -> Self {
2227 self.bg_color = Some(color);
2228 self
2229 }
2230
2231 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#[derive(Clone)]
2254pub struct PaletteCommand {
2255 pub id: &'static str,
2257 pub label: String,
2259 pub shortcut: Option<String>,
2261 pub category: Option<String>,
2263}
2264
2265impl PaletteCommand {
2266 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 pub fn shortcut(mut self, shortcut: impl Into<String>) -> Self {
2278 self.shortcut = Some(shortcut.into());
2279 self
2280 }
2281
2282 pub fn category(mut self, category: impl Into<String>) -> Self {
2284 self.category = Some(category.into());
2285 self
2286 }
2287}
2288
2289#[derive(Clone)]
2291pub struct CommandPaletteNode {
2292 pub visible: bool,
2294 pub query: String,
2296 pub commands: Vec<PaletteCommand>,
2298 pub selected: usize,
2300 pub on_query_change: Option<ChangeCallback>,
2302 pub on_select: Option<CommandCallback>,
2304 pub on_dismiss: Option<Callback>,
2306 pub width_percent: u16,
2308 pub height_percent: u16,
2310}
2311
2312#[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 pub fn visible(mut self, visible: bool) -> Self {
2337 self.visible = visible;
2338 self
2339 }
2340
2341 pub fn query(mut self, query: impl Into<String>) -> Self {
2343 self.query = query.into();
2344 self
2345 }
2346
2347 pub fn commands(mut self, commands: Vec<PaletteCommand>) -> Self {
2349 self.commands = commands;
2350 self
2351 }
2352
2353 pub fn command(mut self, command: PaletteCommand) -> Self {
2355 self.commands.push(command);
2356 self
2357 }
2358
2359 pub fn selected(mut self, selected: usize) -> Self {
2361 self.selected = selected;
2362 self
2363 }
2364
2365 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 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 pub fn on_dismiss(mut self, callback: impl Fn() + 'static) -> Self {
2379 self.on_dismiss = Some(Rc::new(callback));
2380 self
2381 }
2382
2383 pub fn width(mut self, percent: u16) -> Self {
2385 self.width_percent = percent.min(100);
2386 self
2387 }
2388
2389 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#[derive(Clone)]
2416pub struct Menu {
2417 pub label: String,
2419 pub items: Vec<MenuItemNode>,
2421}
2422
2423impl Menu {
2424 pub fn new(label: impl Into<String>) -> Self {
2426 Self {
2427 label: label.into(),
2428 items: Vec::new(),
2429 }
2430 }
2431
2432 pub fn item(mut self, item: MenuItemNode) -> Self {
2434 self.items.push(item);
2435 self
2436 }
2437
2438 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 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 pub fn separator(self) -> Self {
2463 self.item(MenuItemNode::Separator)
2464 }
2465}
2466
2467#[derive(Clone)]
2469pub enum MenuItemNode {
2470 Command {
2472 id: &'static str,
2473 label: String,
2474 shortcut: Option<String>,
2475 },
2476 Separator,
2478}
2479
2480#[derive(Clone)]
2482pub struct MenuBarNode {
2483 pub menus: Vec<Menu>,
2485 pub active_menu: Option<usize>,
2487 pub highlighted_menu: usize,
2489 pub selected_item: usize,
2491 pub on_select: Option<CommandCallback>,
2493 pub on_menu_change: Option<SelectCallback>,
2495 pub on_highlight_change: Option<SelectCallback>,
2497 pub on_item_change: Option<SelectCallback>,
2499}
2500
2501#[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 pub fn menu(mut self, menu: Menu) -> Self {
2521 self.menus.push(menu);
2522 self
2523 }
2524
2525 pub fn active_menu(mut self, index: Option<usize>) -> Self {
2527 self.active_menu = index;
2528 self
2529 }
2530
2531 pub fn highlighted_menu(mut self, index: usize) -> Self {
2533 self.highlighted_menu = index;
2534 self
2535 }
2536
2537 pub fn selected_item(mut self, index: usize) -> Self {
2539 self.selected_item = index;
2540 self
2541 }
2542
2543 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 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 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 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#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
2587pub enum ToastPosition {
2588 TopRight,
2590 TopLeft,
2592 #[default]
2594 BottomRight,
2595 BottomLeft,
2597}
2598
2599#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
2601pub enum ToastLevelView {
2602 #[default]
2604 Info,
2605 Success,
2607 Warning,
2609 Error,
2611}
2612
2613#[derive(Clone)]
2615pub struct ToastItem {
2616 pub message: String,
2618 pub level: ToastLevelView,
2620 pub progress: f32,
2622}
2623
2624#[derive(Clone)]
2626pub struct ToastContainerNode {
2627 pub toasts: Vec<ToastItem>,
2629 pub position: ToastPosition,
2631 pub max_visible: usize,
2633 pub width: u16,
2635}
2636
2637#[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 pub fn toasts(mut self, toasts: Vec<ToastItem>) -> Self {
2658 self.toasts = toasts;
2659 self
2660 }
2661
2662 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 pub fn position(mut self, position: ToastPosition) -> Self {
2687 self.position = position;
2688 self
2689 }
2690
2691 pub fn max_visible(mut self, max: usize) -> Self {
2693 self.max_visible = max;
2694 self
2695 }
2696
2697 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
2713pub type FormSubmitCallback = Rc<dyn Fn(std::collections::HashMap<String, String>)>;
2719
2720#[derive(Clone)]
2722pub struct FormNode {
2723 pub children: Vec<View>,
2725 pub on_submit: Option<FormSubmitCallback>,
2727 pub spacing: u16,
2729}
2730
2731#[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 pub fn child(mut self, view: View) -> Self {
2749 self.children.push(view);
2750 self
2751 }
2752
2753 pub fn spacing(mut self, spacing: u16) -> Self {
2755 self.spacing = spacing;
2756 self
2757 }
2758
2759 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#[derive(Clone)]
2783pub struct FormFieldNode {
2784 pub name: String,
2786 pub label: String,
2788 pub value: String,
2790 pub placeholder: String,
2792 pub error: Option<String>,
2794 pub password: bool,
2796 pub on_change: Option<ChangeCallback>,
2798 pub on_blur: Option<Callback>,
2800 pub cursor_pos: usize,
2802}
2803
2804#[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 pub fn label(mut self, label: impl Into<String>) -> Self {
2830 self.label = label.into();
2831 self
2832 }
2833
2834 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 pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
2843 self.placeholder = placeholder.into();
2844 self
2845 }
2846
2847 pub fn error(mut self, error: Option<String>) -> Self {
2849 self.error = error;
2850 self
2851 }
2852
2853 pub fn password(mut self, password: bool) -> Self {
2855 self.password = password;
2856 self
2857 }
2858
2859 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 pub fn on_blur(mut self, callback: impl Fn() + 'static) -> Self {
2867 self.on_blur = Some(Rc::new(callback));
2868 self
2869 }
2870
2871 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#[derive(Clone)]
2898pub struct CanvasNode {
2899 pub pixel_width: u16,
2901 pub pixel_height: u16,
2903 pub on_draw: Option<CanvasDrawCallback>,
2905 pub id: u32,
2907}
2908
2909pub 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 pub fn width(mut self, width: u16) -> Self {
2938 self.pixel_width = width;
2939 self
2940 }
2941
2942 pub fn height(mut self, height: u16) -> Self {
2944 self.pixel_height = height;
2945 self
2946 }
2947
2948 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 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#[derive(Clone)]
2982pub struct ImageNode {
2983 pub source: Option<crate::image::ImageSource>,
2985 pub id: u32,
2987 pub cell_width: Option<u16>,
2989 pub cell_height: Option<u16>,
2991 pub alt: Option<String>,
2993}
2994
2995pub 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 pub fn data(mut self, bytes: &[u8]) -> Self {
3033 self.source = Some(crate::image::ImageSource::Data(bytes.to_vec()));
3034
3035 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 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 pub fn width(mut self, cells: u16) -> Self {
3066 self.cell_width = Some(cells);
3067 self
3068 }
3069
3070 pub fn height(mut self, cells: u16) -> Self {
3072 self.cell_height = Some(cells);
3073 self
3074 }
3075
3076 pub fn id(mut self, id: u32) -> Self {
3078 self.id = id;
3079 self
3080 }
3081
3082 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#[derive(Clone)]
3103pub struct TerminalNode {
3104 pub handle: crate::terminal_state::TerminalHandle,
3106 pub rows: usize,
3108 pub cols: usize,
3110 pub border: bool,
3112 pub title: Option<String>,
3114 pub on_exit: Option<Callback>,
3116}
3117
3118pub 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 pub fn handle(mut self, handle: crate::terminal_state::TerminalHandle) -> Self {
3150 self.handle = Some(handle);
3151 self
3152 }
3153
3154 pub fn rows(mut self, rows: usize) -> Self {
3156 self.rows = rows;
3157 self
3158 }
3159
3160 pub fn cols(mut self, cols: usize) -> Self {
3162 self.cols = cols;
3163 self
3164 }
3165
3166 pub fn border(mut self, border: bool) -> Self {
3168 self.border = border;
3169 self
3170 }
3171
3172 pub fn title(mut self, title: impl Into<String>) -> Self {
3174 self.title = Some(title.into());
3175 self
3176 }
3177
3178 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}