pub struct ListBuilder { /* private fields */ }Expand description
Builder for List views.
Implementations§
Source§impl ListBuilder
impl ListBuilder
pub fn new() -> Self
Sourcepub fn items(self, items: Vec<String>) -> Self
pub fn items(self, items: Vec<String>) -> Self
Examples found in repository?
examples/05_todo_list.rs (line 91)
20 fn render(&self, cx: Scope) -> View {
21 let items = state!(cx, || {
22 vec![
23 "Learn Telex".to_string(),
24 "Build something cool".to_string(),
25 ]
26 });
27 let input_value = state!(cx, String::new);
28 let selected = state!(cx, || 0usize);
29 let show_help = state!(cx, || false);
30
31 // F1 toggles help
32 cx.use_command(
33 KeyBinding::key(KeyCode::F(1)),
34 with!(show_help => move || show_help.update(|v| *v = !*v)),
35 );
36
37 // Add new item on submit
38 let on_submit = with!(items, input_value => move || {
39 let text = input_value.get();
40 if !text.is_empty() {
41 items.update(|v| v.push(text));
42 input_value.set(String::new());
43 }
44 });
45
46 // Handle input changes
47 let on_change = with!(input_value => move |text: String| {
48 input_value.set(text);
49 });
50
51 // Delete selected item
52 let on_delete = with!(items, selected => move || {
53 let idx = selected.get();
54 items.update(|v| {
55 if idx < v.len() {
56 v.remove(idx);
57 // Adjust selection if needed
58 if idx > 0 && idx >= v.len() {
59 selected.set(idx - 1);
60 }
61 }
62 });
63 });
64
65 // Track selection
66 let on_select = with!(selected => move |idx: usize| {
67 selected.set(idx);
68 });
69
70 let item_count = items.get().len();
71
72 View::vstack()
73 .child(
74 View::styled_text("Todo List")
75 .color(Color::Cyan)
76 .bold()
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::text_input()
82 .value(input_value.get())
83 .placeholder("Type something to add...")
84 .on_change(on_change)
85 .on_submit(on_submit)
86 .build(),
87 )
88 .child(View::gap(1))
89 .child(if item_count > 0 {
90 View::list()
91 .items(items.get())
92 .selected(selected.get())
93 .on_select(on_select)
94 .build()
95 } else {
96 View::styled_text("No items yet").dim().build()
97 })
98 .child(View::gap(1))
99 .child(
100 View::hstack()
101 .child(View::button().label("Delete").on_press(on_delete).build())
102 .build(),
103 )
104 .child(View::gap(1))
105 .child(
106 View::styled_text("Tab navigate • Enter add/select • F1 help • Ctrl+Q quit")
107 .dim()
108 .build(),
109 )
110 .child(
111 View::modal()
112 .visible(show_help.get())
113 .title("Example 05: Todo List")
114 .on_dismiss(with!(show_help => move || show_help.set(false)))
115 .child(
116 View::vstack()
117 .child(View::styled_text("What you're seeing").bold().build())
118 .child(View::text("• View::text_input() for text entry"))
119 .child(View::text("• View::list() for displaying items"))
120 .child(View::text("• on_submit callback for Enter key"))
121 .child(View::gap(1))
122 .child(View::styled_text("Key concepts").bold().build())
123 .child(View::text("• Controlled input: value + on_change"))
124 .child(View::text("• Vec<String> state for list items"))
125 .child(View::text("• Conditional rendering (empty state)"))
126 .child(View::gap(1))
127 .child(View::styled_text("Try this").bold().build())
128 .child(View::text("• Type something and press Enter to add"))
129 .child(View::text("• Use ↑/↓ to select, then Delete button"))
130 .child(View::text("• Delete all items to see empty state"))
131 .child(View::gap(1))
132 .child(View::styled_text("Next up").bold().build())
133 .child(View::text("→ 06_log_viewer: streaming text content"))
134 .child(View::gap(1))
135 .child(View::styled_text("Press Escape to close").dim().build())
136 .build(),
137 )
138 .build(),
139 )
140 .build()
141 }More examples
examples/13_split_panes.rs (line 74)
20 fn render(&self, cx: Scope) -> View {
21 let show_help = state!(cx, || false);
22
23 // F1 toggles help
24 cx.use_command(
25 KeyBinding::key(KeyCode::F(1)),
26 with!(show_help => move || show_help.update(|v| *v = !*v)),
27 );
28
29 let selected_item = state!(cx, || 0usize);
30
31 let items = vec![
32 "README.md".to_string(),
33 "Cargo.toml".to_string(),
34 "src/".to_string(),
35 "src/main.rs".to_string(),
36 "src/lib.rs".to_string(),
37 "tests/".to_string(),
38 ];
39
40 let on_select = with!(selected_item => move |idx: usize| {
41 selected_item.set(idx);
42 });
43
44 let detail_text = match selected_item.get() {
45 0 => "# README\n\nThis is a demo of split panes.\n\nThe left panel shows a file list,\nthe right panel shows details.",
46 1 => "[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2021\"",
47 2 => "Directory: src/\n\nContains the main source files.",
48 3 => "fn main() {\n println!(\"Hello, world!\");\n}",
49 4 => "pub mod utils;\npub mod widgets;",
50 5 => "Directory: tests/\n\nContains integration tests.",
51 _ => "Select an item to see details.",
52 };
53
54 // Horizontal split: file list on left, details on right
55 View::vstack()
56 .child(
57 View::boxed()
58 .flex(1)
59 .child(
60 View::split()
61 .horizontal()
62 .ratio(0.3)
63 .min_first(15)
64 .first(
65 View::vstack()
66 .child(
67 View::styled_text("Files")
68 .color(Color::Cyan)
69 .bold()
70 .build(),
71 )
72 .child(
73 View::list()
74 .items(items)
75 .selected(selected_item.get())
76 .on_select(on_select)
77 .build(),
78 )
79 .child(View::gap(1))
80 .build(),
81 )
82 .second(
83 View::vstack()
84 .child(
85 View::styled_text("Details")
86 .color(Color::Green)
87 .bold()
88 .build(),
89 )
90 .child(
91 View::boxed()
92 .border(true)
93 .flex(1)
94 .child(View::text(detail_text))
95 .build(),
96 )
97 .build(),
98 )
99 .build(),
100 )
101 .build(),
102 )
103 .child(
104 View::styled_text("↑↓: select file | F1 help | Ctrl+Q: quit")
105 .dim()
106 .build(),
107 )
108 .child(
109 View::modal()
110 .visible(show_help.get())
111 .title("Example 13: Split Panes")
112 .on_dismiss(with!(show_help => move || show_help.set(false)))
113 .child(
114 View::vstack()
115 .child(View::styled_text("What you're seeing").bold().build())
116 .child(View::text("• Horizontal split: file list / details"))
117 .child(View::text("• ratio(0.3) = 30% left, 70% right"))
118 .child(View::text("• min_first(15) sets minimum pane width"))
119 .child(View::gap(1))
120 .child(View::styled_text("Key concepts").bold().build())
121 .child(View::text("• View::split() creates resizable panes"))
122 .child(View::text("• .horizontal() or .vertical() orientation"))
123 .child(View::text("• .first() and .second() set pane content"))
124 .child(View::text("• Splits can be nested"))
125 .child(View::gap(1))
126 .child(View::styled_text("Try this").bold().build())
127 .child(View::text("• Select different files to see details"))
128 .child(View::text("• Resize terminal to see layout adapt"))
129 .child(View::gap(1))
130 .child(View::styled_text("Next up").bold().build())
131 .child(View::text("→ 14_tabs: tabbed interfaces"))
132 .child(View::gap(1))
133 .child(View::styled_text("Press Escape to close").dim().build())
134 .build(),
135 )
136 .build(),
137 )
138 .build()
139 }examples/07_file_browser.rs (line 120)
49 fn render(&self, cx: Scope) -> View {
50 let current_path =
51 state!(cx, || std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/")));
52 let selected = state!(cx, || 0usize);
53 let show_file_info = state!(cx, || false);
54 let selected_file_path = state!(cx, String::new);
55 let show_help = state!(cx, || false);
56 let entries = list_directory(¤t_path.get());
57
58 // F1 toggles help
59 cx.use_command(
60 KeyBinding::key(KeyCode::F(1)),
61 with!(show_help => move || show_help.update(|v| *v = !*v)),
62 );
63
64 // Track selection (just updates index, doesn't navigate)
65 let on_select = with!(selected => move |idx: usize| {
66 selected.set(idx);
67 });
68
69 // Dismiss modal
70 let on_dismiss = with!(show_file_info => move || show_file_info.set(false));
71
72 // Open directory or show file info on Enter
73 let entries_for_cmd = entries.clone();
74 cx.use_command(
75 KeyBinding::key(KeyCode::Enter),
76 with!(current_path, selected, show_file_info, selected_file_path => move || {
77 let idx = selected.get();
78 if idx < entries_for_cmd.len() {
79 let entry = &entries_for_cmd[idx];
80 let path = current_path.get();
81
82 if entry == ".." {
83 if let Some(parent) = path.parent() {
84 current_path.set(parent.to_path_buf());
85 selected.set(0);
86 }
87 } else if entry.ends_with('/') {
88 let dir_name = entry.trim_end_matches('/');
89 let new_path = path.join(dir_name);
90 current_path.set(new_path);
91 selected.set(0);
92 } else {
93 // It's a file - show info modal
94 let full_path = path.join(entry);
95 selected_file_path.set(full_path.to_string_lossy().to_string());
96 show_file_info.set(true);
97 }
98 }
99 }),
100 );
101
102 let path_display = current_path.get().to_string_lossy().to_string();
103
104 View::vstack()
105 .child(
106 View::styled_text("File Browser")
107 .color(Color::Cyan)
108 .bold()
109 .build(),
110 )
111 .child(View::gap(1))
112 .child(
113 View::styled_text(&path_display)
114 .color(Color::Yellow)
115 .build(),
116 )
117 .child(View::gap(1))
118 .child(
119 View::list()
120 .items(entries)
121 .selected(selected.get())
122 .on_select(on_select)
123 .build(),
124 )
125 .child(View::gap(1))
126 .child(
127 View::styled_text("↑/↓ navigate • Enter open • F1 help • Ctrl+Q quit")
128 .dim()
129 .build(),
130 )
131 .child(
132 View::modal()
133 .visible(show_file_info.get())
134 .title("File")
135 .width(60)
136 .height(20)
137 .on_dismiss(on_dismiss)
138 .child(
139 View::vstack()
140 .child(View::text(selected_file_path.get()))
141 .child(View::gap(1))
142 .child(View::styled_text("Press Escape to close").dim().build())
143 .build(),
144 )
145 .build(),
146 )
147 .child(
148 View::modal()
149 .visible(show_help.get())
150 .title("Example 07: File Browser")
151 .on_dismiss(with!(show_help => move || show_help.set(false)))
152 .child(
153 View::vstack()
154 .child(View::styled_text("What you're seeing").bold().build())
155 .child(View::text("• Real filesystem navigation"))
156 .child(View::text("• cx.use_command() for keyboard shortcuts"))
157 .child(View::text("• Modal for file details"))
158 .child(View::gap(1))
159 .child(View::styled_text("Key concepts").bold().build())
160 .child(View::text("• std::fs::read_dir for directory listing"))
161 .child(View::text(
162 "• KeyBinding::key(KeyCode::Enter) for Enter handling",
163 ))
164 .child(View::text("• Directories shown with trailing /"))
165 .child(View::gap(1))
166 .child(View::styled_text("Try this").bold().build())
167 .child(View::text("• Navigate into directories with Enter"))
168 .child(View::text("• Go up with '..' entry"))
169 .child(View::text("• Select a file to see its path"))
170 .child(View::gap(1))
171 .child(View::styled_text("Next up").bold().build())
172 .child(View::text(
173 "→ 08_system_monitor: multiple concurrent streams",
174 ))
175 .child(View::gap(1))
176 .child(View::styled_text("Press Escape to close").dim().build())
177 .build(),
178 )
179 .build(),
180 )
181 .build()
182 }examples/03_theme_switcher.rs (line 115)
20 fn render(&self, cx: Scope) -> View {
21 let selected = state!(cx, || 0usize);
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 let theme_names = vec![
31 "Dark".to_string(),
32 "Light".to_string(),
33 "Nord".to_string(),
34 "Monokai".to_string(),
35 "Catppuccin Mocha".to_string(),
36 "Catppuccin Latte".to_string(),
37 "Dracula".to_string(),
38 "Gruvbox Dark".to_string(),
39 "Solarized Dark".to_string(),
40 "Rosé Pine".to_string(),
41 "Tokyo Night".to_string(),
42 "HaX0R Blue".to_string(),
43 "HaX0R Green".to_string(),
44 "HaX0R Red".to_string(),
45 ];
46
47 // Apply theme when selection changes
48 let on_select = with!(selected => move |idx: usize| {
49 selected.set(idx);
50 let theme = match idx {
51 0 => Theme::dark(),
52 1 => Theme::light(),
53 2 => Theme::nord(),
54 3 => Theme::monokai(),
55 4 => Theme::catppuccin_mocha(),
56 5 => Theme::catppuccin_latte(),
57 6 => Theme::dracula(),
58 7 => Theme::gruvbox_dark(),
59 8 => Theme::solarized_dark(),
60 9 => Theme::rose_pine(),
61 10 => Theme::tokyo_night(),
62 11 => Theme::hax0r_blue(),
63 12 => Theme::hax0r_green(),
64 _ => Theme::hax0r_red(),
65 };
66 set_theme(theme);
67 });
68
69 let theme = current_theme();
70 let true_color = supports_true_color();
71
72 let mut stack = View::vstack();
73
74 // Show warning if true color isn't supported
75 if !true_color {
76 let term = terminal_name().unwrap_or_else(|| "Unknown".to_string());
77 stack = stack
78 .child(
79 View::styled_text(format!("Warning: {} doesn't support true color", term))
80 .color(theme.warning)
81 .bold()
82 .build(),
83 )
84 .child(
85 View::styled_text("Only 'Dark' and 'Light' themes will display correctly")
86 .color(theme.muted)
87 .build(),
88 )
89 .child(View::gap(1));
90 }
91
92 stack
93 .child(
94 View::styled_text("Theme Switcher")
95 .color(theme.primary)
96 .bold()
97 .build(),
98 )
99 .child(
100 View::styled_text("Select a theme from the list")
101 .color(theme.muted)
102 .italic()
103 .build(),
104 )
105 .child(View::gap(1))
106 .child(
107 View::hstack()
108 .spacing(2)
109 .child(
110 View::boxed()
111 .border(true)
112 .min_width(25)
113 .child(
114 View::list()
115 .items(theme_names)
116 .selected(selected.get())
117 .on_select(on_select)
118 .build(),
119 )
120 .build(),
121 )
122 .child(
123 View::boxed()
124 .border(true)
125 .padding(1)
126 .child(
127 View::vstack()
128 .child(View::styled_text("Preview").bold().build())
129 .child(View::gap(1))
130 .child(
131 View::hstack()
132 .child(
133 View::styled_text("Primary")
134 .color(theme.primary)
135 .build(),
136 )
137 .child(View::text(" "))
138 .child(
139 View::styled_text("Secondary")
140 .color(theme.secondary)
141 .build(),
142 )
143 .build(),
144 )
145 .child(
146 View::hstack()
147 .child(
148 View::styled_text("Muted")
149 .color(theme.muted)
150 .build(),
151 )
152 .child(View::text(" "))
153 .child(
154 View::styled_text("Success")
155 .color(theme.success)
156 .build(),
157 )
158 .build(),
159 )
160 .child(
161 View::hstack()
162 .child(
163 View::styled_text("Warning")
164 .color(theme.warning)
165 .build(),
166 )
167 .child(View::text(" "))
168 .child(
169 View::styled_text("Error")
170 .color(theme.error)
171 .build(),
172 )
173 .build(),
174 )
175 .build(),
176 )
177 .build(),
178 )
179 .build(),
180 )
181 .child(View::gap(1))
182 .child(
183 View::styled_text("↑/↓ select • F1 help • Ctrl+Q quit")
184 .color(theme.muted)
185 .build(),
186 )
187 .child(
188 View::modal()
189 .visible(show_help.get())
190 .title("Example 03: Theme Switcher")
191 .on_dismiss(with!(show_help => move || show_help.set(false)))
192 .child(
193 View::vstack()
194 .child(View::styled_text("What you're seeing").bold().build())
195 .child(View::text("• Built-in theme system with 14 themes"))
196 .child(View::text("• View::list() for selection UI"))
197 .child(View::text("• Live preview as you navigate"))
198 .child(View::gap(1))
199 .child(View::styled_text("Key concepts").bold().build())
200 .child(View::text("• current_theme() gets active theme colors"))
201 .child(View::text("• set_theme() changes theme globally"))
202 .child(View::text(
203 "• Themes provide semantic colors (primary, error, etc.)",
204 ))
205 .child(View::gap(1))
206 .child(View::styled_text("Try this").bold().build())
207 .child(View::text(
208 "• Navigate with ↑/↓ to see themes change instantly",
209 ))
210 .child(View::text(
211 "• Notice the preview panel updates with theme colors",
212 ))
213 .child(View::gap(1))
214 .child(View::styled_text("Next up").bold().build())
215 .child(View::text("→ 04_timer: streaming data without interaction"))
216 .child(View::gap(1))
217 .child(View::styled_text("Press Escape to close").dim().build())
218 .build(),
219 )
220 .build(),
221 )
222 .build()
223 }Sourcepub fn selected(self, selected: usize) -> Self
pub fn selected(self, selected: usize) -> Self
Examples found in repository?
examples/05_todo_list.rs (line 92)
20 fn render(&self, cx: Scope) -> View {
21 let items = state!(cx, || {
22 vec![
23 "Learn Telex".to_string(),
24 "Build something cool".to_string(),
25 ]
26 });
27 let input_value = state!(cx, String::new);
28 let selected = state!(cx, || 0usize);
29 let show_help = state!(cx, || false);
30
31 // F1 toggles help
32 cx.use_command(
33 KeyBinding::key(KeyCode::F(1)),
34 with!(show_help => move || show_help.update(|v| *v = !*v)),
35 );
36
37 // Add new item on submit
38 let on_submit = with!(items, input_value => move || {
39 let text = input_value.get();
40 if !text.is_empty() {
41 items.update(|v| v.push(text));
42 input_value.set(String::new());
43 }
44 });
45
46 // Handle input changes
47 let on_change = with!(input_value => move |text: String| {
48 input_value.set(text);
49 });
50
51 // Delete selected item
52 let on_delete = with!(items, selected => move || {
53 let idx = selected.get();
54 items.update(|v| {
55 if idx < v.len() {
56 v.remove(idx);
57 // Adjust selection if needed
58 if idx > 0 && idx >= v.len() {
59 selected.set(idx - 1);
60 }
61 }
62 });
63 });
64
65 // Track selection
66 let on_select = with!(selected => move |idx: usize| {
67 selected.set(idx);
68 });
69
70 let item_count = items.get().len();
71
72 View::vstack()
73 .child(
74 View::styled_text("Todo List")
75 .color(Color::Cyan)
76 .bold()
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::text_input()
82 .value(input_value.get())
83 .placeholder("Type something to add...")
84 .on_change(on_change)
85 .on_submit(on_submit)
86 .build(),
87 )
88 .child(View::gap(1))
89 .child(if item_count > 0 {
90 View::list()
91 .items(items.get())
92 .selected(selected.get())
93 .on_select(on_select)
94 .build()
95 } else {
96 View::styled_text("No items yet").dim().build()
97 })
98 .child(View::gap(1))
99 .child(
100 View::hstack()
101 .child(View::button().label("Delete").on_press(on_delete).build())
102 .build(),
103 )
104 .child(View::gap(1))
105 .child(
106 View::styled_text("Tab navigate • Enter add/select • F1 help • Ctrl+Q quit")
107 .dim()
108 .build(),
109 )
110 .child(
111 View::modal()
112 .visible(show_help.get())
113 .title("Example 05: Todo List")
114 .on_dismiss(with!(show_help => move || show_help.set(false)))
115 .child(
116 View::vstack()
117 .child(View::styled_text("What you're seeing").bold().build())
118 .child(View::text("• View::text_input() for text entry"))
119 .child(View::text("• View::list() for displaying items"))
120 .child(View::text("• on_submit callback for Enter key"))
121 .child(View::gap(1))
122 .child(View::styled_text("Key concepts").bold().build())
123 .child(View::text("• Controlled input: value + on_change"))
124 .child(View::text("• Vec<String> state for list items"))
125 .child(View::text("• Conditional rendering (empty state)"))
126 .child(View::gap(1))
127 .child(View::styled_text("Try this").bold().build())
128 .child(View::text("• Type something and press Enter to add"))
129 .child(View::text("• Use ↑/↓ to select, then Delete button"))
130 .child(View::text("• Delete all items to see empty state"))
131 .child(View::gap(1))
132 .child(View::styled_text("Next up").bold().build())
133 .child(View::text("→ 06_log_viewer: streaming text content"))
134 .child(View::gap(1))
135 .child(View::styled_text("Press Escape to close").dim().build())
136 .build(),
137 )
138 .build(),
139 )
140 .build()
141 }More examples
examples/13_split_panes.rs (line 75)
20 fn render(&self, cx: Scope) -> View {
21 let show_help = state!(cx, || false);
22
23 // F1 toggles help
24 cx.use_command(
25 KeyBinding::key(KeyCode::F(1)),
26 with!(show_help => move || show_help.update(|v| *v = !*v)),
27 );
28
29 let selected_item = state!(cx, || 0usize);
30
31 let items = vec![
32 "README.md".to_string(),
33 "Cargo.toml".to_string(),
34 "src/".to_string(),
35 "src/main.rs".to_string(),
36 "src/lib.rs".to_string(),
37 "tests/".to_string(),
38 ];
39
40 let on_select = with!(selected_item => move |idx: usize| {
41 selected_item.set(idx);
42 });
43
44 let detail_text = match selected_item.get() {
45 0 => "# README\n\nThis is a demo of split panes.\n\nThe left panel shows a file list,\nthe right panel shows details.",
46 1 => "[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2021\"",
47 2 => "Directory: src/\n\nContains the main source files.",
48 3 => "fn main() {\n println!(\"Hello, world!\");\n}",
49 4 => "pub mod utils;\npub mod widgets;",
50 5 => "Directory: tests/\n\nContains integration tests.",
51 _ => "Select an item to see details.",
52 };
53
54 // Horizontal split: file list on left, details on right
55 View::vstack()
56 .child(
57 View::boxed()
58 .flex(1)
59 .child(
60 View::split()
61 .horizontal()
62 .ratio(0.3)
63 .min_first(15)
64 .first(
65 View::vstack()
66 .child(
67 View::styled_text("Files")
68 .color(Color::Cyan)
69 .bold()
70 .build(),
71 )
72 .child(
73 View::list()
74 .items(items)
75 .selected(selected_item.get())
76 .on_select(on_select)
77 .build(),
78 )
79 .child(View::gap(1))
80 .build(),
81 )
82 .second(
83 View::vstack()
84 .child(
85 View::styled_text("Details")
86 .color(Color::Green)
87 .bold()
88 .build(),
89 )
90 .child(
91 View::boxed()
92 .border(true)
93 .flex(1)
94 .child(View::text(detail_text))
95 .build(),
96 )
97 .build(),
98 )
99 .build(),
100 )
101 .build(),
102 )
103 .child(
104 View::styled_text("↑↓: select file | F1 help | Ctrl+Q: quit")
105 .dim()
106 .build(),
107 )
108 .child(
109 View::modal()
110 .visible(show_help.get())
111 .title("Example 13: Split Panes")
112 .on_dismiss(with!(show_help => move || show_help.set(false)))
113 .child(
114 View::vstack()
115 .child(View::styled_text("What you're seeing").bold().build())
116 .child(View::text("• Horizontal split: file list / details"))
117 .child(View::text("• ratio(0.3) = 30% left, 70% right"))
118 .child(View::text("• min_first(15) sets minimum pane width"))
119 .child(View::gap(1))
120 .child(View::styled_text("Key concepts").bold().build())
121 .child(View::text("• View::split() creates resizable panes"))
122 .child(View::text("• .horizontal() or .vertical() orientation"))
123 .child(View::text("• .first() and .second() set pane content"))
124 .child(View::text("• Splits can be nested"))
125 .child(View::gap(1))
126 .child(View::styled_text("Try this").bold().build())
127 .child(View::text("• Select different files to see details"))
128 .child(View::text("• Resize terminal to see layout adapt"))
129 .child(View::gap(1))
130 .child(View::styled_text("Next up").bold().build())
131 .child(View::text("→ 14_tabs: tabbed interfaces"))
132 .child(View::gap(1))
133 .child(View::styled_text("Press Escape to close").dim().build())
134 .build(),
135 )
136 .build(),
137 )
138 .build()
139 }examples/07_file_browser.rs (line 121)
49 fn render(&self, cx: Scope) -> View {
50 let current_path =
51 state!(cx, || std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/")));
52 let selected = state!(cx, || 0usize);
53 let show_file_info = state!(cx, || false);
54 let selected_file_path = state!(cx, String::new);
55 let show_help = state!(cx, || false);
56 let entries = list_directory(¤t_path.get());
57
58 // F1 toggles help
59 cx.use_command(
60 KeyBinding::key(KeyCode::F(1)),
61 with!(show_help => move || show_help.update(|v| *v = !*v)),
62 );
63
64 // Track selection (just updates index, doesn't navigate)
65 let on_select = with!(selected => move |idx: usize| {
66 selected.set(idx);
67 });
68
69 // Dismiss modal
70 let on_dismiss = with!(show_file_info => move || show_file_info.set(false));
71
72 // Open directory or show file info on Enter
73 let entries_for_cmd = entries.clone();
74 cx.use_command(
75 KeyBinding::key(KeyCode::Enter),
76 with!(current_path, selected, show_file_info, selected_file_path => move || {
77 let idx = selected.get();
78 if idx < entries_for_cmd.len() {
79 let entry = &entries_for_cmd[idx];
80 let path = current_path.get();
81
82 if entry == ".." {
83 if let Some(parent) = path.parent() {
84 current_path.set(parent.to_path_buf());
85 selected.set(0);
86 }
87 } else if entry.ends_with('/') {
88 let dir_name = entry.trim_end_matches('/');
89 let new_path = path.join(dir_name);
90 current_path.set(new_path);
91 selected.set(0);
92 } else {
93 // It's a file - show info modal
94 let full_path = path.join(entry);
95 selected_file_path.set(full_path.to_string_lossy().to_string());
96 show_file_info.set(true);
97 }
98 }
99 }),
100 );
101
102 let path_display = current_path.get().to_string_lossy().to_string();
103
104 View::vstack()
105 .child(
106 View::styled_text("File Browser")
107 .color(Color::Cyan)
108 .bold()
109 .build(),
110 )
111 .child(View::gap(1))
112 .child(
113 View::styled_text(&path_display)
114 .color(Color::Yellow)
115 .build(),
116 )
117 .child(View::gap(1))
118 .child(
119 View::list()
120 .items(entries)
121 .selected(selected.get())
122 .on_select(on_select)
123 .build(),
124 )
125 .child(View::gap(1))
126 .child(
127 View::styled_text("↑/↓ navigate • Enter open • F1 help • Ctrl+Q quit")
128 .dim()
129 .build(),
130 )
131 .child(
132 View::modal()
133 .visible(show_file_info.get())
134 .title("File")
135 .width(60)
136 .height(20)
137 .on_dismiss(on_dismiss)
138 .child(
139 View::vstack()
140 .child(View::text(selected_file_path.get()))
141 .child(View::gap(1))
142 .child(View::styled_text("Press Escape to close").dim().build())
143 .build(),
144 )
145 .build(),
146 )
147 .child(
148 View::modal()
149 .visible(show_help.get())
150 .title("Example 07: File Browser")
151 .on_dismiss(with!(show_help => move || show_help.set(false)))
152 .child(
153 View::vstack()
154 .child(View::styled_text("What you're seeing").bold().build())
155 .child(View::text("• Real filesystem navigation"))
156 .child(View::text("• cx.use_command() for keyboard shortcuts"))
157 .child(View::text("• Modal for file details"))
158 .child(View::gap(1))
159 .child(View::styled_text("Key concepts").bold().build())
160 .child(View::text("• std::fs::read_dir for directory listing"))
161 .child(View::text(
162 "• KeyBinding::key(KeyCode::Enter) for Enter handling",
163 ))
164 .child(View::text("• Directories shown with trailing /"))
165 .child(View::gap(1))
166 .child(View::styled_text("Try this").bold().build())
167 .child(View::text("• Navigate into directories with Enter"))
168 .child(View::text("• Go up with '..' entry"))
169 .child(View::text("• Select a file to see its path"))
170 .child(View::gap(1))
171 .child(View::styled_text("Next up").bold().build())
172 .child(View::text(
173 "→ 08_system_monitor: multiple concurrent streams",
174 ))
175 .child(View::gap(1))
176 .child(View::styled_text("Press Escape to close").dim().build())
177 .build(),
178 )
179 .build(),
180 )
181 .build()
182 }examples/03_theme_switcher.rs (line 116)
20 fn render(&self, cx: Scope) -> View {
21 let selected = state!(cx, || 0usize);
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 let theme_names = vec![
31 "Dark".to_string(),
32 "Light".to_string(),
33 "Nord".to_string(),
34 "Monokai".to_string(),
35 "Catppuccin Mocha".to_string(),
36 "Catppuccin Latte".to_string(),
37 "Dracula".to_string(),
38 "Gruvbox Dark".to_string(),
39 "Solarized Dark".to_string(),
40 "Rosé Pine".to_string(),
41 "Tokyo Night".to_string(),
42 "HaX0R Blue".to_string(),
43 "HaX0R Green".to_string(),
44 "HaX0R Red".to_string(),
45 ];
46
47 // Apply theme when selection changes
48 let on_select = with!(selected => move |idx: usize| {
49 selected.set(idx);
50 let theme = match idx {
51 0 => Theme::dark(),
52 1 => Theme::light(),
53 2 => Theme::nord(),
54 3 => Theme::monokai(),
55 4 => Theme::catppuccin_mocha(),
56 5 => Theme::catppuccin_latte(),
57 6 => Theme::dracula(),
58 7 => Theme::gruvbox_dark(),
59 8 => Theme::solarized_dark(),
60 9 => Theme::rose_pine(),
61 10 => Theme::tokyo_night(),
62 11 => Theme::hax0r_blue(),
63 12 => Theme::hax0r_green(),
64 _ => Theme::hax0r_red(),
65 };
66 set_theme(theme);
67 });
68
69 let theme = current_theme();
70 let true_color = supports_true_color();
71
72 let mut stack = View::vstack();
73
74 // Show warning if true color isn't supported
75 if !true_color {
76 let term = terminal_name().unwrap_or_else(|| "Unknown".to_string());
77 stack = stack
78 .child(
79 View::styled_text(format!("Warning: {} doesn't support true color", term))
80 .color(theme.warning)
81 .bold()
82 .build(),
83 )
84 .child(
85 View::styled_text("Only 'Dark' and 'Light' themes will display correctly")
86 .color(theme.muted)
87 .build(),
88 )
89 .child(View::gap(1));
90 }
91
92 stack
93 .child(
94 View::styled_text("Theme Switcher")
95 .color(theme.primary)
96 .bold()
97 .build(),
98 )
99 .child(
100 View::styled_text("Select a theme from the list")
101 .color(theme.muted)
102 .italic()
103 .build(),
104 )
105 .child(View::gap(1))
106 .child(
107 View::hstack()
108 .spacing(2)
109 .child(
110 View::boxed()
111 .border(true)
112 .min_width(25)
113 .child(
114 View::list()
115 .items(theme_names)
116 .selected(selected.get())
117 .on_select(on_select)
118 .build(),
119 )
120 .build(),
121 )
122 .child(
123 View::boxed()
124 .border(true)
125 .padding(1)
126 .child(
127 View::vstack()
128 .child(View::styled_text("Preview").bold().build())
129 .child(View::gap(1))
130 .child(
131 View::hstack()
132 .child(
133 View::styled_text("Primary")
134 .color(theme.primary)
135 .build(),
136 )
137 .child(View::text(" "))
138 .child(
139 View::styled_text("Secondary")
140 .color(theme.secondary)
141 .build(),
142 )
143 .build(),
144 )
145 .child(
146 View::hstack()
147 .child(
148 View::styled_text("Muted")
149 .color(theme.muted)
150 .build(),
151 )
152 .child(View::text(" "))
153 .child(
154 View::styled_text("Success")
155 .color(theme.success)
156 .build(),
157 )
158 .build(),
159 )
160 .child(
161 View::hstack()
162 .child(
163 View::styled_text("Warning")
164 .color(theme.warning)
165 .build(),
166 )
167 .child(View::text(" "))
168 .child(
169 View::styled_text("Error")
170 .color(theme.error)
171 .build(),
172 )
173 .build(),
174 )
175 .build(),
176 )
177 .build(),
178 )
179 .build(),
180 )
181 .child(View::gap(1))
182 .child(
183 View::styled_text("↑/↓ select • F1 help • Ctrl+Q quit")
184 .color(theme.muted)
185 .build(),
186 )
187 .child(
188 View::modal()
189 .visible(show_help.get())
190 .title("Example 03: Theme Switcher")
191 .on_dismiss(with!(show_help => move || show_help.set(false)))
192 .child(
193 View::vstack()
194 .child(View::styled_text("What you're seeing").bold().build())
195 .child(View::text("• Built-in theme system with 14 themes"))
196 .child(View::text("• View::list() for selection UI"))
197 .child(View::text("• Live preview as you navigate"))
198 .child(View::gap(1))
199 .child(View::styled_text("Key concepts").bold().build())
200 .child(View::text("• current_theme() gets active theme colors"))
201 .child(View::text("• set_theme() changes theme globally"))
202 .child(View::text(
203 "• Themes provide semantic colors (primary, error, etc.)",
204 ))
205 .child(View::gap(1))
206 .child(View::styled_text("Try this").bold().build())
207 .child(View::text(
208 "• Navigate with ↑/↓ to see themes change instantly",
209 ))
210 .child(View::text(
211 "• Notice the preview panel updates with theme colors",
212 ))
213 .child(View::gap(1))
214 .child(View::styled_text("Next up").bold().build())
215 .child(View::text("→ 04_timer: streaming data without interaction"))
216 .child(View::gap(1))
217 .child(View::styled_text("Press Escape to close").dim().build())
218 .build(),
219 )
220 .build(),
221 )
222 .build()
223 }Sourcepub fn on_select(self, callback: impl Fn(usize) + 'static) -> Self
pub fn on_select(self, callback: impl Fn(usize) + 'static) -> Self
Examples found in repository?
examples/05_todo_list.rs (line 93)
20 fn render(&self, cx: Scope) -> View {
21 let items = state!(cx, || {
22 vec![
23 "Learn Telex".to_string(),
24 "Build something cool".to_string(),
25 ]
26 });
27 let input_value = state!(cx, String::new);
28 let selected = state!(cx, || 0usize);
29 let show_help = state!(cx, || false);
30
31 // F1 toggles help
32 cx.use_command(
33 KeyBinding::key(KeyCode::F(1)),
34 with!(show_help => move || show_help.update(|v| *v = !*v)),
35 );
36
37 // Add new item on submit
38 let on_submit = with!(items, input_value => move || {
39 let text = input_value.get();
40 if !text.is_empty() {
41 items.update(|v| v.push(text));
42 input_value.set(String::new());
43 }
44 });
45
46 // Handle input changes
47 let on_change = with!(input_value => move |text: String| {
48 input_value.set(text);
49 });
50
51 // Delete selected item
52 let on_delete = with!(items, selected => move || {
53 let idx = selected.get();
54 items.update(|v| {
55 if idx < v.len() {
56 v.remove(idx);
57 // Adjust selection if needed
58 if idx > 0 && idx >= v.len() {
59 selected.set(idx - 1);
60 }
61 }
62 });
63 });
64
65 // Track selection
66 let on_select = with!(selected => move |idx: usize| {
67 selected.set(idx);
68 });
69
70 let item_count = items.get().len();
71
72 View::vstack()
73 .child(
74 View::styled_text("Todo List")
75 .color(Color::Cyan)
76 .bold()
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::text_input()
82 .value(input_value.get())
83 .placeholder("Type something to add...")
84 .on_change(on_change)
85 .on_submit(on_submit)
86 .build(),
87 )
88 .child(View::gap(1))
89 .child(if item_count > 0 {
90 View::list()
91 .items(items.get())
92 .selected(selected.get())
93 .on_select(on_select)
94 .build()
95 } else {
96 View::styled_text("No items yet").dim().build()
97 })
98 .child(View::gap(1))
99 .child(
100 View::hstack()
101 .child(View::button().label("Delete").on_press(on_delete).build())
102 .build(),
103 )
104 .child(View::gap(1))
105 .child(
106 View::styled_text("Tab navigate • Enter add/select • F1 help • Ctrl+Q quit")
107 .dim()
108 .build(),
109 )
110 .child(
111 View::modal()
112 .visible(show_help.get())
113 .title("Example 05: Todo List")
114 .on_dismiss(with!(show_help => move || show_help.set(false)))
115 .child(
116 View::vstack()
117 .child(View::styled_text("What you're seeing").bold().build())
118 .child(View::text("• View::text_input() for text entry"))
119 .child(View::text("• View::list() for displaying items"))
120 .child(View::text("• on_submit callback for Enter key"))
121 .child(View::gap(1))
122 .child(View::styled_text("Key concepts").bold().build())
123 .child(View::text("• Controlled input: value + on_change"))
124 .child(View::text("• Vec<String> state for list items"))
125 .child(View::text("• Conditional rendering (empty state)"))
126 .child(View::gap(1))
127 .child(View::styled_text("Try this").bold().build())
128 .child(View::text("• Type something and press Enter to add"))
129 .child(View::text("• Use ↑/↓ to select, then Delete button"))
130 .child(View::text("• Delete all items to see empty state"))
131 .child(View::gap(1))
132 .child(View::styled_text("Next up").bold().build())
133 .child(View::text("→ 06_log_viewer: streaming text content"))
134 .child(View::gap(1))
135 .child(View::styled_text("Press Escape to close").dim().build())
136 .build(),
137 )
138 .build(),
139 )
140 .build()
141 }More examples
examples/13_split_panes.rs (line 76)
20 fn render(&self, cx: Scope) -> View {
21 let show_help = state!(cx, || false);
22
23 // F1 toggles help
24 cx.use_command(
25 KeyBinding::key(KeyCode::F(1)),
26 with!(show_help => move || show_help.update(|v| *v = !*v)),
27 );
28
29 let selected_item = state!(cx, || 0usize);
30
31 let items = vec![
32 "README.md".to_string(),
33 "Cargo.toml".to_string(),
34 "src/".to_string(),
35 "src/main.rs".to_string(),
36 "src/lib.rs".to_string(),
37 "tests/".to_string(),
38 ];
39
40 let on_select = with!(selected_item => move |idx: usize| {
41 selected_item.set(idx);
42 });
43
44 let detail_text = match selected_item.get() {
45 0 => "# README\n\nThis is a demo of split panes.\n\nThe left panel shows a file list,\nthe right panel shows details.",
46 1 => "[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2021\"",
47 2 => "Directory: src/\n\nContains the main source files.",
48 3 => "fn main() {\n println!(\"Hello, world!\");\n}",
49 4 => "pub mod utils;\npub mod widgets;",
50 5 => "Directory: tests/\n\nContains integration tests.",
51 _ => "Select an item to see details.",
52 };
53
54 // Horizontal split: file list on left, details on right
55 View::vstack()
56 .child(
57 View::boxed()
58 .flex(1)
59 .child(
60 View::split()
61 .horizontal()
62 .ratio(0.3)
63 .min_first(15)
64 .first(
65 View::vstack()
66 .child(
67 View::styled_text("Files")
68 .color(Color::Cyan)
69 .bold()
70 .build(),
71 )
72 .child(
73 View::list()
74 .items(items)
75 .selected(selected_item.get())
76 .on_select(on_select)
77 .build(),
78 )
79 .child(View::gap(1))
80 .build(),
81 )
82 .second(
83 View::vstack()
84 .child(
85 View::styled_text("Details")
86 .color(Color::Green)
87 .bold()
88 .build(),
89 )
90 .child(
91 View::boxed()
92 .border(true)
93 .flex(1)
94 .child(View::text(detail_text))
95 .build(),
96 )
97 .build(),
98 )
99 .build(),
100 )
101 .build(),
102 )
103 .child(
104 View::styled_text("↑↓: select file | F1 help | Ctrl+Q: quit")
105 .dim()
106 .build(),
107 )
108 .child(
109 View::modal()
110 .visible(show_help.get())
111 .title("Example 13: Split Panes")
112 .on_dismiss(with!(show_help => move || show_help.set(false)))
113 .child(
114 View::vstack()
115 .child(View::styled_text("What you're seeing").bold().build())
116 .child(View::text("• Horizontal split: file list / details"))
117 .child(View::text("• ratio(0.3) = 30% left, 70% right"))
118 .child(View::text("• min_first(15) sets minimum pane width"))
119 .child(View::gap(1))
120 .child(View::styled_text("Key concepts").bold().build())
121 .child(View::text("• View::split() creates resizable panes"))
122 .child(View::text("• .horizontal() or .vertical() orientation"))
123 .child(View::text("• .first() and .second() set pane content"))
124 .child(View::text("• Splits can be nested"))
125 .child(View::gap(1))
126 .child(View::styled_text("Try this").bold().build())
127 .child(View::text("• Select different files to see details"))
128 .child(View::text("• Resize terminal to see layout adapt"))
129 .child(View::gap(1))
130 .child(View::styled_text("Next up").bold().build())
131 .child(View::text("→ 14_tabs: tabbed interfaces"))
132 .child(View::gap(1))
133 .child(View::styled_text("Press Escape to close").dim().build())
134 .build(),
135 )
136 .build(),
137 )
138 .build()
139 }examples/07_file_browser.rs (line 122)
49 fn render(&self, cx: Scope) -> View {
50 let current_path =
51 state!(cx, || std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/")));
52 let selected = state!(cx, || 0usize);
53 let show_file_info = state!(cx, || false);
54 let selected_file_path = state!(cx, String::new);
55 let show_help = state!(cx, || false);
56 let entries = list_directory(¤t_path.get());
57
58 // F1 toggles help
59 cx.use_command(
60 KeyBinding::key(KeyCode::F(1)),
61 with!(show_help => move || show_help.update(|v| *v = !*v)),
62 );
63
64 // Track selection (just updates index, doesn't navigate)
65 let on_select = with!(selected => move |idx: usize| {
66 selected.set(idx);
67 });
68
69 // Dismiss modal
70 let on_dismiss = with!(show_file_info => move || show_file_info.set(false));
71
72 // Open directory or show file info on Enter
73 let entries_for_cmd = entries.clone();
74 cx.use_command(
75 KeyBinding::key(KeyCode::Enter),
76 with!(current_path, selected, show_file_info, selected_file_path => move || {
77 let idx = selected.get();
78 if idx < entries_for_cmd.len() {
79 let entry = &entries_for_cmd[idx];
80 let path = current_path.get();
81
82 if entry == ".." {
83 if let Some(parent) = path.parent() {
84 current_path.set(parent.to_path_buf());
85 selected.set(0);
86 }
87 } else if entry.ends_with('/') {
88 let dir_name = entry.trim_end_matches('/');
89 let new_path = path.join(dir_name);
90 current_path.set(new_path);
91 selected.set(0);
92 } else {
93 // It's a file - show info modal
94 let full_path = path.join(entry);
95 selected_file_path.set(full_path.to_string_lossy().to_string());
96 show_file_info.set(true);
97 }
98 }
99 }),
100 );
101
102 let path_display = current_path.get().to_string_lossy().to_string();
103
104 View::vstack()
105 .child(
106 View::styled_text("File Browser")
107 .color(Color::Cyan)
108 .bold()
109 .build(),
110 )
111 .child(View::gap(1))
112 .child(
113 View::styled_text(&path_display)
114 .color(Color::Yellow)
115 .build(),
116 )
117 .child(View::gap(1))
118 .child(
119 View::list()
120 .items(entries)
121 .selected(selected.get())
122 .on_select(on_select)
123 .build(),
124 )
125 .child(View::gap(1))
126 .child(
127 View::styled_text("↑/↓ navigate • Enter open • F1 help • Ctrl+Q quit")
128 .dim()
129 .build(),
130 )
131 .child(
132 View::modal()
133 .visible(show_file_info.get())
134 .title("File")
135 .width(60)
136 .height(20)
137 .on_dismiss(on_dismiss)
138 .child(
139 View::vstack()
140 .child(View::text(selected_file_path.get()))
141 .child(View::gap(1))
142 .child(View::styled_text("Press Escape to close").dim().build())
143 .build(),
144 )
145 .build(),
146 )
147 .child(
148 View::modal()
149 .visible(show_help.get())
150 .title("Example 07: File Browser")
151 .on_dismiss(with!(show_help => move || show_help.set(false)))
152 .child(
153 View::vstack()
154 .child(View::styled_text("What you're seeing").bold().build())
155 .child(View::text("• Real filesystem navigation"))
156 .child(View::text("• cx.use_command() for keyboard shortcuts"))
157 .child(View::text("• Modal for file details"))
158 .child(View::gap(1))
159 .child(View::styled_text("Key concepts").bold().build())
160 .child(View::text("• std::fs::read_dir for directory listing"))
161 .child(View::text(
162 "• KeyBinding::key(KeyCode::Enter) for Enter handling",
163 ))
164 .child(View::text("• Directories shown with trailing /"))
165 .child(View::gap(1))
166 .child(View::styled_text("Try this").bold().build())
167 .child(View::text("• Navigate into directories with Enter"))
168 .child(View::text("• Go up with '..' entry"))
169 .child(View::text("• Select a file to see its path"))
170 .child(View::gap(1))
171 .child(View::styled_text("Next up").bold().build())
172 .child(View::text(
173 "→ 08_system_monitor: multiple concurrent streams",
174 ))
175 .child(View::gap(1))
176 .child(View::styled_text("Press Escape to close").dim().build())
177 .build(),
178 )
179 .build(),
180 )
181 .build()
182 }examples/03_theme_switcher.rs (line 117)
20 fn render(&self, cx: Scope) -> View {
21 let selected = state!(cx, || 0usize);
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 let theme_names = vec![
31 "Dark".to_string(),
32 "Light".to_string(),
33 "Nord".to_string(),
34 "Monokai".to_string(),
35 "Catppuccin Mocha".to_string(),
36 "Catppuccin Latte".to_string(),
37 "Dracula".to_string(),
38 "Gruvbox Dark".to_string(),
39 "Solarized Dark".to_string(),
40 "Rosé Pine".to_string(),
41 "Tokyo Night".to_string(),
42 "HaX0R Blue".to_string(),
43 "HaX0R Green".to_string(),
44 "HaX0R Red".to_string(),
45 ];
46
47 // Apply theme when selection changes
48 let on_select = with!(selected => move |idx: usize| {
49 selected.set(idx);
50 let theme = match idx {
51 0 => Theme::dark(),
52 1 => Theme::light(),
53 2 => Theme::nord(),
54 3 => Theme::monokai(),
55 4 => Theme::catppuccin_mocha(),
56 5 => Theme::catppuccin_latte(),
57 6 => Theme::dracula(),
58 7 => Theme::gruvbox_dark(),
59 8 => Theme::solarized_dark(),
60 9 => Theme::rose_pine(),
61 10 => Theme::tokyo_night(),
62 11 => Theme::hax0r_blue(),
63 12 => Theme::hax0r_green(),
64 _ => Theme::hax0r_red(),
65 };
66 set_theme(theme);
67 });
68
69 let theme = current_theme();
70 let true_color = supports_true_color();
71
72 let mut stack = View::vstack();
73
74 // Show warning if true color isn't supported
75 if !true_color {
76 let term = terminal_name().unwrap_or_else(|| "Unknown".to_string());
77 stack = stack
78 .child(
79 View::styled_text(format!("Warning: {} doesn't support true color", term))
80 .color(theme.warning)
81 .bold()
82 .build(),
83 )
84 .child(
85 View::styled_text("Only 'Dark' and 'Light' themes will display correctly")
86 .color(theme.muted)
87 .build(),
88 )
89 .child(View::gap(1));
90 }
91
92 stack
93 .child(
94 View::styled_text("Theme Switcher")
95 .color(theme.primary)
96 .bold()
97 .build(),
98 )
99 .child(
100 View::styled_text("Select a theme from the list")
101 .color(theme.muted)
102 .italic()
103 .build(),
104 )
105 .child(View::gap(1))
106 .child(
107 View::hstack()
108 .spacing(2)
109 .child(
110 View::boxed()
111 .border(true)
112 .min_width(25)
113 .child(
114 View::list()
115 .items(theme_names)
116 .selected(selected.get())
117 .on_select(on_select)
118 .build(),
119 )
120 .build(),
121 )
122 .child(
123 View::boxed()
124 .border(true)
125 .padding(1)
126 .child(
127 View::vstack()
128 .child(View::styled_text("Preview").bold().build())
129 .child(View::gap(1))
130 .child(
131 View::hstack()
132 .child(
133 View::styled_text("Primary")
134 .color(theme.primary)
135 .build(),
136 )
137 .child(View::text(" "))
138 .child(
139 View::styled_text("Secondary")
140 .color(theme.secondary)
141 .build(),
142 )
143 .build(),
144 )
145 .child(
146 View::hstack()
147 .child(
148 View::styled_text("Muted")
149 .color(theme.muted)
150 .build(),
151 )
152 .child(View::text(" "))
153 .child(
154 View::styled_text("Success")
155 .color(theme.success)
156 .build(),
157 )
158 .build(),
159 )
160 .child(
161 View::hstack()
162 .child(
163 View::styled_text("Warning")
164 .color(theme.warning)
165 .build(),
166 )
167 .child(View::text(" "))
168 .child(
169 View::styled_text("Error")
170 .color(theme.error)
171 .build(),
172 )
173 .build(),
174 )
175 .build(),
176 )
177 .build(),
178 )
179 .build(),
180 )
181 .child(View::gap(1))
182 .child(
183 View::styled_text("↑/↓ select • F1 help • Ctrl+Q quit")
184 .color(theme.muted)
185 .build(),
186 )
187 .child(
188 View::modal()
189 .visible(show_help.get())
190 .title("Example 03: Theme Switcher")
191 .on_dismiss(with!(show_help => move || show_help.set(false)))
192 .child(
193 View::vstack()
194 .child(View::styled_text("What you're seeing").bold().build())
195 .child(View::text("• Built-in theme system with 14 themes"))
196 .child(View::text("• View::list() for selection UI"))
197 .child(View::text("• Live preview as you navigate"))
198 .child(View::gap(1))
199 .child(View::styled_text("Key concepts").bold().build())
200 .child(View::text("• current_theme() gets active theme colors"))
201 .child(View::text("• set_theme() changes theme globally"))
202 .child(View::text(
203 "• Themes provide semantic colors (primary, error, etc.)",
204 ))
205 .child(View::gap(1))
206 .child(View::styled_text("Try this").bold().build())
207 .child(View::text(
208 "• Navigate with ↑/↓ to see themes change instantly",
209 ))
210 .child(View::text(
211 "• Notice the preview panel updates with theme colors",
212 ))
213 .child(View::gap(1))
214 .child(View::styled_text("Next up").bold().build())
215 .child(View::text("→ 04_timer: streaming data without interaction"))
216 .child(View::gap(1))
217 .child(View::styled_text("Press Escape to close").dim().build())
218 .build(),
219 )
220 .build(),
221 )
222 .build()
223 }Sourcepub fn build(self) -> View
pub fn build(self) -> View
Examples found in repository?
examples/05_todo_list.rs (line 94)
20 fn render(&self, cx: Scope) -> View {
21 let items = state!(cx, || {
22 vec![
23 "Learn Telex".to_string(),
24 "Build something cool".to_string(),
25 ]
26 });
27 let input_value = state!(cx, String::new);
28 let selected = state!(cx, || 0usize);
29 let show_help = state!(cx, || false);
30
31 // F1 toggles help
32 cx.use_command(
33 KeyBinding::key(KeyCode::F(1)),
34 with!(show_help => move || show_help.update(|v| *v = !*v)),
35 );
36
37 // Add new item on submit
38 let on_submit = with!(items, input_value => move || {
39 let text = input_value.get();
40 if !text.is_empty() {
41 items.update(|v| v.push(text));
42 input_value.set(String::new());
43 }
44 });
45
46 // Handle input changes
47 let on_change = with!(input_value => move |text: String| {
48 input_value.set(text);
49 });
50
51 // Delete selected item
52 let on_delete = with!(items, selected => move || {
53 let idx = selected.get();
54 items.update(|v| {
55 if idx < v.len() {
56 v.remove(idx);
57 // Adjust selection if needed
58 if idx > 0 && idx >= v.len() {
59 selected.set(idx - 1);
60 }
61 }
62 });
63 });
64
65 // Track selection
66 let on_select = with!(selected => move |idx: usize| {
67 selected.set(idx);
68 });
69
70 let item_count = items.get().len();
71
72 View::vstack()
73 .child(
74 View::styled_text("Todo List")
75 .color(Color::Cyan)
76 .bold()
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::text_input()
82 .value(input_value.get())
83 .placeholder("Type something to add...")
84 .on_change(on_change)
85 .on_submit(on_submit)
86 .build(),
87 )
88 .child(View::gap(1))
89 .child(if item_count > 0 {
90 View::list()
91 .items(items.get())
92 .selected(selected.get())
93 .on_select(on_select)
94 .build()
95 } else {
96 View::styled_text("No items yet").dim().build()
97 })
98 .child(View::gap(1))
99 .child(
100 View::hstack()
101 .child(View::button().label("Delete").on_press(on_delete).build())
102 .build(),
103 )
104 .child(View::gap(1))
105 .child(
106 View::styled_text("Tab navigate • Enter add/select • F1 help • Ctrl+Q quit")
107 .dim()
108 .build(),
109 )
110 .child(
111 View::modal()
112 .visible(show_help.get())
113 .title("Example 05: Todo List")
114 .on_dismiss(with!(show_help => move || show_help.set(false)))
115 .child(
116 View::vstack()
117 .child(View::styled_text("What you're seeing").bold().build())
118 .child(View::text("• View::text_input() for text entry"))
119 .child(View::text("• View::list() for displaying items"))
120 .child(View::text("• on_submit callback for Enter key"))
121 .child(View::gap(1))
122 .child(View::styled_text("Key concepts").bold().build())
123 .child(View::text("• Controlled input: value + on_change"))
124 .child(View::text("• Vec<String> state for list items"))
125 .child(View::text("• Conditional rendering (empty state)"))
126 .child(View::gap(1))
127 .child(View::styled_text("Try this").bold().build())
128 .child(View::text("• Type something and press Enter to add"))
129 .child(View::text("• Use ↑/↓ to select, then Delete button"))
130 .child(View::text("• Delete all items to see empty state"))
131 .child(View::gap(1))
132 .child(View::styled_text("Next up").bold().build())
133 .child(View::text("→ 06_log_viewer: streaming text content"))
134 .child(View::gap(1))
135 .child(View::styled_text("Press Escape to close").dim().build())
136 .build(),
137 )
138 .build(),
139 )
140 .build()
141 }More examples
examples/13_split_panes.rs (line 77)
20 fn render(&self, cx: Scope) -> View {
21 let show_help = state!(cx, || false);
22
23 // F1 toggles help
24 cx.use_command(
25 KeyBinding::key(KeyCode::F(1)),
26 with!(show_help => move || show_help.update(|v| *v = !*v)),
27 );
28
29 let selected_item = state!(cx, || 0usize);
30
31 let items = vec![
32 "README.md".to_string(),
33 "Cargo.toml".to_string(),
34 "src/".to_string(),
35 "src/main.rs".to_string(),
36 "src/lib.rs".to_string(),
37 "tests/".to_string(),
38 ];
39
40 let on_select = with!(selected_item => move |idx: usize| {
41 selected_item.set(idx);
42 });
43
44 let detail_text = match selected_item.get() {
45 0 => "# README\n\nThis is a demo of split panes.\n\nThe left panel shows a file list,\nthe right panel shows details.",
46 1 => "[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2021\"",
47 2 => "Directory: src/\n\nContains the main source files.",
48 3 => "fn main() {\n println!(\"Hello, world!\");\n}",
49 4 => "pub mod utils;\npub mod widgets;",
50 5 => "Directory: tests/\n\nContains integration tests.",
51 _ => "Select an item to see details.",
52 };
53
54 // Horizontal split: file list on left, details on right
55 View::vstack()
56 .child(
57 View::boxed()
58 .flex(1)
59 .child(
60 View::split()
61 .horizontal()
62 .ratio(0.3)
63 .min_first(15)
64 .first(
65 View::vstack()
66 .child(
67 View::styled_text("Files")
68 .color(Color::Cyan)
69 .bold()
70 .build(),
71 )
72 .child(
73 View::list()
74 .items(items)
75 .selected(selected_item.get())
76 .on_select(on_select)
77 .build(),
78 )
79 .child(View::gap(1))
80 .build(),
81 )
82 .second(
83 View::vstack()
84 .child(
85 View::styled_text("Details")
86 .color(Color::Green)
87 .bold()
88 .build(),
89 )
90 .child(
91 View::boxed()
92 .border(true)
93 .flex(1)
94 .child(View::text(detail_text))
95 .build(),
96 )
97 .build(),
98 )
99 .build(),
100 )
101 .build(),
102 )
103 .child(
104 View::styled_text("↑↓: select file | F1 help | Ctrl+Q: quit")
105 .dim()
106 .build(),
107 )
108 .child(
109 View::modal()
110 .visible(show_help.get())
111 .title("Example 13: Split Panes")
112 .on_dismiss(with!(show_help => move || show_help.set(false)))
113 .child(
114 View::vstack()
115 .child(View::styled_text("What you're seeing").bold().build())
116 .child(View::text("• Horizontal split: file list / details"))
117 .child(View::text("• ratio(0.3) = 30% left, 70% right"))
118 .child(View::text("• min_first(15) sets minimum pane width"))
119 .child(View::gap(1))
120 .child(View::styled_text("Key concepts").bold().build())
121 .child(View::text("• View::split() creates resizable panes"))
122 .child(View::text("• .horizontal() or .vertical() orientation"))
123 .child(View::text("• .first() and .second() set pane content"))
124 .child(View::text("• Splits can be nested"))
125 .child(View::gap(1))
126 .child(View::styled_text("Try this").bold().build())
127 .child(View::text("• Select different files to see details"))
128 .child(View::text("• Resize terminal to see layout adapt"))
129 .child(View::gap(1))
130 .child(View::styled_text("Next up").bold().build())
131 .child(View::text("→ 14_tabs: tabbed interfaces"))
132 .child(View::gap(1))
133 .child(View::styled_text("Press Escape to close").dim().build())
134 .build(),
135 )
136 .build(),
137 )
138 .build()
139 }examples/07_file_browser.rs (line 123)
49 fn render(&self, cx: Scope) -> View {
50 let current_path =
51 state!(cx, || std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/")));
52 let selected = state!(cx, || 0usize);
53 let show_file_info = state!(cx, || false);
54 let selected_file_path = state!(cx, String::new);
55 let show_help = state!(cx, || false);
56 let entries = list_directory(¤t_path.get());
57
58 // F1 toggles help
59 cx.use_command(
60 KeyBinding::key(KeyCode::F(1)),
61 with!(show_help => move || show_help.update(|v| *v = !*v)),
62 );
63
64 // Track selection (just updates index, doesn't navigate)
65 let on_select = with!(selected => move |idx: usize| {
66 selected.set(idx);
67 });
68
69 // Dismiss modal
70 let on_dismiss = with!(show_file_info => move || show_file_info.set(false));
71
72 // Open directory or show file info on Enter
73 let entries_for_cmd = entries.clone();
74 cx.use_command(
75 KeyBinding::key(KeyCode::Enter),
76 with!(current_path, selected, show_file_info, selected_file_path => move || {
77 let idx = selected.get();
78 if idx < entries_for_cmd.len() {
79 let entry = &entries_for_cmd[idx];
80 let path = current_path.get();
81
82 if entry == ".." {
83 if let Some(parent) = path.parent() {
84 current_path.set(parent.to_path_buf());
85 selected.set(0);
86 }
87 } else if entry.ends_with('/') {
88 let dir_name = entry.trim_end_matches('/');
89 let new_path = path.join(dir_name);
90 current_path.set(new_path);
91 selected.set(0);
92 } else {
93 // It's a file - show info modal
94 let full_path = path.join(entry);
95 selected_file_path.set(full_path.to_string_lossy().to_string());
96 show_file_info.set(true);
97 }
98 }
99 }),
100 );
101
102 let path_display = current_path.get().to_string_lossy().to_string();
103
104 View::vstack()
105 .child(
106 View::styled_text("File Browser")
107 .color(Color::Cyan)
108 .bold()
109 .build(),
110 )
111 .child(View::gap(1))
112 .child(
113 View::styled_text(&path_display)
114 .color(Color::Yellow)
115 .build(),
116 )
117 .child(View::gap(1))
118 .child(
119 View::list()
120 .items(entries)
121 .selected(selected.get())
122 .on_select(on_select)
123 .build(),
124 )
125 .child(View::gap(1))
126 .child(
127 View::styled_text("↑/↓ navigate • Enter open • F1 help • Ctrl+Q quit")
128 .dim()
129 .build(),
130 )
131 .child(
132 View::modal()
133 .visible(show_file_info.get())
134 .title("File")
135 .width(60)
136 .height(20)
137 .on_dismiss(on_dismiss)
138 .child(
139 View::vstack()
140 .child(View::text(selected_file_path.get()))
141 .child(View::gap(1))
142 .child(View::styled_text("Press Escape to close").dim().build())
143 .build(),
144 )
145 .build(),
146 )
147 .child(
148 View::modal()
149 .visible(show_help.get())
150 .title("Example 07: File Browser")
151 .on_dismiss(with!(show_help => move || show_help.set(false)))
152 .child(
153 View::vstack()
154 .child(View::styled_text("What you're seeing").bold().build())
155 .child(View::text("• Real filesystem navigation"))
156 .child(View::text("• cx.use_command() for keyboard shortcuts"))
157 .child(View::text("• Modal for file details"))
158 .child(View::gap(1))
159 .child(View::styled_text("Key concepts").bold().build())
160 .child(View::text("• std::fs::read_dir for directory listing"))
161 .child(View::text(
162 "• KeyBinding::key(KeyCode::Enter) for Enter handling",
163 ))
164 .child(View::text("• Directories shown with trailing /"))
165 .child(View::gap(1))
166 .child(View::styled_text("Try this").bold().build())
167 .child(View::text("• Navigate into directories with Enter"))
168 .child(View::text("• Go up with '..' entry"))
169 .child(View::text("• Select a file to see its path"))
170 .child(View::gap(1))
171 .child(View::styled_text("Next up").bold().build())
172 .child(View::text(
173 "→ 08_system_monitor: multiple concurrent streams",
174 ))
175 .child(View::gap(1))
176 .child(View::styled_text("Press Escape to close").dim().build())
177 .build(),
178 )
179 .build(),
180 )
181 .build()
182 }examples/03_theme_switcher.rs (line 118)
20 fn render(&self, cx: Scope) -> View {
21 let selected = state!(cx, || 0usize);
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 let theme_names = vec![
31 "Dark".to_string(),
32 "Light".to_string(),
33 "Nord".to_string(),
34 "Monokai".to_string(),
35 "Catppuccin Mocha".to_string(),
36 "Catppuccin Latte".to_string(),
37 "Dracula".to_string(),
38 "Gruvbox Dark".to_string(),
39 "Solarized Dark".to_string(),
40 "Rosé Pine".to_string(),
41 "Tokyo Night".to_string(),
42 "HaX0R Blue".to_string(),
43 "HaX0R Green".to_string(),
44 "HaX0R Red".to_string(),
45 ];
46
47 // Apply theme when selection changes
48 let on_select = with!(selected => move |idx: usize| {
49 selected.set(idx);
50 let theme = match idx {
51 0 => Theme::dark(),
52 1 => Theme::light(),
53 2 => Theme::nord(),
54 3 => Theme::monokai(),
55 4 => Theme::catppuccin_mocha(),
56 5 => Theme::catppuccin_latte(),
57 6 => Theme::dracula(),
58 7 => Theme::gruvbox_dark(),
59 8 => Theme::solarized_dark(),
60 9 => Theme::rose_pine(),
61 10 => Theme::tokyo_night(),
62 11 => Theme::hax0r_blue(),
63 12 => Theme::hax0r_green(),
64 _ => Theme::hax0r_red(),
65 };
66 set_theme(theme);
67 });
68
69 let theme = current_theme();
70 let true_color = supports_true_color();
71
72 let mut stack = View::vstack();
73
74 // Show warning if true color isn't supported
75 if !true_color {
76 let term = terminal_name().unwrap_or_else(|| "Unknown".to_string());
77 stack = stack
78 .child(
79 View::styled_text(format!("Warning: {} doesn't support true color", term))
80 .color(theme.warning)
81 .bold()
82 .build(),
83 )
84 .child(
85 View::styled_text("Only 'Dark' and 'Light' themes will display correctly")
86 .color(theme.muted)
87 .build(),
88 )
89 .child(View::gap(1));
90 }
91
92 stack
93 .child(
94 View::styled_text("Theme Switcher")
95 .color(theme.primary)
96 .bold()
97 .build(),
98 )
99 .child(
100 View::styled_text("Select a theme from the list")
101 .color(theme.muted)
102 .italic()
103 .build(),
104 )
105 .child(View::gap(1))
106 .child(
107 View::hstack()
108 .spacing(2)
109 .child(
110 View::boxed()
111 .border(true)
112 .min_width(25)
113 .child(
114 View::list()
115 .items(theme_names)
116 .selected(selected.get())
117 .on_select(on_select)
118 .build(),
119 )
120 .build(),
121 )
122 .child(
123 View::boxed()
124 .border(true)
125 .padding(1)
126 .child(
127 View::vstack()
128 .child(View::styled_text("Preview").bold().build())
129 .child(View::gap(1))
130 .child(
131 View::hstack()
132 .child(
133 View::styled_text("Primary")
134 .color(theme.primary)
135 .build(),
136 )
137 .child(View::text(" "))
138 .child(
139 View::styled_text("Secondary")
140 .color(theme.secondary)
141 .build(),
142 )
143 .build(),
144 )
145 .child(
146 View::hstack()
147 .child(
148 View::styled_text("Muted")
149 .color(theme.muted)
150 .build(),
151 )
152 .child(View::text(" "))
153 .child(
154 View::styled_text("Success")
155 .color(theme.success)
156 .build(),
157 )
158 .build(),
159 )
160 .child(
161 View::hstack()
162 .child(
163 View::styled_text("Warning")
164 .color(theme.warning)
165 .build(),
166 )
167 .child(View::text(" "))
168 .child(
169 View::styled_text("Error")
170 .color(theme.error)
171 .build(),
172 )
173 .build(),
174 )
175 .build(),
176 )
177 .build(),
178 )
179 .build(),
180 )
181 .child(View::gap(1))
182 .child(
183 View::styled_text("↑/↓ select • F1 help • Ctrl+Q quit")
184 .color(theme.muted)
185 .build(),
186 )
187 .child(
188 View::modal()
189 .visible(show_help.get())
190 .title("Example 03: Theme Switcher")
191 .on_dismiss(with!(show_help => move || show_help.set(false)))
192 .child(
193 View::vstack()
194 .child(View::styled_text("What you're seeing").bold().build())
195 .child(View::text("• Built-in theme system with 14 themes"))
196 .child(View::text("• View::list() for selection UI"))
197 .child(View::text("• Live preview as you navigate"))
198 .child(View::gap(1))
199 .child(View::styled_text("Key concepts").bold().build())
200 .child(View::text("• current_theme() gets active theme colors"))
201 .child(View::text("• set_theme() changes theme globally"))
202 .child(View::text(
203 "• Themes provide semantic colors (primary, error, etc.)",
204 ))
205 .child(View::gap(1))
206 .child(View::styled_text("Try this").bold().build())
207 .child(View::text(
208 "• Navigate with ↑/↓ to see themes change instantly",
209 ))
210 .child(View::text(
211 "• Notice the preview panel updates with theme colors",
212 ))
213 .child(View::gap(1))
214 .child(View::styled_text("Next up").bold().build())
215 .child(View::text("→ 04_timer: streaming data without interaction"))
216 .child(View::gap(1))
217 .child(View::styled_text("Press Escape to close").dim().build())
218 .build(),
219 )
220 .build(),
221 )
222 .build()
223 }Trait Implementations§
Source§impl Default for ListBuilder
impl Default for ListBuilder
Source§fn default() -> ListBuilder
fn default() -> ListBuilder
Returns the “default value” for a type. Read more
Auto Trait Implementations§
impl Freeze for ListBuilder
impl !RefUnwindSafe for ListBuilder
impl !Send for ListBuilder
impl !Sync for ListBuilder
impl Unpin for ListBuilder
impl !UnwindSafe for ListBuilder
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Convert
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Convert
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Convert
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.