pub enum View {
Show 30 variants
Text(TextNode),
VStack(VStackNode),
HStack(HStackNode),
Button(ButtonNode),
Box(BoxNode),
Spacer(SpacerNode),
List(ListNode),
TextInput(TextInputNode),
TextArea(TextAreaNode),
Checkbox(CheckboxNode),
RadioGroup(RadioGroupNode),
Modal(ModalNode),
Split(SplitNode),
Tabs(TabsNode),
Tree(TreeNode),
Table(TableNode),
ProgressBar(ProgressBarNode),
StatusBar(StatusBarNode),
CommandPalette(CommandPaletteNode),
MenuBar(MenuBarNode),
ToastContainer(ToastContainerNode),
Form(FormNode),
FormField(FormFieldNode),
Canvas(CanvasNode),
Image(ImageNode),
Terminal(TerminalNode),
ErrorBoundary(ErrorBoundaryNode),
Custom(CustomNode),
Slider(SliderNode),
Empty,
}Expand description
The core view type - a node in the UI tree.
Variants§
Text(TextNode)
A text node displaying a string.
VStack(VStackNode)
A vertical stack of child views.
HStack(HStackNode)
A horizontal stack of child views.
Button(ButtonNode)
A clickable button.
Box(BoxNode)
A container with optional border, padding, and flex sizing.
Spacer(SpacerNode)
Flexible space that expands to fill available space.
List(ListNode)
A selectable list of items.
TextInput(TextInputNode)
A single-line text input.
TextArea(TextAreaNode)
A multi-line text area.
Checkbox(CheckboxNode)
A checkbox (toggle).
RadioGroup(RadioGroupNode)
A group of radio buttons (mutually exclusive options).
Modal(ModalNode)
A modal dialog overlay.
Split(SplitNode)
A split pane container with two resizable panels.
Tabs(TabsNode)
A tabbed interface container.
Tree(TreeNode)
A hierarchical tree view.
Table(TableNode)
A data table with columns and rows.
ProgressBar(ProgressBarNode)
A progress bar showing completion status.
StatusBar(StatusBarNode)
A status bar displayed at the bottom of the screen.
CommandPalette(CommandPaletteNode)
A command palette overlay for searching and executing commands.
MenuBar(MenuBarNode)
A horizontal menu bar with dropdown menus.
ToastContainer(ToastContainerNode)
A container for toast notifications.
Form(FormNode)
A form container with validation support.
FormField(FormFieldNode)
A form field with label and error display.
Canvas(CanvasNode)
A pixel-level canvas using Kitty graphics protocol.
Image(ImageNode)
An image display using Kitty graphics protocol.
Terminal(TerminalNode)
An interactive PTY terminal emulator.
ErrorBoundary(ErrorBoundaryNode)
An error boundary that catches panics in its child view.
Custom(CustomNode)
A user-defined custom widget.
Slider(SliderNode)
A slider for bounded numeric values.
Empty
An empty placeholder.
Implementations§
Source§impl View
impl View
Sourcepub fn text(content: impl Into<String>) -> Self
pub fn text(content: impl Into<String>) -> Self
Create a text view with the given content.
Examples found in repository?
5fn app(cx: Scope) -> View {
6 let terminal = terminal!(cx);
7
8 // Spawn bash on first render
9 if !terminal.is_started() {
10 if let Err(e) = terminal.spawn("bash", &[], 80, 24) {
11 eprintln!("Failed to spawn terminal: {}", e);
12 }
13 }
14
15 View::vstack()
16 .child(View::text("Telex Terminal Demo"))
17 .child(View::text(
18 "Press Ctrl+Shift+[ to escape terminal focus, Tab to navigate",
19 ))
20 .child(View::terminal().handle(terminal).build())
21 .build()
22}More examples
19 fn render(&self, cx: Scope) -> View {
20 let show_help = state!(cx, || false);
21
22 // F1 toggles help
23 cx.use_command(
24 KeyBinding::key(KeyCode::F(1)),
25 with!(show_help => move || show_help.update(|v| *v = !*v)),
26 );
27
28 View::vstack()
29 .child(View::styled_text("Hello World").bold().build())
30 .child(View::gap(1))
31 .child(View::text("Welcome to Telex!"))
32 .child(View::gap(1))
33 .child(
34 View::styled_text("F1 for help • Ctrl+Q to quit")
35 .dim()
36 .build(),
37 )
38 .child(
39 View::modal()
40 .visible(show_help.get())
41 .title("Example 01: Hello World")
42 .on_dismiss(with!(show_help => move || show_help.set(false)))
43 .child(
44 View::vstack()
45 .child(View::styled_text("What you're seeing").bold().build())
46 .child(View::text(
47 "• Basic app structure with struct + Component trait",
48 ))
49 .child(View::text(
50 "• View::text() and View::styled_text() for display",
51 ))
52 .child(View::text("• View::vstack() for vertical layout"))
53 .child(View::gap(1))
54 .child(View::styled_text("Key concepts").bold().build())
55 .child(View::text("• Every Telex app implements Component"))
56 .child(View::text("• render() returns a View tree"))
57 .child(View::text("• No state yet - this is purely static"))
58 .child(View::gap(1))
59 .child(View::styled_text("Next up").bold().build())
60 .child(View::text("→ 02_counter: add state and interactivity"))
61 .child(View::gap(1))
62 .child(View::styled_text("Press Escape to close").dim().build())
63 .build(),
64 )
65 .build(),
66 )
67 .build()
68 }30 fn render(&self, cx: Scope) -> View {
31 let show_help = state!(cx, || false);
32
33 // F1 toggles help
34 cx.use_command(
35 KeyBinding::key(KeyCode::F(1)),
36 with!(show_help => move || show_help.update(|v| *v = !*v)),
37 );
38 View::vstack()
39 .spacing(1)
40 .child(
41 View::styled_text("Image Widget Demo (Kitty Graphics)")
42 .bold()
43 .build(),
44 )
45 .child(View::text("Requires Kitty, Ghostty, or WezTerm terminal"))
46 .child(View::text(""))
47 // Load image from file path
48 .child(View::text("Logo (from file path):"))
49 .child(View::image().file("assets/telex-tui.png").build())
50 .child(View::text(""))
51 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
52 .child(
53 View::modal()
54 .visible(show_help.get())
55 .title("Example 30: Image")
56 .on_dismiss(with!(show_help => move || show_help.set(false)))
57 .child(
58 View::vstack()
59 .child(View::styled_text("What you're seeing").bold().build())
60 .child(View::text("• Image display via Kitty protocol"))
61 .child(View::text("• PNG/JPEG/GIF support"))
62 .child(View::text("• Loaded from file path"))
63 .child(View::gap(1))
64 .child(View::styled_text("Key concepts").bold().build())
65 .child(View::text("• View::image() displays images"))
66 .child(View::text("• .file(\"path\") loads from disk"))
67 .child(View::text("• .bytes(data) for embedded images"))
68 .child(View::text("• Works in Kitty/Ghostty/WezTerm"))
69 .child(View::gap(1))
70 .child(View::styled_text("Try this").bold().build())
71 .child(View::text("• Run in compatible terminal"))
72 .child(View::text("• See the Telex logo rendered"))
73 .child(View::gap(1))
74 .child(View::styled_text("Next up").bold().build())
75 .child(View::text("→ 31_animated_canvas: animations"))
76 .child(View::gap(1))
77 .child(View::styled_text("Press Escape to close").dim().build())
78 .build(),
79 )
80 .build(),
81 )
82 .build()
83 }22 fn render(&self, cx: Scope) -> View {
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // Stream that yields elapsed seconds
32 let elapsed = stream!(cx, || {
33 (0u64..).inspect(|&s| {
34 if s > 0 {
35 std::thread::sleep(Duration::from_secs(1));
36 }
37 })
38 });
39
40 let seconds = elapsed.get();
41 let is_running = elapsed.is_loading();
42
43 // Format as MM:SS
44 let minutes = seconds / 60;
45 let secs = seconds % 60;
46 let time_display = format!("{:02}:{:02}", minutes, secs);
47
48 View::vstack()
49 .child(View::styled_text("Timer").color(Color::Cyan).bold().build())
50 .child(View::gap(1))
51 .child(
52 View::hstack()
53 .child(View::styled_text(&time_display).bold().build())
54 .child(if is_running {
55 View::styled_text(" ●").color(Color::Green).build()
56 } else {
57 View::styled_text(" ○").dim().build()
58 })
59 .build(),
60 )
61 .child(View::gap(1))
62 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
63 .child(
64 View::modal()
65 .visible(show_help.get())
66 .title("Example 04: Timer")
67 .on_dismiss(with!(show_help => move || show_help.set(false)))
68 .child(
69 View::vstack()
70 .child(View::styled_text("What you're seeing").bold().build())
71 .child(View::text("• stream!() macro for background data"))
72 .child(View::text("• Auto-updating UI without user input"))
73 .child(View::text("• Green dot = stream is running"))
74 .child(View::gap(1))
75 .child(View::styled_text("Key concepts").bold().build())
76 .child(View::text("• Streams run in background threads"))
77 .child(View::text("• Each yielded value triggers a re-render"))
78 .child(View::text("• is_loading() tells you if stream is active"))
79 .child(View::gap(1))
80 .child(View::styled_text("Try this").bold().build())
81 .child(View::text("• Just watch - the timer ticks automatically"))
82 .child(View::text("• No button presses needed for updates"))
83 .child(View::gap(1))
84 .child(View::styled_text("Next up").bold().build())
85 .child(View::text("→ 05_todo_list: text input and list management"))
86 .child(View::gap(1))
87 .child(View::styled_text("Press Escape to close").dim().build())
88 .build(),
89 )
90 .build(),
91 )
92 .build()
93 }19 fn render(&self, cx: Scope) -> View {
20 let count = state!(cx, || 0i32);
21 let show_help = state!(cx, || false);
22
23 let increment = with!(count => move || count.update(|n| *n += 1));
24 let decrement = with!(count => move || count.update(|n| *n -= 1));
25
26 // F1 toggles help
27 cx.use_command(
28 KeyBinding::key(KeyCode::F(1)),
29 with!(show_help => move || show_help.update(|v| *v = !*v)),
30 );
31
32 View::vstack()
33 .child(View::styled_text("Counter").bold().build())
34 .child(View::gap(1))
35 .child(View::text(format!("Count: {}", count.get())))
36 .child(View::gap(1))
37 .child(
38 View::hstack()
39 .child(
40 View::button()
41 .label("Decrement")
42 .on_press(decrement)
43 .build(),
44 )
45 .child(View::text(" "))
46 .child(
47 View::button()
48 .label("Increment")
49 .on_press(increment)
50 .build(),
51 )
52 .build(),
53 )
54 .child(View::gap(1))
55 .child(
56 View::styled_text("Tab to switch • Enter to press • F1 for help • Ctrl+Q to quit")
57 .dim()
58 .build(),
59 )
60 .child(
61 View::modal()
62 .visible(show_help.get())
63 .title("Example 02: Counter")
64 .on_dismiss(with!(show_help => move || show_help.set(false)))
65 .child(
66 View::vstack()
67 .child(View::styled_text("What you're seeing").bold().build())
68 .child(View::text("• state!() macro for reactive state"))
69 .child(View::text("• View::button() with on_press callbacks"))
70 .child(View::text(
71 "• The with!() macro for capturing state in closures",
72 ))
73 .child(View::gap(1))
74 .child(View::styled_text("Key concepts").bold().build())
75 .child(View::text("• State persists across renders"))
76 .child(View::text("• Updating state triggers a re-render"))
77 .child(View::text("• Tab navigates between focusable elements"))
78 .child(View::gap(1))
79 .child(View::styled_text("Try this").bold().build())
80 .child(View::text("• Press +/- rapidly - notice instant updates"))
81 .child(View::text(
82 "• The UI stays in sync with state automatically",
83 ))
84 .child(View::gap(1))
85 .child(View::styled_text("Next up").bold().build())
86 .child(View::text("→ 03_theme_switcher: styling and colors"))
87 .child(View::gap(1))
88 .child(View::styled_text("Press Escape to close").dim().build())
89 .build(),
90 )
91 .build(),
92 )
93 .build()
94 }24 fn render(&self, cx: Scope) -> View {
25 let show_help = state!(cx, || false);
26
27 // F1 toggles help
28 cx.use_command(
29 KeyBinding::key(KeyCode::F(1)),
30 with!(show_help => move || show_help.update(|v| *v = !*v)),
31 );
32 View::vstack()
33 .child(View::styled_text("Status Bar Examples").bold().build())
34 .child(View::text(""))
35 .child(View::text("Basic status bar (left only):"))
36 .child(View::status_bar().left("NORMAL").build())
37 .child(View::text(""))
38 .child(View::text("Left and right sections:"))
39 .child(
40 View::status_bar()
41 .left("INSERT")
42 .right("Ln 42, Col 8")
43 .build(),
44 )
45 .child(View::text(""))
46 .child(View::text("All three sections:"))
47 .child(
48 View::status_bar()
49 .left("VISUAL")
50 .center("main.rs")
51 .right("UTF-8 | LF | Rust")
52 .build(),
53 )
54 .child(View::text(""))
55 .child(View::text("Custom colors (green on dark):"))
56 .child(
57 View::status_bar()
58 .left("SUCCESS")
59 .center("All tests passed")
60 .right("100%")
61 .fg(Color::Green)
62 .bg(Color::DarkGreen)
63 .build(),
64 )
65 .child(View::text(""))
66 .child(View::text("Editor-style status bar:"))
67 .child(
68 View::status_bar()
69 .left("-- INSERT --")
70 .center("~/projects/myapp/src/main.rs [+]")
71 .right("1/100 | 50%")
72 .build(),
73 )
74 .child(View::spacer())
75 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
76 .child(
77 View::modal()
78 .visible(show_help.get())
79 .title("Example 19: Status Bar")
80 .on_dismiss(with!(show_help => move || show_help.set(false)))
81 .child(
82 View::vstack()
83 .child(View::styled_text("What you're seeing").bold().build())
84 .child(View::text("• Status bars with left/center/right sections"))
85 .child(View::text("• Custom foreground and background colors"))
86 .child(View::text("• Editor-style status line example"))
87 .child(View::gap(1))
88 .child(View::styled_text("Key concepts").bold().build())
89 .child(View::text("• View::status_bar() creates status lines"))
90 .child(View::text("• .left(), .center(), .right() for sections"))
91 .child(View::text("• .fg() and .bg() for custom colors"))
92 .child(View::text("• Great for showing mode, file info, etc."))
93 .child(View::gap(1))
94 .child(View::styled_text("Try this").bold().build())
95 .child(View::text("• Compare different status bar styles"))
96 .child(View::text("• Notice how sections align"))
97 .child(View::gap(1))
98 .child(View::styled_text("Next up").bold().build())
99 .child(View::text("→ 20_menu_bar: dropdown menus"))
100 .child(View::gap(1))
101 .child(View::styled_text("Press Escape to close").dim().build())
102 .build(),
103 )
104 .build(),
105 )
106 .build()
107 }- examples/09_syntax_comparison.rs
- examples/35_slider.rs
- examples/15_markdown.rs
- examples/18_progress_bar.rs
- examples/16_tree.rs
- examples/05_todo_list.rs
- examples/37_error_boundary.rs
- examples/38_custom_widget.rs
- examples/10_state_explained.rs
- examples/32_effects.rs
- examples/13_split_panes.rs
- examples/07_file_browser.rs
- examples/12_text_area.rs
- examples/06_log_viewer.rs
- examples/24_async_data.rs
- examples/34_channels_and_intervals.rs
- examples/08_system_monitor.rs
- examples/39_port.rs
- examples/25_context.rs
- examples/14_tabs.rs
- examples/11_checkbox.rs
- examples/17_table.rs
- examples/29_canvas.rs
- examples/20_menu_bar.rs
- examples/26_radio_buttons.rs
- examples/21_toasts.rs
- examples/36_reducer.rs
- examples/03_theme_switcher.rs
- examples/28_shared_state.rs
- examples/27_keyed_state.rs
- examples/31_animated_canvas.rs
- examples/22_forms.rs
- examples/23_modal.rs
Sourcepub fn styled_text(content: impl Into<String>) -> TextBuilder
pub fn styled_text(content: impl Into<String>) -> TextBuilder
Create a styled text builder.
Examples found in repository?
19 fn render(&self, cx: Scope) -> View {
20 let show_help = state!(cx, || false);
21
22 // F1 toggles help
23 cx.use_command(
24 KeyBinding::key(KeyCode::F(1)),
25 with!(show_help => move || show_help.update(|v| *v = !*v)),
26 );
27
28 View::vstack()
29 .child(View::styled_text("Hello World").bold().build())
30 .child(View::gap(1))
31 .child(View::text("Welcome to Telex!"))
32 .child(View::gap(1))
33 .child(
34 View::styled_text("F1 for help • Ctrl+Q to quit")
35 .dim()
36 .build(),
37 )
38 .child(
39 View::modal()
40 .visible(show_help.get())
41 .title("Example 01: Hello World")
42 .on_dismiss(with!(show_help => move || show_help.set(false)))
43 .child(
44 View::vstack()
45 .child(View::styled_text("What you're seeing").bold().build())
46 .child(View::text(
47 "• Basic app structure with struct + Component trait",
48 ))
49 .child(View::text(
50 "• View::text() and View::styled_text() for display",
51 ))
52 .child(View::text("• View::vstack() for vertical layout"))
53 .child(View::gap(1))
54 .child(View::styled_text("Key concepts").bold().build())
55 .child(View::text("• Every Telex app implements Component"))
56 .child(View::text("• render() returns a View tree"))
57 .child(View::text("• No state yet - this is purely static"))
58 .child(View::gap(1))
59 .child(View::styled_text("Next up").bold().build())
60 .child(View::text("→ 02_counter: add state and interactivity"))
61 .child(View::gap(1))
62 .child(View::styled_text("Press Escape to close").dim().build())
63 .build(),
64 )
65 .build(),
66 )
67 .build()
68 }More examples
30 fn render(&self, cx: Scope) -> View {
31 let show_help = state!(cx, || false);
32
33 // F1 toggles help
34 cx.use_command(
35 KeyBinding::key(KeyCode::F(1)),
36 with!(show_help => move || show_help.update(|v| *v = !*v)),
37 );
38 View::vstack()
39 .spacing(1)
40 .child(
41 View::styled_text("Image Widget Demo (Kitty Graphics)")
42 .bold()
43 .build(),
44 )
45 .child(View::text("Requires Kitty, Ghostty, or WezTerm terminal"))
46 .child(View::text(""))
47 // Load image from file path
48 .child(View::text("Logo (from file path):"))
49 .child(View::image().file("assets/telex-tui.png").build())
50 .child(View::text(""))
51 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
52 .child(
53 View::modal()
54 .visible(show_help.get())
55 .title("Example 30: Image")
56 .on_dismiss(with!(show_help => move || show_help.set(false)))
57 .child(
58 View::vstack()
59 .child(View::styled_text("What you're seeing").bold().build())
60 .child(View::text("• Image display via Kitty protocol"))
61 .child(View::text("• PNG/JPEG/GIF support"))
62 .child(View::text("• Loaded from file path"))
63 .child(View::gap(1))
64 .child(View::styled_text("Key concepts").bold().build())
65 .child(View::text("• View::image() displays images"))
66 .child(View::text("• .file(\"path\") loads from disk"))
67 .child(View::text("• .bytes(data) for embedded images"))
68 .child(View::text("• Works in Kitty/Ghostty/WezTerm"))
69 .child(View::gap(1))
70 .child(View::styled_text("Try this").bold().build())
71 .child(View::text("• Run in compatible terminal"))
72 .child(View::text("• See the Telex logo rendered"))
73 .child(View::gap(1))
74 .child(View::styled_text("Next up").bold().build())
75 .child(View::text("→ 31_animated_canvas: animations"))
76 .child(View::gap(1))
77 .child(View::styled_text("Press Escape to close").dim().build())
78 .build(),
79 )
80 .build(),
81 )
82 .build()
83 }22 fn render(&self, cx: Scope) -> View {
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // Stream that yields elapsed seconds
32 let elapsed = stream!(cx, || {
33 (0u64..).inspect(|&s| {
34 if s > 0 {
35 std::thread::sleep(Duration::from_secs(1));
36 }
37 })
38 });
39
40 let seconds = elapsed.get();
41 let is_running = elapsed.is_loading();
42
43 // Format as MM:SS
44 let minutes = seconds / 60;
45 let secs = seconds % 60;
46 let time_display = format!("{:02}:{:02}", minutes, secs);
47
48 View::vstack()
49 .child(View::styled_text("Timer").color(Color::Cyan).bold().build())
50 .child(View::gap(1))
51 .child(
52 View::hstack()
53 .child(View::styled_text(&time_display).bold().build())
54 .child(if is_running {
55 View::styled_text(" ●").color(Color::Green).build()
56 } else {
57 View::styled_text(" ○").dim().build()
58 })
59 .build(),
60 )
61 .child(View::gap(1))
62 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
63 .child(
64 View::modal()
65 .visible(show_help.get())
66 .title("Example 04: Timer")
67 .on_dismiss(with!(show_help => move || show_help.set(false)))
68 .child(
69 View::vstack()
70 .child(View::styled_text("What you're seeing").bold().build())
71 .child(View::text("• stream!() macro for background data"))
72 .child(View::text("• Auto-updating UI without user input"))
73 .child(View::text("• Green dot = stream is running"))
74 .child(View::gap(1))
75 .child(View::styled_text("Key concepts").bold().build())
76 .child(View::text("• Streams run in background threads"))
77 .child(View::text("• Each yielded value triggers a re-render"))
78 .child(View::text("• is_loading() tells you if stream is active"))
79 .child(View::gap(1))
80 .child(View::styled_text("Try this").bold().build())
81 .child(View::text("• Just watch - the timer ticks automatically"))
82 .child(View::text("• No button presses needed for updates"))
83 .child(View::gap(1))
84 .child(View::styled_text("Next up").bold().build())
85 .child(View::text("→ 05_todo_list: text input and list management"))
86 .child(View::gap(1))
87 .child(View::styled_text("Press Escape to close").dim().build())
88 .build(),
89 )
90 .build(),
91 )
92 .build()
93 }19 fn render(&self, cx: Scope) -> View {
20 let count = state!(cx, || 0i32);
21 let show_help = state!(cx, || false);
22
23 let increment = with!(count => move || count.update(|n| *n += 1));
24 let decrement = with!(count => move || count.update(|n| *n -= 1));
25
26 // F1 toggles help
27 cx.use_command(
28 KeyBinding::key(KeyCode::F(1)),
29 with!(show_help => move || show_help.update(|v| *v = !*v)),
30 );
31
32 View::vstack()
33 .child(View::styled_text("Counter").bold().build())
34 .child(View::gap(1))
35 .child(View::text(format!("Count: {}", count.get())))
36 .child(View::gap(1))
37 .child(
38 View::hstack()
39 .child(
40 View::button()
41 .label("Decrement")
42 .on_press(decrement)
43 .build(),
44 )
45 .child(View::text(" "))
46 .child(
47 View::button()
48 .label("Increment")
49 .on_press(increment)
50 .build(),
51 )
52 .build(),
53 )
54 .child(View::gap(1))
55 .child(
56 View::styled_text("Tab to switch • Enter to press • F1 for help • Ctrl+Q to quit")
57 .dim()
58 .build(),
59 )
60 .child(
61 View::modal()
62 .visible(show_help.get())
63 .title("Example 02: Counter")
64 .on_dismiss(with!(show_help => move || show_help.set(false)))
65 .child(
66 View::vstack()
67 .child(View::styled_text("What you're seeing").bold().build())
68 .child(View::text("• state!() macro for reactive state"))
69 .child(View::text("• View::button() with on_press callbacks"))
70 .child(View::text(
71 "• The with!() macro for capturing state in closures",
72 ))
73 .child(View::gap(1))
74 .child(View::styled_text("Key concepts").bold().build())
75 .child(View::text("• State persists across renders"))
76 .child(View::text("• Updating state triggers a re-render"))
77 .child(View::text("• Tab navigates between focusable elements"))
78 .child(View::gap(1))
79 .child(View::styled_text("Try this").bold().build())
80 .child(View::text("• Press +/- rapidly - notice instant updates"))
81 .child(View::text(
82 "• The UI stays in sync with state automatically",
83 ))
84 .child(View::gap(1))
85 .child(View::styled_text("Next up").bold().build())
86 .child(View::text("→ 03_theme_switcher: styling and colors"))
87 .child(View::gap(1))
88 .child(View::styled_text("Press Escape to close").dim().build())
89 .build(),
90 )
91 .build(),
92 )
93 .build()
94 }24 fn render(&self, cx: Scope) -> View {
25 let show_help = state!(cx, || false);
26
27 // F1 toggles help
28 cx.use_command(
29 KeyBinding::key(KeyCode::F(1)),
30 with!(show_help => move || show_help.update(|v| *v = !*v)),
31 );
32 View::vstack()
33 .child(View::styled_text("Status Bar Examples").bold().build())
34 .child(View::text(""))
35 .child(View::text("Basic status bar (left only):"))
36 .child(View::status_bar().left("NORMAL").build())
37 .child(View::text(""))
38 .child(View::text("Left and right sections:"))
39 .child(
40 View::status_bar()
41 .left("INSERT")
42 .right("Ln 42, Col 8")
43 .build(),
44 )
45 .child(View::text(""))
46 .child(View::text("All three sections:"))
47 .child(
48 View::status_bar()
49 .left("VISUAL")
50 .center("main.rs")
51 .right("UTF-8 | LF | Rust")
52 .build(),
53 )
54 .child(View::text(""))
55 .child(View::text("Custom colors (green on dark):"))
56 .child(
57 View::status_bar()
58 .left("SUCCESS")
59 .center("All tests passed")
60 .right("100%")
61 .fg(Color::Green)
62 .bg(Color::DarkGreen)
63 .build(),
64 )
65 .child(View::text(""))
66 .child(View::text("Editor-style status bar:"))
67 .child(
68 View::status_bar()
69 .left("-- INSERT --")
70 .center("~/projects/myapp/src/main.rs [+]")
71 .right("1/100 | 50%")
72 .build(),
73 )
74 .child(View::spacer())
75 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
76 .child(
77 View::modal()
78 .visible(show_help.get())
79 .title("Example 19: Status Bar")
80 .on_dismiss(with!(show_help => move || show_help.set(false)))
81 .child(
82 View::vstack()
83 .child(View::styled_text("What you're seeing").bold().build())
84 .child(View::text("• Status bars with left/center/right sections"))
85 .child(View::text("• Custom foreground and background colors"))
86 .child(View::text("• Editor-style status line example"))
87 .child(View::gap(1))
88 .child(View::styled_text("Key concepts").bold().build())
89 .child(View::text("• View::status_bar() creates status lines"))
90 .child(View::text("• .left(), .center(), .right() for sections"))
91 .child(View::text("• .fg() and .bg() for custom colors"))
92 .child(View::text("• Great for showing mode, file info, etc."))
93 .child(View::gap(1))
94 .child(View::styled_text("Try this").bold().build())
95 .child(View::text("• Compare different status bar styles"))
96 .child(View::text("• Notice how sections align"))
97 .child(View::gap(1))
98 .child(View::styled_text("Next up").bold().build())
99 .child(View::text("→ 20_menu_bar: dropdown menus"))
100 .child(View::gap(1))
101 .child(View::styled_text("Press Escape to close").dim().build())
102 .build(),
103 )
104 .build(),
105 )
106 .build()
107 }24 fn render(&self, cx: Scope) -> View {
25 let use_jsx = state!(cx, || false);
26 let show_help = state!(cx, || false);
27
28 // F1 toggles help
29 cx.use_command(
30 KeyBinding::key(KeyCode::F(1)),
31 with!(show_help => move || show_help.update(|v| *v = !*v)),
32 );
33
34 let toggle = with!(use_jsx => move || use_jsx.set(!use_jsx.get()));
35
36 // Show which syntax is currently displayed
37 let syntax_name = if use_jsx.get() {
38 "view! macro (JSX-like)"
39 } else {
40 "Builder pattern"
41 };
42
43 View::vstack()
44 .child(
45 View::styled_text("Syntax Comparison")
46 .color(Color::Cyan)
47 .bold()
48 .build(),
49 )
50 .child(
51 View::styled_text("Same UI, two ways to write it")
52 .dim()
53 .build(),
54 )
55 .child(View::gap(1))
56 .child(
57 View::hstack()
58 .child(View::text("Current syntax: "))
59 .child(
60 View::styled_text(syntax_name)
61 .color(Color::Yellow)
62 .bold()
63 .build(),
64 )
65 .build(),
66 )
67 .child(View::gap(1))
68 .child(
69 View::boxed()
70 .border(true)
71 .padding(1)
72 .child(if use_jsx.get() {
73 counter_jsx(cx.clone())
74 } else {
75 counter_builder(cx.clone())
76 })
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::button()
82 .label("Toggle Syntax")
83 .on_press(toggle)
84 .build(),
85 )
86 .child(View::gap(1))
87 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
88 .child(
89 View::modal()
90 .visible(show_help.get())
91 .title("Example 09: Syntax Comparison")
92 .on_dismiss(with!(show_help => move || show_help.set(false)))
93 .child(
94 View::vstack()
95 .child(View::styled_text("What you're seeing").bold().build())
96 .child(View::text("• Two syntaxes that produce identical output"))
97 .child(View::text("• Builder: View::vstack().child(...).build()"))
98 .child(View::text("• Macro: view! { <VStack>...</VStack> }"))
99 .child(View::gap(1))
100 .child(View::styled_text("Key concepts").bold().build())
101 .child(View::text("• Builder is Rust-native, IDE-friendly"))
102 .child(View::text("• view! macro is JSX-like, less boilerplate"))
103 .child(View::text("• Choose based on your preference"))
104 .child(View::gap(1))
105 .child(View::styled_text("Try this").bold().build())
106 .child(View::text("• Toggle between syntaxes"))
107 .child(View::text("• Notice the output is identical"))
108 .child(View::text("• Check the source code to see both styles"))
109 .child(View::gap(1))
110 .child(View::styled_text("Next up").bold().build())
111 .child(View::text("→ 10_state_explained: deep dive into state"))
112 .child(View::gap(1))
113 .child(View::styled_text("Press Escape to close").dim().build())
114 .build(),
115 )
116 .build(),
117 )
118 .build()
119 }
120}
121
122// =============================================================================
123// Builder Pattern
124// =============================================================================
125// Rust-native, explicit, IDE-friendly. Each method call is clear.
126// Good for: Complex conditional logic, learning the API, debugging.
127
128fn counter_builder(cx: Scope) -> View {
129 let count = state!(cx, || 0i32);
130
131 // with! macro clones the handle for you - much cleaner!
132 let increment = with!(count => move || count.update(|n| *n += 1));
133 let decrement = with!(count => move || count.update(|n| *n -= 1));
134
135 View::vstack()
136 .child(
137 View::styled_text("// Built with builder pattern")
138 .color(Color::DarkGrey)
139 .build(),
140 )
141 .child(View::gap(1))
142 .child(
143 View::styled_text(format!("Count: {}", count.get()))
144 .bold()
145 .build(),
146 )
147 .child(View::gap(1))
148 .child(
149 View::hstack()
150 .child(View::button().label(" - ").on_press(decrement).build())
151 .child(View::text(" "))
152 .child(View::button().label(" + ").on_press(increment).build())
153 .build(),
154 )
155 .build()
156}- examples/35_slider.rs
- examples/15_markdown.rs
- examples/18_progress_bar.rs
- examples/16_tree.rs
- examples/05_todo_list.rs
- examples/37_error_boundary.rs
- examples/38_custom_widget.rs
- examples/10_state_explained.rs
- examples/32_effects.rs
- examples/13_split_panes.rs
- examples/07_file_browser.rs
- examples/12_text_area.rs
- examples/06_log_viewer.rs
- examples/24_async_data.rs
- examples/34_channels_and_intervals.rs
- examples/08_system_monitor.rs
- examples/39_port.rs
- examples/25_context.rs
- examples/14_tabs.rs
- examples/11_checkbox.rs
- examples/17_table.rs
- examples/29_canvas.rs
- examples/20_menu_bar.rs
- examples/26_radio_buttons.rs
- examples/21_toasts.rs
- examples/36_reducer.rs
- examples/03_theme_switcher.rs
- examples/28_shared_state.rs
- examples/27_keyed_state.rs
- examples/31_animated_canvas.rs
- examples/22_forms.rs
- examples/23_modal.rs
Sourcepub fn vstack() -> VStackBuilder
pub fn vstack() -> VStackBuilder
Create a vertical stack builder.
Examples found in repository?
5fn app(cx: Scope) -> View {
6 let terminal = terminal!(cx);
7
8 // Spawn bash on first render
9 if !terminal.is_started() {
10 if let Err(e) = terminal.spawn("bash", &[], 80, 24) {
11 eprintln!("Failed to spawn terminal: {}", e);
12 }
13 }
14
15 View::vstack()
16 .child(View::text("Telex Terminal Demo"))
17 .child(View::text(
18 "Press Ctrl+Shift+[ to escape terminal focus, Tab to navigate",
19 ))
20 .child(View::terminal().handle(terminal).build())
21 .build()
22}More examples
19 fn render(&self, cx: Scope) -> View {
20 let show_help = state!(cx, || false);
21
22 // F1 toggles help
23 cx.use_command(
24 KeyBinding::key(KeyCode::F(1)),
25 with!(show_help => move || show_help.update(|v| *v = !*v)),
26 );
27
28 View::vstack()
29 .child(View::styled_text("Hello World").bold().build())
30 .child(View::gap(1))
31 .child(View::text("Welcome to Telex!"))
32 .child(View::gap(1))
33 .child(
34 View::styled_text("F1 for help • Ctrl+Q to quit")
35 .dim()
36 .build(),
37 )
38 .child(
39 View::modal()
40 .visible(show_help.get())
41 .title("Example 01: Hello World")
42 .on_dismiss(with!(show_help => move || show_help.set(false)))
43 .child(
44 View::vstack()
45 .child(View::styled_text("What you're seeing").bold().build())
46 .child(View::text(
47 "• Basic app structure with struct + Component trait",
48 ))
49 .child(View::text(
50 "• View::text() and View::styled_text() for display",
51 ))
52 .child(View::text("• View::vstack() for vertical layout"))
53 .child(View::gap(1))
54 .child(View::styled_text("Key concepts").bold().build())
55 .child(View::text("• Every Telex app implements Component"))
56 .child(View::text("• render() returns a View tree"))
57 .child(View::text("• No state yet - this is purely static"))
58 .child(View::gap(1))
59 .child(View::styled_text("Next up").bold().build())
60 .child(View::text("→ 02_counter: add state and interactivity"))
61 .child(View::gap(1))
62 .child(View::styled_text("Press Escape to close").dim().build())
63 .build(),
64 )
65 .build(),
66 )
67 .build()
68 }30 fn render(&self, cx: Scope) -> View {
31 let show_help = state!(cx, || false);
32
33 // F1 toggles help
34 cx.use_command(
35 KeyBinding::key(KeyCode::F(1)),
36 with!(show_help => move || show_help.update(|v| *v = !*v)),
37 );
38 View::vstack()
39 .spacing(1)
40 .child(
41 View::styled_text("Image Widget Demo (Kitty Graphics)")
42 .bold()
43 .build(),
44 )
45 .child(View::text("Requires Kitty, Ghostty, or WezTerm terminal"))
46 .child(View::text(""))
47 // Load image from file path
48 .child(View::text("Logo (from file path):"))
49 .child(View::image().file("assets/telex-tui.png").build())
50 .child(View::text(""))
51 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
52 .child(
53 View::modal()
54 .visible(show_help.get())
55 .title("Example 30: Image")
56 .on_dismiss(with!(show_help => move || show_help.set(false)))
57 .child(
58 View::vstack()
59 .child(View::styled_text("What you're seeing").bold().build())
60 .child(View::text("• Image display via Kitty protocol"))
61 .child(View::text("• PNG/JPEG/GIF support"))
62 .child(View::text("• Loaded from file path"))
63 .child(View::gap(1))
64 .child(View::styled_text("Key concepts").bold().build())
65 .child(View::text("• View::image() displays images"))
66 .child(View::text("• .file(\"path\") loads from disk"))
67 .child(View::text("• .bytes(data) for embedded images"))
68 .child(View::text("• Works in Kitty/Ghostty/WezTerm"))
69 .child(View::gap(1))
70 .child(View::styled_text("Try this").bold().build())
71 .child(View::text("• Run in compatible terminal"))
72 .child(View::text("• See the Telex logo rendered"))
73 .child(View::gap(1))
74 .child(View::styled_text("Next up").bold().build())
75 .child(View::text("→ 31_animated_canvas: animations"))
76 .child(View::gap(1))
77 .child(View::styled_text("Press Escape to close").dim().build())
78 .build(),
79 )
80 .build(),
81 )
82 .build()
83 }22 fn render(&self, cx: Scope) -> View {
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // Stream that yields elapsed seconds
32 let elapsed = stream!(cx, || {
33 (0u64..).inspect(|&s| {
34 if s > 0 {
35 std::thread::sleep(Duration::from_secs(1));
36 }
37 })
38 });
39
40 let seconds = elapsed.get();
41 let is_running = elapsed.is_loading();
42
43 // Format as MM:SS
44 let minutes = seconds / 60;
45 let secs = seconds % 60;
46 let time_display = format!("{:02}:{:02}", minutes, secs);
47
48 View::vstack()
49 .child(View::styled_text("Timer").color(Color::Cyan).bold().build())
50 .child(View::gap(1))
51 .child(
52 View::hstack()
53 .child(View::styled_text(&time_display).bold().build())
54 .child(if is_running {
55 View::styled_text(" ●").color(Color::Green).build()
56 } else {
57 View::styled_text(" ○").dim().build()
58 })
59 .build(),
60 )
61 .child(View::gap(1))
62 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
63 .child(
64 View::modal()
65 .visible(show_help.get())
66 .title("Example 04: Timer")
67 .on_dismiss(with!(show_help => move || show_help.set(false)))
68 .child(
69 View::vstack()
70 .child(View::styled_text("What you're seeing").bold().build())
71 .child(View::text("• stream!() macro for background data"))
72 .child(View::text("• Auto-updating UI without user input"))
73 .child(View::text("• Green dot = stream is running"))
74 .child(View::gap(1))
75 .child(View::styled_text("Key concepts").bold().build())
76 .child(View::text("• Streams run in background threads"))
77 .child(View::text("• Each yielded value triggers a re-render"))
78 .child(View::text("• is_loading() tells you if stream is active"))
79 .child(View::gap(1))
80 .child(View::styled_text("Try this").bold().build())
81 .child(View::text("• Just watch - the timer ticks automatically"))
82 .child(View::text("• No button presses needed for updates"))
83 .child(View::gap(1))
84 .child(View::styled_text("Next up").bold().build())
85 .child(View::text("→ 05_todo_list: text input and list management"))
86 .child(View::gap(1))
87 .child(View::styled_text("Press Escape to close").dim().build())
88 .build(),
89 )
90 .build(),
91 )
92 .build()
93 }19 fn render(&self, cx: Scope) -> View {
20 let count = state!(cx, || 0i32);
21 let show_help = state!(cx, || false);
22
23 let increment = with!(count => move || count.update(|n| *n += 1));
24 let decrement = with!(count => move || count.update(|n| *n -= 1));
25
26 // F1 toggles help
27 cx.use_command(
28 KeyBinding::key(KeyCode::F(1)),
29 with!(show_help => move || show_help.update(|v| *v = !*v)),
30 );
31
32 View::vstack()
33 .child(View::styled_text("Counter").bold().build())
34 .child(View::gap(1))
35 .child(View::text(format!("Count: {}", count.get())))
36 .child(View::gap(1))
37 .child(
38 View::hstack()
39 .child(
40 View::button()
41 .label("Decrement")
42 .on_press(decrement)
43 .build(),
44 )
45 .child(View::text(" "))
46 .child(
47 View::button()
48 .label("Increment")
49 .on_press(increment)
50 .build(),
51 )
52 .build(),
53 )
54 .child(View::gap(1))
55 .child(
56 View::styled_text("Tab to switch • Enter to press • F1 for help • Ctrl+Q to quit")
57 .dim()
58 .build(),
59 )
60 .child(
61 View::modal()
62 .visible(show_help.get())
63 .title("Example 02: Counter")
64 .on_dismiss(with!(show_help => move || show_help.set(false)))
65 .child(
66 View::vstack()
67 .child(View::styled_text("What you're seeing").bold().build())
68 .child(View::text("• state!() macro for reactive state"))
69 .child(View::text("• View::button() with on_press callbacks"))
70 .child(View::text(
71 "• The with!() macro for capturing state in closures",
72 ))
73 .child(View::gap(1))
74 .child(View::styled_text("Key concepts").bold().build())
75 .child(View::text("• State persists across renders"))
76 .child(View::text("• Updating state triggers a re-render"))
77 .child(View::text("• Tab navigates between focusable elements"))
78 .child(View::gap(1))
79 .child(View::styled_text("Try this").bold().build())
80 .child(View::text("• Press +/- rapidly - notice instant updates"))
81 .child(View::text(
82 "• The UI stays in sync with state automatically",
83 ))
84 .child(View::gap(1))
85 .child(View::styled_text("Next up").bold().build())
86 .child(View::text("→ 03_theme_switcher: styling and colors"))
87 .child(View::gap(1))
88 .child(View::styled_text("Press Escape to close").dim().build())
89 .build(),
90 )
91 .build(),
92 )
93 .build()
94 }24 fn render(&self, cx: Scope) -> View {
25 let show_help = state!(cx, || false);
26
27 // F1 toggles help
28 cx.use_command(
29 KeyBinding::key(KeyCode::F(1)),
30 with!(show_help => move || show_help.update(|v| *v = !*v)),
31 );
32 View::vstack()
33 .child(View::styled_text("Status Bar Examples").bold().build())
34 .child(View::text(""))
35 .child(View::text("Basic status bar (left only):"))
36 .child(View::status_bar().left("NORMAL").build())
37 .child(View::text(""))
38 .child(View::text("Left and right sections:"))
39 .child(
40 View::status_bar()
41 .left("INSERT")
42 .right("Ln 42, Col 8")
43 .build(),
44 )
45 .child(View::text(""))
46 .child(View::text("All three sections:"))
47 .child(
48 View::status_bar()
49 .left("VISUAL")
50 .center("main.rs")
51 .right("UTF-8 | LF | Rust")
52 .build(),
53 )
54 .child(View::text(""))
55 .child(View::text("Custom colors (green on dark):"))
56 .child(
57 View::status_bar()
58 .left("SUCCESS")
59 .center("All tests passed")
60 .right("100%")
61 .fg(Color::Green)
62 .bg(Color::DarkGreen)
63 .build(),
64 )
65 .child(View::text(""))
66 .child(View::text("Editor-style status bar:"))
67 .child(
68 View::status_bar()
69 .left("-- INSERT --")
70 .center("~/projects/myapp/src/main.rs [+]")
71 .right("1/100 | 50%")
72 .build(),
73 )
74 .child(View::spacer())
75 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
76 .child(
77 View::modal()
78 .visible(show_help.get())
79 .title("Example 19: Status Bar")
80 .on_dismiss(with!(show_help => move || show_help.set(false)))
81 .child(
82 View::vstack()
83 .child(View::styled_text("What you're seeing").bold().build())
84 .child(View::text("• Status bars with left/center/right sections"))
85 .child(View::text("• Custom foreground and background colors"))
86 .child(View::text("• Editor-style status line example"))
87 .child(View::gap(1))
88 .child(View::styled_text("Key concepts").bold().build())
89 .child(View::text("• View::status_bar() creates status lines"))
90 .child(View::text("• .left(), .center(), .right() for sections"))
91 .child(View::text("• .fg() and .bg() for custom colors"))
92 .child(View::text("• Great for showing mode, file info, etc."))
93 .child(View::gap(1))
94 .child(View::styled_text("Try this").bold().build())
95 .child(View::text("• Compare different status bar styles"))
96 .child(View::text("• Notice how sections align"))
97 .child(View::gap(1))
98 .child(View::styled_text("Next up").bold().build())
99 .child(View::text("→ 20_menu_bar: dropdown menus"))
100 .child(View::gap(1))
101 .child(View::styled_text("Press Escape to close").dim().build())
102 .build(),
103 )
104 .build(),
105 )
106 .build()
107 }- examples/09_syntax_comparison.rs
- examples/35_slider.rs
- examples/15_markdown.rs
- examples/18_progress_bar.rs
- examples/16_tree.rs
- examples/05_todo_list.rs
- examples/37_error_boundary.rs
- examples/38_custom_widget.rs
- examples/10_state_explained.rs
- examples/32_effects.rs
- examples/13_split_panes.rs
- examples/07_file_browser.rs
- examples/12_text_area.rs
- examples/06_log_viewer.rs
- examples/24_async_data.rs
- examples/34_channels_and_intervals.rs
- examples/08_system_monitor.rs
- examples/39_port.rs
- examples/25_context.rs
- examples/14_tabs.rs
- examples/11_checkbox.rs
- examples/17_table.rs
- examples/29_canvas.rs
- examples/20_menu_bar.rs
- examples/26_radio_buttons.rs
- examples/21_toasts.rs
- examples/36_reducer.rs
- examples/03_theme_switcher.rs
- examples/28_shared_state.rs
- examples/27_keyed_state.rs
- examples/31_animated_canvas.rs
- examples/22_forms.rs
- examples/23_modal.rs
Sourcepub fn hstack() -> HStackBuilder
pub fn hstack() -> HStackBuilder
Create a horizontal stack builder.
Examples found in repository?
22 fn render(&self, cx: Scope) -> View {
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // Stream that yields elapsed seconds
32 let elapsed = stream!(cx, || {
33 (0u64..).inspect(|&s| {
34 if s > 0 {
35 std::thread::sleep(Duration::from_secs(1));
36 }
37 })
38 });
39
40 let seconds = elapsed.get();
41 let is_running = elapsed.is_loading();
42
43 // Format as MM:SS
44 let minutes = seconds / 60;
45 let secs = seconds % 60;
46 let time_display = format!("{:02}:{:02}", minutes, secs);
47
48 View::vstack()
49 .child(View::styled_text("Timer").color(Color::Cyan).bold().build())
50 .child(View::gap(1))
51 .child(
52 View::hstack()
53 .child(View::styled_text(&time_display).bold().build())
54 .child(if is_running {
55 View::styled_text(" ●").color(Color::Green).build()
56 } else {
57 View::styled_text(" ○").dim().build()
58 })
59 .build(),
60 )
61 .child(View::gap(1))
62 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
63 .child(
64 View::modal()
65 .visible(show_help.get())
66 .title("Example 04: Timer")
67 .on_dismiss(with!(show_help => move || show_help.set(false)))
68 .child(
69 View::vstack()
70 .child(View::styled_text("What you're seeing").bold().build())
71 .child(View::text("• stream!() macro for background data"))
72 .child(View::text("• Auto-updating UI without user input"))
73 .child(View::text("• Green dot = stream is running"))
74 .child(View::gap(1))
75 .child(View::styled_text("Key concepts").bold().build())
76 .child(View::text("• Streams run in background threads"))
77 .child(View::text("• Each yielded value triggers a re-render"))
78 .child(View::text("• is_loading() tells you if stream is active"))
79 .child(View::gap(1))
80 .child(View::styled_text("Try this").bold().build())
81 .child(View::text("• Just watch - the timer ticks automatically"))
82 .child(View::text("• No button presses needed for updates"))
83 .child(View::gap(1))
84 .child(View::styled_text("Next up").bold().build())
85 .child(View::text("→ 05_todo_list: text input and list management"))
86 .child(View::gap(1))
87 .child(View::styled_text("Press Escape to close").dim().build())
88 .build(),
89 )
90 .build(),
91 )
92 .build()
93 }More examples
19 fn render(&self, cx: Scope) -> View {
20 let count = state!(cx, || 0i32);
21 let show_help = state!(cx, || false);
22
23 let increment = with!(count => move || count.update(|n| *n += 1));
24 let decrement = with!(count => move || count.update(|n| *n -= 1));
25
26 // F1 toggles help
27 cx.use_command(
28 KeyBinding::key(KeyCode::F(1)),
29 with!(show_help => move || show_help.update(|v| *v = !*v)),
30 );
31
32 View::vstack()
33 .child(View::styled_text("Counter").bold().build())
34 .child(View::gap(1))
35 .child(View::text(format!("Count: {}", count.get())))
36 .child(View::gap(1))
37 .child(
38 View::hstack()
39 .child(
40 View::button()
41 .label("Decrement")
42 .on_press(decrement)
43 .build(),
44 )
45 .child(View::text(" "))
46 .child(
47 View::button()
48 .label("Increment")
49 .on_press(increment)
50 .build(),
51 )
52 .build(),
53 )
54 .child(View::gap(1))
55 .child(
56 View::styled_text("Tab to switch • Enter to press • F1 for help • Ctrl+Q to quit")
57 .dim()
58 .build(),
59 )
60 .child(
61 View::modal()
62 .visible(show_help.get())
63 .title("Example 02: Counter")
64 .on_dismiss(with!(show_help => move || show_help.set(false)))
65 .child(
66 View::vstack()
67 .child(View::styled_text("What you're seeing").bold().build())
68 .child(View::text("• state!() macro for reactive state"))
69 .child(View::text("• View::button() with on_press callbacks"))
70 .child(View::text(
71 "• The with!() macro for capturing state in closures",
72 ))
73 .child(View::gap(1))
74 .child(View::styled_text("Key concepts").bold().build())
75 .child(View::text("• State persists across renders"))
76 .child(View::text("• Updating state triggers a re-render"))
77 .child(View::text("• Tab navigates between focusable elements"))
78 .child(View::gap(1))
79 .child(View::styled_text("Try this").bold().build())
80 .child(View::text("• Press +/- rapidly - notice instant updates"))
81 .child(View::text(
82 "• The UI stays in sync with state automatically",
83 ))
84 .child(View::gap(1))
85 .child(View::styled_text("Next up").bold().build())
86 .child(View::text("→ 03_theme_switcher: styling and colors"))
87 .child(View::gap(1))
88 .child(View::styled_text("Press Escape to close").dim().build())
89 .build(),
90 )
91 .build(),
92 )
93 .build()
94 }24 fn render(&self, cx: Scope) -> View {
25 let use_jsx = state!(cx, || false);
26 let show_help = state!(cx, || false);
27
28 // F1 toggles help
29 cx.use_command(
30 KeyBinding::key(KeyCode::F(1)),
31 with!(show_help => move || show_help.update(|v| *v = !*v)),
32 );
33
34 let toggle = with!(use_jsx => move || use_jsx.set(!use_jsx.get()));
35
36 // Show which syntax is currently displayed
37 let syntax_name = if use_jsx.get() {
38 "view! macro (JSX-like)"
39 } else {
40 "Builder pattern"
41 };
42
43 View::vstack()
44 .child(
45 View::styled_text("Syntax Comparison")
46 .color(Color::Cyan)
47 .bold()
48 .build(),
49 )
50 .child(
51 View::styled_text("Same UI, two ways to write it")
52 .dim()
53 .build(),
54 )
55 .child(View::gap(1))
56 .child(
57 View::hstack()
58 .child(View::text("Current syntax: "))
59 .child(
60 View::styled_text(syntax_name)
61 .color(Color::Yellow)
62 .bold()
63 .build(),
64 )
65 .build(),
66 )
67 .child(View::gap(1))
68 .child(
69 View::boxed()
70 .border(true)
71 .padding(1)
72 .child(if use_jsx.get() {
73 counter_jsx(cx.clone())
74 } else {
75 counter_builder(cx.clone())
76 })
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::button()
82 .label("Toggle Syntax")
83 .on_press(toggle)
84 .build(),
85 )
86 .child(View::gap(1))
87 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
88 .child(
89 View::modal()
90 .visible(show_help.get())
91 .title("Example 09: Syntax Comparison")
92 .on_dismiss(with!(show_help => move || show_help.set(false)))
93 .child(
94 View::vstack()
95 .child(View::styled_text("What you're seeing").bold().build())
96 .child(View::text("• Two syntaxes that produce identical output"))
97 .child(View::text("• Builder: View::vstack().child(...).build()"))
98 .child(View::text("• Macro: view! { <VStack>...</VStack> }"))
99 .child(View::gap(1))
100 .child(View::styled_text("Key concepts").bold().build())
101 .child(View::text("• Builder is Rust-native, IDE-friendly"))
102 .child(View::text("• view! macro is JSX-like, less boilerplate"))
103 .child(View::text("• Choose based on your preference"))
104 .child(View::gap(1))
105 .child(View::styled_text("Try this").bold().build())
106 .child(View::text("• Toggle between syntaxes"))
107 .child(View::text("• Notice the output is identical"))
108 .child(View::text("• Check the source code to see both styles"))
109 .child(View::gap(1))
110 .child(View::styled_text("Next up").bold().build())
111 .child(View::text("→ 10_state_explained: deep dive into state"))
112 .child(View::gap(1))
113 .child(View::styled_text("Press Escape to close").dim().build())
114 .build(),
115 )
116 .build(),
117 )
118 .build()
119 }
120}
121
122// =============================================================================
123// Builder Pattern
124// =============================================================================
125// Rust-native, explicit, IDE-friendly. Each method call is clear.
126// Good for: Complex conditional logic, learning the API, debugging.
127
128fn counter_builder(cx: Scope) -> View {
129 let count = state!(cx, || 0i32);
130
131 // with! macro clones the handle for you - much cleaner!
132 let increment = with!(count => move || count.update(|n| *n += 1));
133 let decrement = with!(count => move || count.update(|n| *n -= 1));
134
135 View::vstack()
136 .child(
137 View::styled_text("// Built with builder pattern")
138 .color(Color::DarkGrey)
139 .build(),
140 )
141 .child(View::gap(1))
142 .child(
143 View::styled_text(format!("Count: {}", count.get()))
144 .bold()
145 .build(),
146 )
147 .child(View::gap(1))
148 .child(
149 View::hstack()
150 .child(View::button().label(" - ").on_press(decrement).build())
151 .child(View::text(" "))
152 .child(View::button().label(" + ").on_press(increment).build())
153 .build(),
154 )
155 .build()
156}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 }38 fn render(&self, cx: Scope) -> View {
39 let show_help = state!(cx, || false);
40
41 cx.use_command(
42 KeyBinding::key(KeyCode::F(1)),
43 with!(show_help => move || show_help.update(|v| *v = !*v)),
44 );
45
46 let count = state!(cx, || 0i32);
47
48 // The panic must happen at render time (inside render_view) so the
49 // error boundary's catch_unwind can catch it. A custom widget defers
50 // execution to the render pass.
51 let risky_view = View::custom(std::rc::Rc::new(std::cell::RefCell::new(RiskyCounter(count.get()))));
52
53 let fallback = View::vstack()
54 .child(View::styled_text("CAUGHT PANIC").color(Color::Red).bold().build())
55 .child(View::text("The child view panicked."))
56 .child(View::text("But the app is still running!"))
57 .build();
58
59 View::vstack()
60 .spacing(1)
61 .child(View::styled_text("Error Boundary Demo").bold().build())
62 .child(
63 View::hstack()
64 .spacing(2)
65 .child(
66 View::vstack()
67 .spacing(1)
68 .child(View::styled_text("Protected Panel").bold().build())
69 .child(
70 View::error_boundary()
71 .child(risky_view)
72 .fallback(fallback)
73 .build(),
74 )
75 .build(),
76 )
77 .child(
78 View::vstack()
79 .spacing(1)
80 .child(View::styled_text("How It Works").bold().build())
81 .child(View::text("The left panel asserts"))
82 .child(View::text("count < 5. When it hits"))
83 .child(View::text("5, the error boundary"))
84 .child(View::text("catches the panic and"))
85 .child(View::text("renders the fallback."))
86 .build(),
87 )
88 .build(),
89 )
90 .child(
91 View::hstack()
92 .spacing(1)
93 .child(
94 View::button()
95 .label("[ + Increment ]")
96 .on_press(with!(count => move || count.update(|n| *n += 1)))
97 .build(),
98 )
99 .child(
100 View::button()
101 .label("[ Reset to 0 ]")
102 .on_press(with!(count => move || count.set(0)))
103 .build(),
104 )
105 .child(View::styled_text(format!("count = {}", count.get())).dim().build())
106 .build(),
107 )
108 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
109 .child(
110 View::modal()
111 .visible(show_help.get())
112 .title("Example 37: Error Boundary")
113 .on_dismiss(with!(show_help => move || show_help.set(false)))
114 .child(
115 View::vstack()
116 .child(View::styled_text("What you're seeing").bold().build())
117 .child(View::text("• A counter that panics at 5"))
118 .child(View::text("• Error boundary catches the panic"))
119 .child(View::text("• Red fallback replaces the crash"))
120 .child(View::gap(1))
121 .child(View::styled_text("Key concepts").bold().build())
122 .child(View::text("• View::error_boundary()"))
123 .child(View::text("• .child(risky) .fallback(safe)"))
124 .child(View::text("• Panics are caught, not propagated"))
125 .child(View::text("• App keeps running after panic"))
126 .child(View::gap(1))
127 .child(View::styled_text("Try this").bold().build())
128 .child(View::text("• Increment to 5 to trigger panic"))
129 .child(View::text("• Reset to 0 to recover"))
130 .child(View::text("• Keep incrementing past 5"))
131 .child(View::gap(1))
132 .child(View::styled_text("Next up").bold().build())
133 .child(View::text("-> 38_custom_widget: Game of Life"))
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 }119 fn render(&self, cx: Scope) -> View {
120 let show_help = state!(cx, || false);
121
122 cx.use_command(
123 KeyBinding::key(KeyCode::F(1)),
124 with!(show_help => move || show_help.update(|v| *v = !*v)),
125 );
126
127 let game: State<GameOfLife> = state!(cx, || {
128 let mut g = GameOfLife::new();
129 g.randomize();
130 g
131 });
132 let generation = state!(cx, || 0u64);
133 let playing = state!(cx, || false);
134
135 // Auto-step when playing
136 interval!(cx, Duration::from_millis(150), with!(playing, game, generation => move || {
137 if playing.get() {
138 game.update(|g| g.step());
139 generation.update(|n| *n += 1);
140 }
141 }));
142
143 let widget = Rc::new(RefCell::new(game.get()));
144
145 View::vstack()
146 .spacing(1)
147 .child(View::styled_text("Game of Life").bold().build())
148 .child(
149 View::hstack()
150 .spacing(1)
151 .child(View::styled_text(format!("Gen: {}", generation.get())).dim().build())
152 .child(View::styled_text(format!("Alive: {}", game.get().alive_count())).dim().build())
153 .child(if playing.get() {
154 View::styled_text("PLAYING").color(Color::Green).bold().build()
155 } else {
156 View::styled_text("PAUSED").color(Color::Yellow).build()
157 })
158 .build(),
159 )
160 .child(View::custom(widget))
161 .child(
162 View::hstack()
163 .spacing(1)
164 .child(
165 View::button()
166 .label("[ Step ]")
167 .on_press(with!(game, generation => move || {
168 game.update(|g| g.step());
169 generation.update(|n| *n += 1);
170 }))
171 .build(),
172 )
173 .child(
174 View::button()
175 .label(if playing.get() { "[ Pause ]" } else { "[ Play ]" })
176 .on_press(with!(playing => move || playing.update(|p| *p = !*p)))
177 .build(),
178 )
179 .child(
180 View::button()
181 .label("[ Randomize ]")
182 .on_press(with!(game, generation => move || {
183 game.update(|g| g.randomize());
184 generation.set(0);
185 }))
186 .build(),
187 )
188 .child(
189 View::button()
190 .label("[ Clear ]")
191 .on_press(with!(game, generation => move || {
192 game.update(|g| g.clear());
193 generation.set(0);
194 }))
195 .build(),
196 )
197 .build(),
198 )
199 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
200 .child(
201 View::modal()
202 .visible(show_help.get())
203 .title("Example 38: Custom Widget")
204 .on_dismiss(with!(show_help => move || show_help.set(false)))
205 .child(
206 View::vstack()
207 .child(View::styled_text("What you're seeing").bold().build())
208 .child(View::text("• Conway's Game of Life"))
209 .child(View::text("• Custom Widget renders the grid"))
210 .child(View::text("• interval! drives auto-play"))
211 .child(View::gap(1))
212 .child(View::styled_text("Key concepts").bold().build())
213 .child(View::text("• impl Widget for YourStruct"))
214 .child(View::text("• render(area, buf) draws cells"))
215 .child(View::text("• height_hint / width_hint for sizing"))
216 .child(View::text("• View::custom(Rc<RefCell<W>>)"))
217 .child(View::gap(1))
218 .child(View::styled_text("Try this").bold().build())
219 .child(View::text("• Press Play to auto-step"))
220 .child(View::text("• Step manually one at a time"))
221 .child(View::text("• Randomize for a new pattern"))
222 .child(View::text("• Clear then Step to watch"))
223 .child(View::gap(1))
224 .child(View::styled_text("Next up").bold().build())
225 .child(View::text("-> 39_port: bidirectional comms"))
226 .child(View::gap(1))
227 .child(View::styled_text("Press Escape to close").dim().build())
228 .build(),
229 )
230 .build(),
231 )
232 .build()
233 }- examples/10_state_explained.rs
- examples/32_effects.rs
- examples/12_text_area.rs
- examples/06_log_viewer.rs
- examples/24_async_data.rs
- examples/34_channels_and_intervals.rs
- examples/08_system_monitor.rs
- examples/39_port.rs
- examples/25_context.rs
- examples/11_checkbox.rs
- examples/26_radio_buttons.rs
- examples/21_toasts.rs
- examples/36_reducer.rs
- examples/03_theme_switcher.rs
- examples/28_shared_state.rs
- examples/27_keyed_state.rs
- examples/22_forms.rs
- examples/23_modal.rs
Create a button builder.
Examples found in repository?
19 fn render(&self, cx: Scope) -> View {
20 let count = state!(cx, || 0i32);
21 let show_help = state!(cx, || false);
22
23 let increment = with!(count => move || count.update(|n| *n += 1));
24 let decrement = with!(count => move || count.update(|n| *n -= 1));
25
26 // F1 toggles help
27 cx.use_command(
28 KeyBinding::key(KeyCode::F(1)),
29 with!(show_help => move || show_help.update(|v| *v = !*v)),
30 );
31
32 View::vstack()
33 .child(View::styled_text("Counter").bold().build())
34 .child(View::gap(1))
35 .child(View::text(format!("Count: {}", count.get())))
36 .child(View::gap(1))
37 .child(
38 View::hstack()
39 .child(
40 View::button()
41 .label("Decrement")
42 .on_press(decrement)
43 .build(),
44 )
45 .child(View::text(" "))
46 .child(
47 View::button()
48 .label("Increment")
49 .on_press(increment)
50 .build(),
51 )
52 .build(),
53 )
54 .child(View::gap(1))
55 .child(
56 View::styled_text("Tab to switch • Enter to press • F1 for help • Ctrl+Q to quit")
57 .dim()
58 .build(),
59 )
60 .child(
61 View::modal()
62 .visible(show_help.get())
63 .title("Example 02: Counter")
64 .on_dismiss(with!(show_help => move || show_help.set(false)))
65 .child(
66 View::vstack()
67 .child(View::styled_text("What you're seeing").bold().build())
68 .child(View::text("• state!() macro for reactive state"))
69 .child(View::text("• View::button() with on_press callbacks"))
70 .child(View::text(
71 "• The with!() macro for capturing state in closures",
72 ))
73 .child(View::gap(1))
74 .child(View::styled_text("Key concepts").bold().build())
75 .child(View::text("• State persists across renders"))
76 .child(View::text("• Updating state triggers a re-render"))
77 .child(View::text("• Tab navigates between focusable elements"))
78 .child(View::gap(1))
79 .child(View::styled_text("Try this").bold().build())
80 .child(View::text("• Press +/- rapidly - notice instant updates"))
81 .child(View::text(
82 "• The UI stays in sync with state automatically",
83 ))
84 .child(View::gap(1))
85 .child(View::styled_text("Next up").bold().build())
86 .child(View::text("→ 03_theme_switcher: styling and colors"))
87 .child(View::gap(1))
88 .child(View::styled_text("Press Escape to close").dim().build())
89 .build(),
90 )
91 .build(),
92 )
93 .build()
94 }More examples
24 fn render(&self, cx: Scope) -> View {
25 let use_jsx = state!(cx, || false);
26 let show_help = state!(cx, || false);
27
28 // F1 toggles help
29 cx.use_command(
30 KeyBinding::key(KeyCode::F(1)),
31 with!(show_help => move || show_help.update(|v| *v = !*v)),
32 );
33
34 let toggle = with!(use_jsx => move || use_jsx.set(!use_jsx.get()));
35
36 // Show which syntax is currently displayed
37 let syntax_name = if use_jsx.get() {
38 "view! macro (JSX-like)"
39 } else {
40 "Builder pattern"
41 };
42
43 View::vstack()
44 .child(
45 View::styled_text("Syntax Comparison")
46 .color(Color::Cyan)
47 .bold()
48 .build(),
49 )
50 .child(
51 View::styled_text("Same UI, two ways to write it")
52 .dim()
53 .build(),
54 )
55 .child(View::gap(1))
56 .child(
57 View::hstack()
58 .child(View::text("Current syntax: "))
59 .child(
60 View::styled_text(syntax_name)
61 .color(Color::Yellow)
62 .bold()
63 .build(),
64 )
65 .build(),
66 )
67 .child(View::gap(1))
68 .child(
69 View::boxed()
70 .border(true)
71 .padding(1)
72 .child(if use_jsx.get() {
73 counter_jsx(cx.clone())
74 } else {
75 counter_builder(cx.clone())
76 })
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::button()
82 .label("Toggle Syntax")
83 .on_press(toggle)
84 .build(),
85 )
86 .child(View::gap(1))
87 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
88 .child(
89 View::modal()
90 .visible(show_help.get())
91 .title("Example 09: Syntax Comparison")
92 .on_dismiss(with!(show_help => move || show_help.set(false)))
93 .child(
94 View::vstack()
95 .child(View::styled_text("What you're seeing").bold().build())
96 .child(View::text("• Two syntaxes that produce identical output"))
97 .child(View::text("• Builder: View::vstack().child(...).build()"))
98 .child(View::text("• Macro: view! { <VStack>...</VStack> }"))
99 .child(View::gap(1))
100 .child(View::styled_text("Key concepts").bold().build())
101 .child(View::text("• Builder is Rust-native, IDE-friendly"))
102 .child(View::text("• view! macro is JSX-like, less boilerplate"))
103 .child(View::text("• Choose based on your preference"))
104 .child(View::gap(1))
105 .child(View::styled_text("Try this").bold().build())
106 .child(View::text("• Toggle between syntaxes"))
107 .child(View::text("• Notice the output is identical"))
108 .child(View::text("• Check the source code to see both styles"))
109 .child(View::gap(1))
110 .child(View::styled_text("Next up").bold().build())
111 .child(View::text("→ 10_state_explained: deep dive into state"))
112 .child(View::gap(1))
113 .child(View::styled_text("Press Escape to close").dim().build())
114 .build(),
115 )
116 .build(),
117 )
118 .build()
119 }
120}
121
122// =============================================================================
123// Builder Pattern
124// =============================================================================
125// Rust-native, explicit, IDE-friendly. Each method call is clear.
126// Good for: Complex conditional logic, learning the API, debugging.
127
128fn counter_builder(cx: Scope) -> View {
129 let count = state!(cx, || 0i32);
130
131 // with! macro clones the handle for you - much cleaner!
132 let increment = with!(count => move || count.update(|n| *n += 1));
133 let decrement = with!(count => move || count.update(|n| *n -= 1));
134
135 View::vstack()
136 .child(
137 View::styled_text("// Built with builder pattern")
138 .color(Color::DarkGrey)
139 .build(),
140 )
141 .child(View::gap(1))
142 .child(
143 View::styled_text(format!("Count: {}", count.get()))
144 .bold()
145 .build(),
146 )
147 .child(View::gap(1))
148 .child(
149 View::hstack()
150 .child(View::button().label(" - ").on_press(decrement).build())
151 .child(View::text(" "))
152 .child(View::button().label(" + ").on_press(increment).build())
153 .build(),
154 )
155 .build()
156}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 }38 fn render(&self, cx: Scope) -> View {
39 let show_help = state!(cx, || false);
40
41 cx.use_command(
42 KeyBinding::key(KeyCode::F(1)),
43 with!(show_help => move || show_help.update(|v| *v = !*v)),
44 );
45
46 let count = state!(cx, || 0i32);
47
48 // The panic must happen at render time (inside render_view) so the
49 // error boundary's catch_unwind can catch it. A custom widget defers
50 // execution to the render pass.
51 let risky_view = View::custom(std::rc::Rc::new(std::cell::RefCell::new(RiskyCounter(count.get()))));
52
53 let fallback = View::vstack()
54 .child(View::styled_text("CAUGHT PANIC").color(Color::Red).bold().build())
55 .child(View::text("The child view panicked."))
56 .child(View::text("But the app is still running!"))
57 .build();
58
59 View::vstack()
60 .spacing(1)
61 .child(View::styled_text("Error Boundary Demo").bold().build())
62 .child(
63 View::hstack()
64 .spacing(2)
65 .child(
66 View::vstack()
67 .spacing(1)
68 .child(View::styled_text("Protected Panel").bold().build())
69 .child(
70 View::error_boundary()
71 .child(risky_view)
72 .fallback(fallback)
73 .build(),
74 )
75 .build(),
76 )
77 .child(
78 View::vstack()
79 .spacing(1)
80 .child(View::styled_text("How It Works").bold().build())
81 .child(View::text("The left panel asserts"))
82 .child(View::text("count < 5. When it hits"))
83 .child(View::text("5, the error boundary"))
84 .child(View::text("catches the panic and"))
85 .child(View::text("renders the fallback."))
86 .build(),
87 )
88 .build(),
89 )
90 .child(
91 View::hstack()
92 .spacing(1)
93 .child(
94 View::button()
95 .label("[ + Increment ]")
96 .on_press(with!(count => move || count.update(|n| *n += 1)))
97 .build(),
98 )
99 .child(
100 View::button()
101 .label("[ Reset to 0 ]")
102 .on_press(with!(count => move || count.set(0)))
103 .build(),
104 )
105 .child(View::styled_text(format!("count = {}", count.get())).dim().build())
106 .build(),
107 )
108 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
109 .child(
110 View::modal()
111 .visible(show_help.get())
112 .title("Example 37: Error Boundary")
113 .on_dismiss(with!(show_help => move || show_help.set(false)))
114 .child(
115 View::vstack()
116 .child(View::styled_text("What you're seeing").bold().build())
117 .child(View::text("• A counter that panics at 5"))
118 .child(View::text("• Error boundary catches the panic"))
119 .child(View::text("• Red fallback replaces the crash"))
120 .child(View::gap(1))
121 .child(View::styled_text("Key concepts").bold().build())
122 .child(View::text("• View::error_boundary()"))
123 .child(View::text("• .child(risky) .fallback(safe)"))
124 .child(View::text("• Panics are caught, not propagated"))
125 .child(View::text("• App keeps running after panic"))
126 .child(View::gap(1))
127 .child(View::styled_text("Try this").bold().build())
128 .child(View::text("• Increment to 5 to trigger panic"))
129 .child(View::text("• Reset to 0 to recover"))
130 .child(View::text("• Keep incrementing past 5"))
131 .child(View::gap(1))
132 .child(View::styled_text("Next up").bold().build())
133 .child(View::text("-> 38_custom_widget: Game of Life"))
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 }119 fn render(&self, cx: Scope) -> View {
120 let show_help = state!(cx, || false);
121
122 cx.use_command(
123 KeyBinding::key(KeyCode::F(1)),
124 with!(show_help => move || show_help.update(|v| *v = !*v)),
125 );
126
127 let game: State<GameOfLife> = state!(cx, || {
128 let mut g = GameOfLife::new();
129 g.randomize();
130 g
131 });
132 let generation = state!(cx, || 0u64);
133 let playing = state!(cx, || false);
134
135 // Auto-step when playing
136 interval!(cx, Duration::from_millis(150), with!(playing, game, generation => move || {
137 if playing.get() {
138 game.update(|g| g.step());
139 generation.update(|n| *n += 1);
140 }
141 }));
142
143 let widget = Rc::new(RefCell::new(game.get()));
144
145 View::vstack()
146 .spacing(1)
147 .child(View::styled_text("Game of Life").bold().build())
148 .child(
149 View::hstack()
150 .spacing(1)
151 .child(View::styled_text(format!("Gen: {}", generation.get())).dim().build())
152 .child(View::styled_text(format!("Alive: {}", game.get().alive_count())).dim().build())
153 .child(if playing.get() {
154 View::styled_text("PLAYING").color(Color::Green).bold().build()
155 } else {
156 View::styled_text("PAUSED").color(Color::Yellow).build()
157 })
158 .build(),
159 )
160 .child(View::custom(widget))
161 .child(
162 View::hstack()
163 .spacing(1)
164 .child(
165 View::button()
166 .label("[ Step ]")
167 .on_press(with!(game, generation => move || {
168 game.update(|g| g.step());
169 generation.update(|n| *n += 1);
170 }))
171 .build(),
172 )
173 .child(
174 View::button()
175 .label(if playing.get() { "[ Pause ]" } else { "[ Play ]" })
176 .on_press(with!(playing => move || playing.update(|p| *p = !*p)))
177 .build(),
178 )
179 .child(
180 View::button()
181 .label("[ Randomize ]")
182 .on_press(with!(game, generation => move || {
183 game.update(|g| g.randomize());
184 generation.set(0);
185 }))
186 .build(),
187 )
188 .child(
189 View::button()
190 .label("[ Clear ]")
191 .on_press(with!(game, generation => move || {
192 game.update(|g| g.clear());
193 generation.set(0);
194 }))
195 .build(),
196 )
197 .build(),
198 )
199 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
200 .child(
201 View::modal()
202 .visible(show_help.get())
203 .title("Example 38: Custom Widget")
204 .on_dismiss(with!(show_help => move || show_help.set(false)))
205 .child(
206 View::vstack()
207 .child(View::styled_text("What you're seeing").bold().build())
208 .child(View::text("• Conway's Game of Life"))
209 .child(View::text("• Custom Widget renders the grid"))
210 .child(View::text("• interval! drives auto-play"))
211 .child(View::gap(1))
212 .child(View::styled_text("Key concepts").bold().build())
213 .child(View::text("• impl Widget for YourStruct"))
214 .child(View::text("• render(area, buf) draws cells"))
215 .child(View::text("• height_hint / width_hint for sizing"))
216 .child(View::text("• View::custom(Rc<RefCell<W>>)"))
217 .child(View::gap(1))
218 .child(View::styled_text("Try this").bold().build())
219 .child(View::text("• Press Play to auto-step"))
220 .child(View::text("• Step manually one at a time"))
221 .child(View::text("• Randomize for a new pattern"))
222 .child(View::text("• Clear then Step to watch"))
223 .child(View::gap(1))
224 .child(View::styled_text("Next up").bold().build())
225 .child(View::text("-> 39_port: bidirectional comms"))
226 .child(View::gap(1))
227 .child(View::styled_text("Press Escape to close").dim().build())
228 .build(),
229 )
230 .build(),
231 )
232 .build()
233 }21 fn render(&self, cx: Scope) -> View {
22 let count = state!(cx, || 0i32);
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // Clone handles for closures (this is the pattern being explained)
32 let count_for_increment = count.clone();
33 let count_for_decrement = count.clone();
34 let count_for_reset = count.clone();
35
36 let increment = move || {
37 let current = count_for_increment.get();
38 count_for_increment.set(current + 1);
39 };
40
41 let decrement = move || {
42 let current = count_for_decrement.get();
43 count_for_decrement.set(current - 1);
44 };
45
46 let reset = move || {
47 count_for_reset.set(0);
48 };
49
50 let current_value = count.get();
51
52 // Hook ordering demo
53 let _always_called_1 = state!(cx, || "hook 1");
54 let _always_called_2 = state!(cx, || "hook 2");
55
56 View::vstack()
57 .spacing(1)
58 .child(
59 View::styled_text("State Explained")
60 .color(Color::Cyan)
61 .bold()
62 .build(),
63 )
64 .child(
65 View::boxed()
66 .border(true)
67 .padding(1)
68 .child(
69 View::vstack()
70 .child(View::styled_text("The Mental Model:").bold().build())
71 .child(View::gap(1))
72 .child(View::text(" State<T> is a HANDLE, not data"))
73 .child(View::text(" clone() copies the handle, not the data"))
74 .child(View::text(" All handles point to ONE value"))
75 .child(View::gap(1))
76 .child(View::text(" count ──────┐"))
77 .child(View::text(" ├──► i32: 0 (shared!)"))
78 .child(View::text(" count2 ─────┘"))
79 .build(),
80 )
81 .build(),
82 )
83 .child(
84 View::hstack()
85 .spacing(1)
86 .child(View::text("Current value:"))
87 .child(
88 View::styled_text(format!("{}", current_value))
89 .color(Color::Yellow)
90 .bold()
91 .build(),
92 )
93 .build(),
94 )
95 .child(
96 View::hstack()
97 .spacing(1)
98 .child(View::button().label(" - ").on_press(decrement).build())
99 .child(View::button().label(" + ").on_press(increment).build())
100 .child(View::button().label("Reset").on_press(reset).build())
101 .build(),
102 )
103 .child(
104 View::styled_text("All three buttons modify the SAME underlying i32")
105 .dim()
106 .build(),
107 )
108 .child(View::gap(1))
109 .child(
110 View::styled_text("Tab navigate • F1 help • Ctrl+Q quit")
111 .dim()
112 .build(),
113 )
114 .child(
115 View::modal()
116 .visible(show_help.get())
117 .title("Example 10: State Explained")
118 .on_dismiss(with!(show_help => move || show_help.set(false)))
119 .child(
120 View::vstack()
121 .child(View::styled_text("What you're seeing").bold().build())
122 .child(View::text("• State<T> as a handle/pointer concept"))
123 .child(View::text("• Multiple closures sharing one value"))
124 .child(View::text("• Visual diagram of the mental model"))
125 .child(View::gap(1))
126 .child(View::styled_text("Key concepts").bold().build())
127 .child(View::text("• clone() copies handle, not data"))
128 .child(View::text("• All handles point to same underlying value"))
129 .child(View::text(
130 "• Hooks must be called in same order every render",
131 ))
132 .child(View::gap(1))
133 .child(View::styled_text("Important rule").bold().build())
134 .child(View::text("• NEVER put use_state inside an if block"))
135 .child(View::text("• Use with!() macro to simplify cloning"))
136 .child(View::gap(1))
137 .child(View::styled_text("Next up").bold().build())
138 .child(View::text("→ 11_checkbox: toggle controls"))
139 .child(View::gap(1))
140 .child(View::styled_text("Press Escape to close").dim().build())
141 .build(),
142 )
143 .build(),
144 )
145 .build()
146 }Sourcepub fn boxed() -> BoxBuilder
pub fn boxed() -> BoxBuilder
Create a box builder.
Examples found in repository?
24 fn render(&self, cx: Scope) -> View {
25 let use_jsx = state!(cx, || false);
26 let show_help = state!(cx, || false);
27
28 // F1 toggles help
29 cx.use_command(
30 KeyBinding::key(KeyCode::F(1)),
31 with!(show_help => move || show_help.update(|v| *v = !*v)),
32 );
33
34 let toggle = with!(use_jsx => move || use_jsx.set(!use_jsx.get()));
35
36 // Show which syntax is currently displayed
37 let syntax_name = if use_jsx.get() {
38 "view! macro (JSX-like)"
39 } else {
40 "Builder pattern"
41 };
42
43 View::vstack()
44 .child(
45 View::styled_text("Syntax Comparison")
46 .color(Color::Cyan)
47 .bold()
48 .build(),
49 )
50 .child(
51 View::styled_text("Same UI, two ways to write it")
52 .dim()
53 .build(),
54 )
55 .child(View::gap(1))
56 .child(
57 View::hstack()
58 .child(View::text("Current syntax: "))
59 .child(
60 View::styled_text(syntax_name)
61 .color(Color::Yellow)
62 .bold()
63 .build(),
64 )
65 .build(),
66 )
67 .child(View::gap(1))
68 .child(
69 View::boxed()
70 .border(true)
71 .padding(1)
72 .child(if use_jsx.get() {
73 counter_jsx(cx.clone())
74 } else {
75 counter_builder(cx.clone())
76 })
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::button()
82 .label("Toggle Syntax")
83 .on_press(toggle)
84 .build(),
85 )
86 .child(View::gap(1))
87 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
88 .child(
89 View::modal()
90 .visible(show_help.get())
91 .title("Example 09: Syntax Comparison")
92 .on_dismiss(with!(show_help => move || show_help.set(false)))
93 .child(
94 View::vstack()
95 .child(View::styled_text("What you're seeing").bold().build())
96 .child(View::text("• Two syntaxes that produce identical output"))
97 .child(View::text("• Builder: View::vstack().child(...).build()"))
98 .child(View::text("• Macro: view! { <VStack>...</VStack> }"))
99 .child(View::gap(1))
100 .child(View::styled_text("Key concepts").bold().build())
101 .child(View::text("• Builder is Rust-native, IDE-friendly"))
102 .child(View::text("• view! macro is JSX-like, less boilerplate"))
103 .child(View::text("• Choose based on your preference"))
104 .child(View::gap(1))
105 .child(View::styled_text("Try this").bold().build())
106 .child(View::text("• Toggle between syntaxes"))
107 .child(View::text("• Notice the output is identical"))
108 .child(View::text("• Check the source code to see both styles"))
109 .child(View::gap(1))
110 .child(View::styled_text("Next up").bold().build())
111 .child(View::text("→ 10_state_explained: deep dive into state"))
112 .child(View::gap(1))
113 .child(View::styled_text("Press Escape to close").dim().build())
114 .build(),
115 )
116 .build(),
117 )
118 .build()
119 }More examples
54 fn render(&self, cx: Scope) -> View {
55 let show_help = state!(cx, || false);
56
57 // F1 toggles help
58 cx.use_command(
59 KeyBinding::key(KeyCode::F(1)),
60 with!(show_help => move || show_help.update(|v| *v = !*v)),
61 );
62
63 let rendered = telex::markdown::render(DEMO_MARKDOWN);
64
65 View::vstack()
66 .child(
67 View::styled_text("Markdown Rendering Demo")
68 .color(Color::Cyan)
69 .bold()
70 .build(),
71 )
72 .child(
73 View::boxed()
74 .flex(1)
75 .child(
76 View::split()
77 .horizontal()
78 .ratio(0.4)
79 .first(
80 View::vstack()
81 .child(View::styled_text(" Source ").bold().build())
82 .child(
83 View::boxed()
84 .flex(1)
85 .border(true)
86 .scroll(true)
87 .child(View::text(DEMO_MARKDOWN))
88 .build(),
89 )
90 .build(),
91 )
92 .second(
93 View::vstack()
94 .child(View::styled_text(" Rendered ").bold().build())
95 .child(
96 View::boxed()
97 .flex(1)
98 .border(true)
99 .scroll(true)
100 .child(rendered)
101 .build(),
102 )
103 .build(),
104 )
105 .build(),
106 )
107 .build(),
108 )
109 .child(
110 View::styled_text("Tab: switch panes | ↑↓/jk: scroll | F1 help | Ctrl+Q: quit")
111 .dim()
112 .build(),
113 )
114 .child(
115 View::modal()
116 .visible(show_help.get())
117 .title("Example 15: Markdown")
118 .on_dismiss(with!(show_help => move || show_help.set(false)))
119 .child(
120 View::vstack()
121 .child(View::styled_text("What you're seeing").bold().build())
122 .child(View::text("• Side-by-side markdown source and rendered"))
123 .child(View::text("• Full markdown syntax support"))
124 .child(View::text("• Scrollable panes for long content"))
125 .child(View::gap(1))
126 .child(View::styled_text("Key concepts").bold().build())
127 .child(View::text("• telex::markdown::render() parses markdown"))
128 .child(View::text("• Returns a View tree with styled text"))
129 .child(View::text("• Code blocks, lists, quotes, headers"))
130 .child(View::text("• Split view for comparison"))
131 .child(View::gap(1))
132 .child(View::styled_text("Try this").bold().build())
133 .child(View::text("• Tab between source and rendered panes"))
134 .child(View::text("• Scroll with arrow keys or j/k"))
135 .child(View::text("• Compare source with rendered output"))
136 .child(View::gap(1))
137 .child(View::styled_text("Next up").bold().build())
138 .child(View::text("→ 16_progress: progress bars"))
139 .child(View::gap(1))
140 .child(View::styled_text("Press Escape to close").dim().build())
141 .build(),
142 )
143 .build(),
144 )
145 .build()
146 }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 // Track selected path
30 let selected = state!(cx, || vec![0usize]);
31
32 // Track expanded state for each node (by path prefix)
33 let expanded_paths = state!(cx, || {
34 vec![
35 vec![0], // src/ expanded
36 vec![0, 0], // src/components/ expanded
37 ]
38 });
39
40 // Build tree items with current expanded state
41 let items = build_tree(&expanded_paths.get());
42
43 let on_select = with!(selected => move |path: TreePath| {
44 selected.set(path);
45 });
46
47 let on_activate = with!(expanded_paths => move |path: TreePath| {
48 // Toggle expand/collapse for the activated item
49 let mut paths = expanded_paths.get().clone();
50 if let Some(pos) = paths.iter().position(|p| *p == path) {
51 // Currently expanded, collapse it
52 paths.remove(pos);
53 } else {
54 // Currently collapsed, expand it
55 paths.push(path.clone());
56 }
57 expanded_paths.set(paths);
58 });
59
60 let selected_label = get_item_at_path(&items, &selected.get())
61 .map(|item| item.label.clone())
62 .unwrap_or_else(|| "Nothing".to_string());
63
64 View::vstack()
65 .child(
66 View::styled_text("File Browser")
67 .color(Color::Cyan)
68 .bold()
69 .build(),
70 )
71 .child(
72 View::styled_text(format!("Selected: {}", selected_label))
73 .dim()
74 .build(),
75 )
76 .child(
77 View::boxed()
78 .flex(1)
79 .border(true)
80 .child(
81 View::tree()
82 .items(items)
83 .selected(selected.get().clone())
84 .on_select(on_select)
85 .on_activate(on_activate)
86 .build(),
87 )
88 .build(),
89 )
90 .child(
91 View::styled_text(
92 "↑↓/jk: navigate | Enter: expand/collapse | F1 help | Ctrl+Q: quit",
93 )
94 .dim()
95 .build(),
96 )
97 .child(
98 View::modal()
99 .visible(show_help.get())
100 .title("Example 16: Tree View")
101 .on_dismiss(with!(show_help => move || show_help.set(false)))
102 .child(
103 View::vstack()
104 .child(View::styled_text("What you're seeing").bold().build())
105 .child(View::text("• Hierarchical tree widget"))
106 .child(View::text("• Expand/collapse folders"))
107 .child(View::text("• Path-based selection tracking"))
108 .child(View::gap(1))
109 .child(View::styled_text("Key concepts").bold().build())
110 .child(View::text("• View::tree() for hierarchical data"))
111 .child(View::text("• TreeItem::new().child() builds hierarchy"))
112 .child(View::text("• on_select returns TreePath (Vec<usize>)"))
113 .child(View::text("• on_activate for expand/collapse"))
114 .child(View::gap(1))
115 .child(View::styled_text("Try this").bold().build())
116 .child(View::text("• Navigate with arrow keys"))
117 .child(View::text("• Press Enter to expand/collapse folders"))
118 .child(View::text("• Watch the 'Selected:' text update"))
119 .child(View::gap(1))
120 .child(View::styled_text("Next up").bold().build())
121 .child(View::text("→ 17_table: data tables with sorting"))
122 .child(View::gap(1))
123 .child(View::styled_text("Press Escape to close").dim().build())
124 .build(),
125 )
126 .build(),
127 )
128 .build()
129 }21 fn render(&self, cx: Scope) -> View {
22 let count = state!(cx, || 0i32);
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // Clone handles for closures (this is the pattern being explained)
32 let count_for_increment = count.clone();
33 let count_for_decrement = count.clone();
34 let count_for_reset = count.clone();
35
36 let increment = move || {
37 let current = count_for_increment.get();
38 count_for_increment.set(current + 1);
39 };
40
41 let decrement = move || {
42 let current = count_for_decrement.get();
43 count_for_decrement.set(current - 1);
44 };
45
46 let reset = move || {
47 count_for_reset.set(0);
48 };
49
50 let current_value = count.get();
51
52 // Hook ordering demo
53 let _always_called_1 = state!(cx, || "hook 1");
54 let _always_called_2 = state!(cx, || "hook 2");
55
56 View::vstack()
57 .spacing(1)
58 .child(
59 View::styled_text("State Explained")
60 .color(Color::Cyan)
61 .bold()
62 .build(),
63 )
64 .child(
65 View::boxed()
66 .border(true)
67 .padding(1)
68 .child(
69 View::vstack()
70 .child(View::styled_text("The Mental Model:").bold().build())
71 .child(View::gap(1))
72 .child(View::text(" State<T> is a HANDLE, not data"))
73 .child(View::text(" clone() copies the handle, not the data"))
74 .child(View::text(" All handles point to ONE value"))
75 .child(View::gap(1))
76 .child(View::text(" count ──────┐"))
77 .child(View::text(" ├──► i32: 0 (shared!)"))
78 .child(View::text(" count2 ─────┘"))
79 .build(),
80 )
81 .build(),
82 )
83 .child(
84 View::hstack()
85 .spacing(1)
86 .child(View::text("Current value:"))
87 .child(
88 View::styled_text(format!("{}", current_value))
89 .color(Color::Yellow)
90 .bold()
91 .build(),
92 )
93 .build(),
94 )
95 .child(
96 View::hstack()
97 .spacing(1)
98 .child(View::button().label(" - ").on_press(decrement).build())
99 .child(View::button().label(" + ").on_press(increment).build())
100 .child(View::button().label("Reset").on_press(reset).build())
101 .build(),
102 )
103 .child(
104 View::styled_text("All three buttons modify the SAME underlying i32")
105 .dim()
106 .build(),
107 )
108 .child(View::gap(1))
109 .child(
110 View::styled_text("Tab navigate • F1 help • Ctrl+Q quit")
111 .dim()
112 .build(),
113 )
114 .child(
115 View::modal()
116 .visible(show_help.get())
117 .title("Example 10: State Explained")
118 .on_dismiss(with!(show_help => move || show_help.set(false)))
119 .child(
120 View::vstack()
121 .child(View::styled_text("What you're seeing").bold().build())
122 .child(View::text("• State<T> as a handle/pointer concept"))
123 .child(View::text("• Multiple closures sharing one value"))
124 .child(View::text("• Visual diagram of the mental model"))
125 .child(View::gap(1))
126 .child(View::styled_text("Key concepts").bold().build())
127 .child(View::text("• clone() copies handle, not data"))
128 .child(View::text("• All handles point to same underlying value"))
129 .child(View::text(
130 "• Hooks must be called in same order every render",
131 ))
132 .child(View::gap(1))
133 .child(View::styled_text("Important rule").bold().build())
134 .child(View::text("• NEVER put use_state inside an if block"))
135 .child(View::text("• Use with!() macro to simplify cloning"))
136 .child(View::gap(1))
137 .child(View::styled_text("Next up").bold().build())
138 .child(View::text("→ 11_checkbox: toggle controls"))
139 .child(View::gap(1))
140 .child(View::styled_text("Press Escape to close").dim().build())
141 .build(),
142 )
143 .build(),
144 )
145 .build()
146 }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 }21 fn render(&self, cx: Scope) -> View {
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 // Stream log entries
31 let logs = text_stream!(cx, || {
32 let log_messages = vec![
33 "[INFO] Application started",
34 "[INFO] Loading configuration...",
35 "[OK] Config loaded successfully",
36 "[INFO] Connecting to database...",
37 "[OK] Database connected",
38 "[INFO] Starting web server on :8080",
39 "[OK] Server listening",
40 "[INFO] Processing request GET /api/users",
41 "[OK] Response 200 in 45ms",
42 "[INFO] Processing request POST /api/login",
43 "[OK] Response 200 in 120ms",
44 "[WARN] High memory usage detected: 85%",
45 "[INFO] Running garbage collection...",
46 "[OK] Memory freed: 200MB",
47 "[INFO] Processing request GET /api/data",
48 "[ERROR] Database timeout after 5000ms",
49 "[INFO] Retrying database connection...",
50 "[OK] Database reconnected",
51 "[OK] Response 200 in 5045ms",
52 "[INFO] Scheduled backup starting...",
53 "[OK] Backup completed: 1.2GB",
54 "[INFO] Processing request GET /api/health",
55 "[OK] Response 200 in 12ms",
56 "[INFO] Processing request PUT /api/users/123",
57 "[OK] Response 200 in 89ms",
58 "[INFO] Cache invalidation triggered",
59 "[OK] Cache cleared: 50MB",
60 "[WARN] Slow query detected: 1200ms",
61 "[INFO] Query optimization suggested",
62 "[INFO] Processing request DELETE /api/sessions",
63 "[OK] Response 204 in 34ms",
64 "[INFO] SSL certificate check",
65 "[OK] Certificate valid for 45 days",
66 "[INFO] Processing request POST /api/upload",
67 "[OK] File uploaded: 15MB",
68 "[WARN] Disk usage at 78%",
69 "[INFO] Processing request GET /api/reports",
70 "[OK] Response 200 in 230ms",
71 "[INFO] Metrics exported to monitoring",
72 "[OK] Heartbeat sent successfully",
73 "------- Log stream completed -------",
74 ];
75
76 log_messages.into_iter().map(|msg| {
77 std::thread::sleep(Duration::from_millis(500));
78 format!("{}\n", msg)
79 })
80 });
81
82 let log_text = logs.get();
83 let is_streaming = logs.is_loading();
84
85 // Color the status indicator
86 let status = if is_streaming {
87 View::styled_text(" [LIVE]")
88 .color(Color::Green)
89 .bold()
90 .build()
91 } else {
92 View::styled_text(" [END]").dim().build()
93 };
94
95 View::vstack()
96 .child(
97 View::hstack()
98 .child(
99 View::styled_text("Log Viewer")
100 .color(Color::Cyan)
101 .bold()
102 .build(),
103 )
104 .child(status)
105 .build(),
106 )
107 .child(View::gap(1))
108 .child(
109 View::boxed()
110 .scroll(true)
111 .auto_scroll_bottom(true)
112 .min_height(15)
113 .max_height(15)
114 .child(View::text(&log_text))
115 .build(),
116 )
117 .child(View::gap(1))
118 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
119 .child(
120 View::modal()
121 .visible(show_help.get())
122 .title("Example 06: Log Viewer")
123 .on_dismiss(with!(show_help => move || show_help.set(false)))
124 .child(
125 View::vstack()
126 .child(View::styled_text("What you're seeing").bold().build())
127 .child(View::text("• text_stream!() macro for accumulating text"))
128 .child(View::text("• Auto-scrolling box that follows new content"))
129 .child(View::text("• Live/End indicator based on stream state"))
130 .child(View::gap(1))
131 .child(View::styled_text("Key concepts").bold().build())
132 .child(View::text("• text_stream! concatenates yielded strings"))
133 .child(View::text(
134 "• auto_scroll_bottom(true) keeps newest visible",
135 ))
136 .child(View::text("• Simulates tailing a log file"))
137 .child(View::gap(1))
138 .child(View::styled_text("Try this").bold().build())
139 .child(View::text("• Watch the [LIVE] indicator change to [END]"))
140 .child(View::text("• Notice auto-scroll keeps up with new entries"))
141 .child(View::gap(1))
142 .child(View::styled_text("Next up").bold().build())
143 .child(View::text("→ 07_file_browser: real filesystem navigation"))
144 .child(View::gap(1))
145 .child(View::styled_text("Press Escape to close").dim().build())
146 .build(),
147 )
148 .build(),
149 )
150 .build()
151 }- examples/24_async_data.rs
- examples/25_context.rs
- examples/14_tabs.rs
- examples/11_checkbox.rs
- examples/17_table.rs
- examples/20_menu_bar.rs
- examples/26_radio_buttons.rs
- examples/21_toasts.rs
- examples/03_theme_switcher.rs
- examples/28_shared_state.rs
- examples/27_keyed_state.rs
- examples/22_forms.rs
- examples/23_modal.rs
Sourcepub fn spacer() -> Self
pub fn spacer() -> Self
Create a spacer with flex factor 1 (expands to fill available space).
Examples found in repository?
24 fn render(&self, cx: Scope) -> View {
25 let show_help = state!(cx, || false);
26
27 // F1 toggles help
28 cx.use_command(
29 KeyBinding::key(KeyCode::F(1)),
30 with!(show_help => move || show_help.update(|v| *v = !*v)),
31 );
32 View::vstack()
33 .child(View::styled_text("Status Bar Examples").bold().build())
34 .child(View::text(""))
35 .child(View::text("Basic status bar (left only):"))
36 .child(View::status_bar().left("NORMAL").build())
37 .child(View::text(""))
38 .child(View::text("Left and right sections:"))
39 .child(
40 View::status_bar()
41 .left("INSERT")
42 .right("Ln 42, Col 8")
43 .build(),
44 )
45 .child(View::text(""))
46 .child(View::text("All three sections:"))
47 .child(
48 View::status_bar()
49 .left("VISUAL")
50 .center("main.rs")
51 .right("UTF-8 | LF | Rust")
52 .build(),
53 )
54 .child(View::text(""))
55 .child(View::text("Custom colors (green on dark):"))
56 .child(
57 View::status_bar()
58 .left("SUCCESS")
59 .center("All tests passed")
60 .right("100%")
61 .fg(Color::Green)
62 .bg(Color::DarkGreen)
63 .build(),
64 )
65 .child(View::text(""))
66 .child(View::text("Editor-style status bar:"))
67 .child(
68 View::status_bar()
69 .left("-- INSERT --")
70 .center("~/projects/myapp/src/main.rs [+]")
71 .right("1/100 | 50%")
72 .build(),
73 )
74 .child(View::spacer())
75 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
76 .child(
77 View::modal()
78 .visible(show_help.get())
79 .title("Example 19: Status Bar")
80 .on_dismiss(with!(show_help => move || show_help.set(false)))
81 .child(
82 View::vstack()
83 .child(View::styled_text("What you're seeing").bold().build())
84 .child(View::text("• Status bars with left/center/right sections"))
85 .child(View::text("• Custom foreground and background colors"))
86 .child(View::text("• Editor-style status line example"))
87 .child(View::gap(1))
88 .child(View::styled_text("Key concepts").bold().build())
89 .child(View::text("• View::status_bar() creates status lines"))
90 .child(View::text("• .left(), .center(), .right() for sections"))
91 .child(View::text("• .fg() and .bg() for custom colors"))
92 .child(View::text("• Great for showing mode, file info, etc."))
93 .child(View::gap(1))
94 .child(View::styled_text("Try this").bold().build())
95 .child(View::text("• Compare different status bar styles"))
96 .child(View::text("• Notice how sections align"))
97 .child(View::gap(1))
98 .child(View::styled_text("Next up").bold().build())
99 .child(View::text("→ 20_menu_bar: dropdown menus"))
100 .child(View::gap(1))
101 .child(View::styled_text("Press Escape to close").dim().build())
102 .build(),
103 )
104 .build(),
105 )
106 .build()
107 }More examples
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23 let theme_idx = state!(cx, || 0usize);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // F2 cycles through themes
32 cx.use_command(
33 KeyBinding::key(KeyCode::F(2)),
34 with!(theme_idx => move || {
35 let next = (theme_idx.get() + 1) % 6;
36 theme_idx.set(next);
37 let theme = match next {
38 0 => Theme::nord(),
39 1 => Theme::dark(),
40 2 => Theme::light(),
41 3 => Theme::dracula(),
42 4 => Theme::gruvbox_dark(),
43 _ => Theme::catppuccin_mocha(),
44 };
45 set_theme(theme);
46 }),
47 );
48
49 let content = state!(cx, String::new);
50 let cursor_line = state!(cx, || 0usize);
51 let cursor_col = state!(cx, || 0usize);
52
53 // Track changes and cursor position
54 let on_change = with!(content => move |text: String| {
55 content.set(text);
56 });
57
58 let on_cursor_change = with!(cursor_line, cursor_col => move |line: usize, col: usize| {
59 cursor_line.set(line);
60 cursor_col.set(col);
61 });
62
63 // Calculate stats
64 let text = content.get();
65 let line_count = if text.is_empty() {
66 0
67 } else {
68 text.lines().count()
69 };
70 let char_count = text.chars().count();
71 let word_count = text.split_whitespace().count();
72
73 let theme_name = match theme_idx.get() {
74 0 => "Nord",
75 1 => "Dark",
76 2 => "Light",
77 3 => "Dracula",
78 4 => "Gruvbox Dark",
79 _ => "Catppuccin Mocha",
80 };
81
82 View::vstack()
83 .spacing(1)
84 .child(
85 View::hstack()
86 .child(View::styled_text("Notes").color(Color::Cyan).bold().build())
87 .child(View::spacer())
88 .child(View::styled_text(format!("Theme: {}", theme_name)).dim().build())
89 .build(),
90 )
91 .child(
92 View::styled_text("A simple multi-line text editor")
93 .dim()
94 .build(),
95 )
96 .child(View::gap(1))
97 .child(
98 View::text_area()
99 .value(content.get())
100 .placeholder("Start typing your notes here...")
101 .rows(12)
102 .cursor_line(cursor_line.get())
103 .cursor_col(cursor_col.get())
104 .on_change(on_change)
105 .on_cursor_change(on_cursor_change)
106 .build(),
107 )
108 .child(View::gap(1))
109 .child(
110 View::hstack()
111 .spacing(3)
112 .child(
113 View::styled_text(format!("Lines: {}", line_count))
114 .color(Color::DarkGrey)
115 .build(),
116 )
117 .child(
118 View::styled_text(format!("Words: {}", word_count))
119 .color(Color::DarkGrey)
120 .build(),
121 )
122 .child(
123 View::styled_text(format!("Chars: {}", char_count))
124 .color(Color::DarkGrey)
125 .build(),
126 )
127 .build(),
128 )
129 .child(View::gap(1))
130 .child(View::styled_text("F1 help • F2 theme • Ctrl+Q quit").dim().build())
131 .child(
132 View::modal()
133 .visible(show_help.get())
134 .title("Example 12: TextArea")
135 .on_dismiss(with!(show_help => move || show_help.set(false)))
136 .child(
137 View::vstack()
138 .child(View::styled_text("What you're seeing").bold().build())
139 .child(View::text("• Multi-line text editing with TextArea"))
140 .child(View::text("• Real-time line/word/char counts"))
141 .child(View::text("• Cursor position tracking"))
142 .child(View::gap(1))
143 .child(View::styled_text("Key concepts").bold().build())
144 .child(View::text("• View::text_area() for multi-line input"))
145 .child(View::text("• on_change callback for text updates"))
146 .child(View::text("• on_cursor_change for cursor tracking"))
147 .child(View::text("• placeholder text when empty"))
148 .child(View::gap(1))
149 .child(View::styled_text("Try this").bold().build())
150 .child(View::text("• Type multiple lines of text"))
151 .child(View::text("• Watch the stats update in real-time"))
152 .child(View::text("• Use arrow keys to navigate"))
153 .child(View::gap(1))
154 .child(View::styled_text("Next up").bold().build())
155 .child(View::text("→ 13_split_panes: resizable panel layouts"))
156 .child(View::gap(1))
157 .child(View::styled_text("Press Escape to close").dim().build())
158 .build(),
159 )
160 .build(),
161 )
162 .build()
163 }71 fn render(&self, cx: Scope) -> View {
72 let show_help = state!(cx, || false);
73
74 // F1 toggles help
75 cx.use_command(
76 KeyBinding::key(KeyCode::F(1)),
77 with!(show_help => move || show_help.update(|v| *v = !*v)),
78 );
79
80 // State that we'll provide via context
81 let theme = state!(cx, || ColorTheme::Default);
82 let user = state!(cx, || User {
83 name: "Guest".to_string(),
84 logged_in: false,
85 });
86
87 // Provide static config via context
88 cx.provide_context(AppConfig {
89 app_name: "Context Demo".to_string(),
90 version: "1.0.0".to_string(),
91 });
92
93 // Provide dynamic state via context (current values)
94 cx.provide_context(theme.get());
95 cx.provide_context(user.get());
96
97 // Theme switching handlers
98 let set_default = with!(theme => move || theme.set(ColorTheme::Default));
99 let set_ocean = with!(theme => move || theme.set(ColorTheme::Ocean));
100 let set_forest = with!(theme => move || theme.set(ColorTheme::Forest));
101 let set_sunset = with!(theme => move || theme.set(ColorTheme::Sunset));
102
103 // Login/logout handlers
104 let toggle_login = with!(user => move || {
105 let current = user.get();
106 if current.logged_in {
107 user.set(User {
108 name: "Guest".to_string(),
109 logged_in: false,
110 });
111 } else {
112 user.set(User {
113 name: "Alice".to_string(),
114 logged_in: true,
115 });
116 }
117 });
118
119 // Get current theme for styling
120 let current_theme = theme.get();
121
122 View::vstack()
123 .spacing(1)
124 // Header - uses context
125 .child(render_header(&cx))
126 .child(
127 // Main content
128 View::boxed()
129 .flex(1)
130 .border(true)
131 .padding(1)
132 .child(
133 View::vstack()
134 .spacing(1)
135 .child(
136 View::styled_text("Theme Selection:")
137 .color(current_theme.primary())
138 .bold()
139 .build(),
140 )
141 .child(
142 View::hstack()
143 .spacing(2)
144 .child(
145 View::button()
146 .label("Default")
147 .on_press(set_default)
148 .build(),
149 )
150 .child(
151 View::button().label("Ocean").on_press(set_ocean).build(),
152 )
153 .child(
154 View::button().label("Forest").on_press(set_forest).build(),
155 )
156 .child(
157 View::button().label("Sunset").on_press(set_sunset).build(),
158 )
159 .build(),
160 )
161 .child(View::gap(1))
162 .child(
163 View::styled_text("User Actions:")
164 .color(current_theme.primary())
165 .bold()
166 .build(),
167 )
168 .child(
169 View::button()
170 .label(if user.get().logged_in {
171 "Logout"
172 } else {
173 "Login as Alice"
174 })
175 .on_press(toggle_login)
176 .build(),
177 )
178 .child(View::spacer())
179 // User info panel - uses context
180 .child(render_user_panel(&cx))
181 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
182 .build(),
183 )
184 .build(),
185 )
186 // Status bar - uses context
187 .child(render_status_bar(&cx))
188 .child(
189 View::modal()
190 .visible(show_help.get())
191 .title("Example 25: Context")
192 .on_dismiss(with!(show_help => move || show_help.set(false)))
193 .child(
194 View::vstack()
195 .child(View::styled_text("What you're seeing").bold().build())
196 .child(View::text("• Context API for global state"))
197 .child(View::text("• Theme colors propagate everywhere"))
198 .child(View::text("• User state shared across components"))
199 .child(View::gap(1))
200 .child(View::styled_text("Key concepts").bold().build())
201 .child(View::text("• cx.provide_context() adds to context"))
202 .child(View::text("• cx.use_context::<T>() reads context"))
203 .child(View::text("• Avoids prop drilling"))
204 .child(View::text("• Great for themes, user, config"))
205 .child(View::gap(1))
206 .child(View::styled_text("Try this").bold().build())
207 .child(View::text("• Switch themes - colors update"))
208 .child(View::text("• Login/logout - panel updates"))
209 .child(View::text("• Header and status bar read context"))
210 .child(View::gap(1))
211 .child(View::styled_text("Next up").bold().build())
212 .child(View::text("→ 26_radio_buttons: radio selections"))
213 .child(View::gap(1))
214 .child(View::styled_text("Press Escape to close").dim().build())
215 .build(),
216 )
217 .build(),
218 )
219 .build()
220 }
221}
222
223// Helper function that reads from context
224fn render_header(cx: &Scope) -> View {
225 // Read config and theme from context
226 let config = cx.use_context::<AppConfig>();
227 let theme = cx
228 .use_context::<ColorTheme>()
229 .unwrap_or(ColorTheme::Default);
230
231 let title = config
232 .map(|c| format!("{} v{}", c.app_name, c.version))
233 .unwrap_or_else(|| "No config".to_string());
234
235 View::boxed()
236 .border(true)
237 .padding(1)
238 .child(
239 View::vstack()
240 .child(
241 View::styled_text(title)
242 .bold()
243 .color(theme.primary())
244 .build(),
245 )
246 .child(
247 View::styled_text("Demonstrates provide_context and use_context")
248 .dim()
249 .build(),
250 )
251 .build(),
252 )
253 .build()
254}
255
256// Helper function that reads user from context
257fn render_user_panel(cx: &Scope) -> View {
258 let user = cx.use_context::<User>();
259 let theme = cx
260 .use_context::<ColorTheme>()
261 .unwrap_or(ColorTheme::Default);
262
263 let (status_text, status_color) = match &user {
264 Some(u) if u.logged_in => (format!("Logged in as: {}", u.name), theme.accent()),
265 Some(_) => ("Not logged in".to_string(), Color::DarkGrey),
266 None => ("User context not available".to_string(), Color::Red),
267 };
268
269 View::boxed()
270 .border(true)
271 .padding(1)
272 .child(
273 View::vstack()
274 .child(
275 View::styled_text("User Panel (reads from context)")
276 .bold()
277 .color(theme.primary())
278 .build(),
279 )
280 .child(View::styled_text(status_text).color(status_color).build())
281 .build(),
282 )
283 .build()
284}
285
286// Helper function for status bar
287fn render_status_bar(cx: &Scope) -> View {
288 let theme = cx
289 .use_context::<ColorTheme>()
290 .unwrap_or(ColorTheme::Default);
291
292 View::boxed()
293 .border(true)
294 .child(
295 View::hstack()
296 .child(
297 View::styled_text(format!(" Theme: {} ", theme.name()))
298 .color(theme.accent())
299 .build(),
300 )
301 .child(View::spacer())
302 .child(
303 View::styled_text(" Context values propagate automatically ")
304 .dim()
305 .build(),
306 )
307 .build(),
308 )
309 .build()
310}26 fn render(&self, cx: Scope) -> View {
27 let show_help = state!(cx, || false);
28
29 // F1 toggles help
30 cx.use_command(
31 KeyBinding::key(KeyCode::F(1)),
32 with!(show_help => move || show_help.update(|v| *v = !*v)),
33 );
34
35 // Menu state
36 let active_menu = state!(cx, || Option::<usize>::None);
37 let highlighted_menu = state!(cx, || 0usize);
38 let selected_item = state!(cx, || 0usize);
39
40 // App state
41 let message = state!(cx, || "Use Tab to focus menu bar, then Enter to open".to_string());
42 let counter = state!(cx, || 0i32);
43
44 // Command handler - executes menu commands
45 let handle_command = with!(message, counter, active_menu, selected_item => move |cmd_id: &'static str| {
46 let msg = match cmd_id {
47 "file.new" => "Created new file".to_string(),
48 "file.open" => "Opening file...".to_string(),
49 "file.save" => "File saved".to_string(),
50 "file.quit" => "Use Ctrl+Q to quit".to_string(),
51 "edit.undo" => "Undone".to_string(),
52 "edit.redo" => "Redone".to_string(),
53 "edit.cut" => "Cut to clipboard".to_string(),
54 "edit.copy" => "Copied to clipboard".to_string(),
55 "edit.paste" => "Pasted from clipboard".to_string(),
56 "counter.increment" => {
57 counter.update(|n| *n += 1);
58 format!("Counter: {}", counter.get())
59 }
60 "counter.decrement" => {
61 counter.update(|n| *n -= 1);
62 format!("Counter: {}", counter.get())
63 }
64 "counter.reset" => {
65 counter.set(0);
66 "Counter reset".to_string()
67 }
68 _ => format!("Unknown: {}", cmd_id),
69 };
70 message.set(msg);
71 // Close menu after executing command
72 active_menu.set(None);
73 selected_item.set(0);
74 });
75
76 // Menu change handler - opens/closes menus
77 let on_menu_change = with!(active_menu, highlighted_menu, selected_item => move |idx: usize| {
78 if active_menu.get() == Some(idx) {
79 // Clicking same menu toggles it closed
80 active_menu.set(None);
81 } else {
82 active_menu.set(Some(idx));
83 highlighted_menu.set(idx); // Keep highlight in sync
84 selected_item.set(0);
85 }
86 });
87
88 // Highlight change handler - arrow key navigation when no menu is open
89 let on_highlight_change = with!(highlighted_menu => move |idx: usize| {
90 highlighted_menu.set(idx);
91 });
92
93 // Item change handler - navigates within menu
94 let on_item_change = with!(selected_item => move |idx: usize| {
95 selected_item.set(idx);
96 });
97
98 // Build menus
99 let file_menu = Menu::new("File")
100 .command_with_shortcut("file.new", "New", "Ctrl+N")
101 .command_with_shortcut("file.open", "Open", "Ctrl+O")
102 .command_with_shortcut("file.save", "Save", "Ctrl+S")
103 .separator()
104 .command_with_shortcut("file.quit", "Quit", "Ctrl+Q");
105
106 let edit_menu = Menu::new("Edit")
107 .command_with_shortcut("edit.undo", "Undo", "Ctrl+Z")
108 .command_with_shortcut("edit.redo", "Redo", "Ctrl+Y")
109 .separator()
110 .command_with_shortcut("edit.cut", "Cut", "Ctrl+X")
111 .command_with_shortcut("edit.copy", "Copy", "Ctrl+C")
112 .command_with_shortcut("edit.paste", "Paste", "Ctrl+V");
113
114 let counter_menu = Menu::new("Counter")
115 .command("counter.increment", "Increment")
116 .command("counter.decrement", "Decrement")
117 .separator()
118 .command("counter.reset", "Reset to Zero");
119
120 View::vstack()
121 .child(
122 View::menu_bar()
123 .menu(file_menu)
124 .menu(edit_menu)
125 .menu(counter_menu)
126 .active_menu(active_menu.get())
127 .highlighted_menu(highlighted_menu.get())
128 .selected_item(selected_item.get())
129 .on_select(handle_command)
130 .on_menu_change(on_menu_change)
131 .on_highlight_change(on_highlight_change)
132 .on_item_change(on_item_change)
133 .build(),
134 )
135 .child(
136 View::boxed()
137 .flex(1)
138 .border(true)
139 .padding(2)
140 .child(
141 View::vstack()
142 .spacing(1)
143 .child(View::styled_text("Menu Bar Demo").bold().build())
144 .child(
145 View::styled_text(format!("Counter: {}", counter.get()))
146 .color(Color::Cyan)
147 .bold()
148 .build(),
149 )
150 .child(
151 View::styled_text(format!("Status: {}", message.get()))
152 .dim()
153 .build(),
154 )
155 .child(View::spacer())
156 .child(View::styled_text("Keyboard Navigation:").bold().build())
157 .child(View::text(" Tab Focus menu bar"))
158 .child(View::text(" Enter Open menu / Execute item"))
159 .child(View::text(" Up/Down Navigate menu items"))
160 .child(View::text(" Left/Right Switch between menus"))
161 .child(View::text(" Escape Close menu"))
162 .child(View::text(" Ctrl+Q Quit"))
163 .child(View::text(" F1 Help"))
164 .build(),
165 )
166 .build(),
167 )
168 .child(
169 View::modal()
170 .visible(show_help.get())
171 .title("Example 20: Menu Bar")
172 .on_dismiss(with!(show_help => move || show_help.set(false)))
173 .child(
174 View::vstack()
175 .child(View::styled_text("What you're seeing").bold().build())
176 .child(View::text("• Dropdown menu bar with keyboard nav"))
177 .child(View::text("• Menu items with shortcuts"))
178 .child(View::text("• Separators in menus"))
179 .child(View::gap(1))
180 .child(View::styled_text("Key concepts").bold().build())
181 .child(View::text("• View::menu_bar() creates menu system"))
182 .child(View::text("• Menu::new().command() adds items"))
183 .child(View::text("• .command_with_shortcut() shows key hints"))
184 .child(View::text("• on_select receives command ID"))
185 .child(View::gap(1))
186 .child(View::styled_text("Try this").bold().build())
187 .child(View::text("• Tab to menu, Enter to open"))
188 .child(View::text("• Arrow keys navigate menus"))
189 .child(View::text("• Try the Counter menu"))
190 .child(View::gap(1))
191 .child(View::styled_text("Next up").bold().build())
192 .child(View::text("→ 21_toasts: toast notifications"))
193 .child(View::gap(1))
194 .child(View::styled_text("Press Escape to close").dim().build())
195 .build(),
196 )
197 .build(),
198 )
199 .build()
200 }22 fn render(&self, cx: Scope) -> View {
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // Create a toast queue with 3 second default duration
32 let toasts = state!(cx, || ToastQueue::with_duration(Duration::from_secs(3)));
33 let position = state!(cx, || ToastPosition::BottomRight);
34
35 // Buttons to trigger different toast types
36 let show_info = with!(toasts => move || {
37 toasts.get().info("This is an informational message");
38 });
39
40 let show_success = with!(toasts => move || {
41 toasts.get().success("Operation completed successfully!");
42 });
43
44 let show_warning = with!(toasts => move || {
45 toasts.get().warning("Warning: This action cannot be undone");
46 });
47
48 let show_error = with!(toasts => move || {
49 toasts.get().error("Error: Connection failed");
50 });
51
52 let show_long_error = with!(toasts => move || {
53 toasts.get().error_long("Critical Error: Server not responding. Check your network.");
54 });
55
56 let clear_all = with!(toasts => move || {
57 toasts.get().clear();
58 });
59
60 // Position cycling
61 let cycle_position = with!(position => move || {
62 let next = match position.get() {
63 ToastPosition::TopRight => ToastPosition::TopLeft,
64 ToastPosition::TopLeft => ToastPosition::BottomLeft,
65 ToastPosition::BottomLeft => ToastPosition::BottomRight,
66 ToastPosition::BottomRight => ToastPosition::TopRight,
67 };
68 position.set(next);
69 });
70
71 let position_name = match position.get() {
72 ToastPosition::TopRight => "Top Right",
73 ToastPosition::TopLeft => "Top Left",
74 ToastPosition::BottomLeft => "Bottom Left",
75 ToastPosition::BottomRight => "Bottom Right",
76 };
77
78 View::vstack()
79 .spacing(1)
80 .child(
81 // Header
82 View::boxed()
83 .border(true)
84 .padding(1)
85 .child(
86 View::vstack()
87 .child(View::styled_text("Toast Notifications Demo").bold().build())
88 .child(
89 View::styled_text("Click buttons to show different toast types")
90 .dim()
91 .build(),
92 )
93 .build(),
94 )
95 .build(),
96 )
97 .child(
98 // Main content
99 View::boxed()
100 .flex(1)
101 .border(true)
102 .padding(1)
103 .child(
104 View::vstack()
105 .spacing(1)
106 .child(View::text("Toast Types:"))
107 .child(
108 View::hstack()
109 .spacing(2)
110 .child(View::button().label("Info").on_press(show_info).build())
111 .child(
112 View::button()
113 .label("Success")
114 .on_press(show_success)
115 .build(),
116 )
117 .child(
118 View::button()
119 .label("Warning")
120 .on_press(show_warning)
121 .build(),
122 )
123 .child(
124 View::button().label("Error").on_press(show_error).build(),
125 )
126 .build(),
127 )
128 .child(View::gap(1))
129 .child(View::text("Other Actions:"))
130 .child(
131 View::hstack()
132 .spacing(2)
133 .child(
134 View::button()
135 .label("Long Error")
136 .on_press(show_long_error)
137 .build(),
138 )
139 .child(
140 View::button()
141 .label("Clear All")
142 .on_press(clear_all)
143 .build(),
144 )
145 .build(),
146 )
147 .child(View::gap(1))
148 .child(View::text(format!("Position: {}", position_name)))
149 .child(
150 View::button()
151 .label("Change Position")
152 .on_press(cycle_position)
153 .build(),
154 )
155 .child(View::spacer())
156 .child(
157 View::styled_text(format!("Active toasts: {}", toasts.get().len()))
158 .color(Color::Yellow)
159 .build(),
160 )
161 .child(
162 View::styled_text("Toasts auto-dismiss after 3 seconds")
163 .dim()
164 .build(),
165 )
166 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
167 .build(),
168 )
169 .build(),
170 )
171 .child(
172 // Toast container - renders the toast stack
173 View::toast_container()
174 .from_queue(&toasts.get())
175 .position(position.get())
176 .max_visible(5)
177 .width(40)
178 .build(),
179 )
180 .child(
181 View::modal()
182 .visible(show_help.get())
183 .title("Example 21: Toasts")
184 .on_dismiss(with!(show_help => move || show_help.set(false)))
185 .child(
186 View::vstack()
187 .child(View::styled_text("What you're seeing").bold().build())
188 .child(View::text("• Toast notifications in corner"))
189 .child(View::text("• Auto-dismiss after 3 seconds"))
190 .child(View::text(
191 "• Multiple toast types (info/success/warn/error)",
192 ))
193 .child(View::gap(1))
194 .child(View::styled_text("Key concepts").bold().build())
195 .child(View::text("• ToastQueue manages notifications"))
196 .child(View::text("• View::toast_container() renders them"))
197 .child(View::text("• .position() controls corner placement"))
198 .child(View::text("• .max_visible() limits shown toasts"))
199 .child(View::gap(1))
200 .child(View::styled_text("Try this").bold().build())
201 .child(View::text("• Click different toast type buttons"))
202 .child(View::text("• Change position to see toasts move"))
203 .child(View::text("• Spam buttons to stack toasts"))
204 .child(View::gap(1))
205 .child(View::styled_text("Next up").bold().build())
206 .child(View::text("→ 22_forms: form validation"))
207 .child(View::gap(1))
208 .child(View::styled_text("Press Escape to close").dim().build())
209 .build(),
210 )
211 .build(),
212 )
213 .build()
214 }21 fn render(&self, cx: Scope) -> View {
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 // Modal visibility states
31 let show_confirm = state!(cx, || false);
32 let show_alert = state!(cx, || false);
33 let show_custom = state!(cx, || false);
34
35 // App state that modals can modify
36 let deleted_count = state!(cx, || 0);
37 let custom_input = state!(cx, String::new);
38 let last_action = state!(cx, || "No action yet".to_string());
39
40 // Handlers
41 let open_confirm = with!(show_confirm => move || {
42 show_confirm.set(true);
43 });
44
45 let open_alert = with!(show_alert => move || {
46 show_alert.set(true);
47 });
48
49 let open_custom = with!(show_custom => move || {
50 show_custom.set(true);
51 });
52
53 let on_confirm_yes = with!(show_confirm, deleted_count, last_action => move || {
54 deleted_count.set(deleted_count.get() + 1);
55 last_action.set(format!("Deleted item #{}", deleted_count.get()));
56 show_confirm.set(false);
57 });
58
59 let on_confirm_no = with!(show_confirm, last_action => move || {
60 last_action.set("Cancelled delete".to_string());
61 show_confirm.set(false);
62 });
63
64 let on_alert_dismiss = with!(show_alert, last_action => move || {
65 last_action.set("Dismissed alert".to_string());
66 show_alert.set(false);
67 });
68
69 let on_custom_save = with!(show_custom, custom_input, last_action => move || {
70 let value = custom_input.get();
71 if !value.is_empty() {
72 last_action.set(format!("Saved: {}", value));
73 custom_input.set(String::new());
74 }
75 show_custom.set(false);
76 });
77
78 let on_custom_dismiss = with!(show_custom => move || {
79 show_custom.set(false);
80 });
81
82 View::vstack()
83 .spacing(1)
84 .child(
85 // Header
86 View::boxed()
87 .border(true)
88 .padding(1)
89 .child(
90 View::vstack()
91 .child(View::styled_text("Modal Dialogs Demo").bold().build())
92 .child(
93 View::styled_text("Click buttons to open different modal types")
94 .dim()
95 .build(),
96 )
97 .build(),
98 )
99 .build(),
100 )
101 .child(
102 // Main content
103 View::boxed()
104 .flex(1)
105 .border(true)
106 .padding(1)
107 .child(
108 View::vstack()
109 .spacing(1)
110 .child(View::text("Modal Types:"))
111 .child(
112 View::hstack()
113 .spacing(2)
114 .child(
115 View::button()
116 .label("Confirm Dialog")
117 .on_press(open_confirm)
118 .build(),
119 )
120 .child(
121 View::button()
122 .label("Alert Dialog")
123 .on_press(open_alert)
124 .build(),
125 )
126 .child(
127 View::button()
128 .label("Custom Modal")
129 .on_press(open_custom)
130 .build(),
131 )
132 .build(),
133 )
134 .child(View::spacer())
135 .child(
136 View::styled_text(format!(
137 "Deleted items: {}",
138 deleted_count.get()
139 ))
140 .color(Color::Yellow)
141 .build(),
142 )
143 .child(
144 View::styled_text(format!("Last action: {}", last_action.get()))
145 .dim()
146 .build(),
147 )
148 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
149 .build(),
150 )
151 .build(),
152 )
153 // Help modal
154 .child(
155 View::modal()
156 .visible(show_help.get())
157 .title("Example 23: Modal")
158 .on_dismiss(with!(show_help => move || show_help.set(false)))
159 .child(
160 View::vstack()
161 .child(View::styled_text("What you're seeing").bold().build())
162 .child(View::text("• Confirm, alert, and custom modals"))
163 .child(View::text("• Modal focus containment"))
164 .child(View::text("• Escape to dismiss"))
165 .child(View::gap(1))
166 .child(View::styled_text("Key concepts").bold().build())
167 .child(View::text("• View::modal() creates overlay"))
168 .child(View::text("• .visible() controls show/hide"))
169 .child(View::text("• .on_dismiss() handles Escape"))
170 .child(View::text("• Focus trapped in open modal"))
171 .child(View::gap(1))
172 .child(View::styled_text("Try this").bold().build())
173 .child(View::text("• Open confirm, click Yes/No"))
174 .child(View::text("• Custom modal has text input"))
175 .child(View::text("• Press Escape to close modals"))
176 .child(View::gap(1))
177 .child(View::styled_text("Next up").bold().build())
178 .child(View::text("→ 24_async_data: async loading"))
179 .child(View::gap(1))
180 .child(View::styled_text("Press Escape to close").dim().build())
181 .build(),
182 )
183 .build(),
184 )
185 // Confirm dialog modal
186 .child(
187 View::modal()
188 .visible(show_confirm.get())
189 .title("Confirm Delete")
190 .on_dismiss(on_confirm_no.clone())
191 .width(40)
192 .height(30)
193 .child(
194 View::vstack()
195 .spacing(1)
196 .child(View::text("Are you sure you want to delete this item?"))
197 .child(View::text("This action cannot be undone."))
198 .child(View::spacer())
199 .child(
200 View::hstack()
201 .spacing(2)
202 .child(
203 View::button()
204 .label("Yes, Delete")
205 .on_press(on_confirm_yes)
206 .build(),
207 )
208 .child(
209 View::button()
210 .label("Cancel")
211 .on_press(on_confirm_no)
212 .build(),
213 )
214 .build(),
215 )
216 .build(),
217 )
218 .build(),
219 )
220 // Alert dialog modal
221 .child(
222 View::modal()
223 .visible(show_alert.get())
224 .title("Alert")
225 .on_dismiss(on_alert_dismiss.clone())
226 .width(50)
227 .height(25)
228 .child(
229 View::vstack()
230 .spacing(1)
231 .child(
232 View::styled_text("Operation completed successfully!")
233 .color(Color::Green)
234 .build(),
235 )
236 .child(View::text("Your changes have been saved."))
237 .child(View::spacer())
238 .child(
239 View::button()
240 .label("OK")
241 .on_press(on_alert_dismiss)
242 .build(),
243 )
244 .build(),
245 )
246 .build(),
247 )
248 // Custom modal with input
249 .child(
250 View::modal()
251 .visible(show_custom.get())
252 .title("Enter Details")
253 .on_dismiss(on_custom_dismiss.clone())
254 .width(50)
255 .height(40)
256 .child(
257 View::vstack()
258 .spacing(1)
259 .child(View::text("Enter a value:"))
260 .child(
261 View::text_input()
262 .value(custom_input.get())
263 .placeholder("Type something...")
264 .on_change(with!(custom_input => move |v: String| {
265 custom_input.set(v);
266 }))
267 .build(),
268 )
269 .child(View::spacer())
270 .child(
271 View::hstack()
272 .spacing(2)
273 .child(
274 View::button()
275 .label("Save")
276 .on_press(on_custom_save)
277 .build(),
278 )
279 .child(
280 View::button()
281 .label("Cancel")
282 .on_press(on_custom_dismiss)
283 .build(),
284 )
285 .build(),
286 )
287 .build(),
288 )
289 .build(),
290 )
291 .build()
292 }Sourcepub fn spacer_flex(flex: u16) -> Self
pub fn spacer_flex(flex: u16) -> Self
Create a spacer with a specific flex factor.
Sourcepub fn gap(height: u16) -> Self
pub fn gap(height: u16) -> Self
Create a fixed-height gap (blank lines).
Unlike spacer() which expands to fill space, gap() is a fixed height.
§Example
View::vstack()
.child(View::text("Header"))
.child(View::gap(1)) // One blank line
.child(View::text("Content"))
.build()Examples found in repository?
19 fn render(&self, cx: Scope) -> View {
20 let show_help = state!(cx, || false);
21
22 // F1 toggles help
23 cx.use_command(
24 KeyBinding::key(KeyCode::F(1)),
25 with!(show_help => move || show_help.update(|v| *v = !*v)),
26 );
27
28 View::vstack()
29 .child(View::styled_text("Hello World").bold().build())
30 .child(View::gap(1))
31 .child(View::text("Welcome to Telex!"))
32 .child(View::gap(1))
33 .child(
34 View::styled_text("F1 for help • Ctrl+Q to quit")
35 .dim()
36 .build(),
37 )
38 .child(
39 View::modal()
40 .visible(show_help.get())
41 .title("Example 01: Hello World")
42 .on_dismiss(with!(show_help => move || show_help.set(false)))
43 .child(
44 View::vstack()
45 .child(View::styled_text("What you're seeing").bold().build())
46 .child(View::text(
47 "• Basic app structure with struct + Component trait",
48 ))
49 .child(View::text(
50 "• View::text() and View::styled_text() for display",
51 ))
52 .child(View::text("• View::vstack() for vertical layout"))
53 .child(View::gap(1))
54 .child(View::styled_text("Key concepts").bold().build())
55 .child(View::text("• Every Telex app implements Component"))
56 .child(View::text("• render() returns a View tree"))
57 .child(View::text("• No state yet - this is purely static"))
58 .child(View::gap(1))
59 .child(View::styled_text("Next up").bold().build())
60 .child(View::text("→ 02_counter: add state and interactivity"))
61 .child(View::gap(1))
62 .child(View::styled_text("Press Escape to close").dim().build())
63 .build(),
64 )
65 .build(),
66 )
67 .build()
68 }More examples
30 fn render(&self, cx: Scope) -> View {
31 let show_help = state!(cx, || false);
32
33 // F1 toggles help
34 cx.use_command(
35 KeyBinding::key(KeyCode::F(1)),
36 with!(show_help => move || show_help.update(|v| *v = !*v)),
37 );
38 View::vstack()
39 .spacing(1)
40 .child(
41 View::styled_text("Image Widget Demo (Kitty Graphics)")
42 .bold()
43 .build(),
44 )
45 .child(View::text("Requires Kitty, Ghostty, or WezTerm terminal"))
46 .child(View::text(""))
47 // Load image from file path
48 .child(View::text("Logo (from file path):"))
49 .child(View::image().file("assets/telex-tui.png").build())
50 .child(View::text(""))
51 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
52 .child(
53 View::modal()
54 .visible(show_help.get())
55 .title("Example 30: Image")
56 .on_dismiss(with!(show_help => move || show_help.set(false)))
57 .child(
58 View::vstack()
59 .child(View::styled_text("What you're seeing").bold().build())
60 .child(View::text("• Image display via Kitty protocol"))
61 .child(View::text("• PNG/JPEG/GIF support"))
62 .child(View::text("• Loaded from file path"))
63 .child(View::gap(1))
64 .child(View::styled_text("Key concepts").bold().build())
65 .child(View::text("• View::image() displays images"))
66 .child(View::text("• .file(\"path\") loads from disk"))
67 .child(View::text("• .bytes(data) for embedded images"))
68 .child(View::text("• Works in Kitty/Ghostty/WezTerm"))
69 .child(View::gap(1))
70 .child(View::styled_text("Try this").bold().build())
71 .child(View::text("• Run in compatible terminal"))
72 .child(View::text("• See the Telex logo rendered"))
73 .child(View::gap(1))
74 .child(View::styled_text("Next up").bold().build())
75 .child(View::text("→ 31_animated_canvas: animations"))
76 .child(View::gap(1))
77 .child(View::styled_text("Press Escape to close").dim().build())
78 .build(),
79 )
80 .build(),
81 )
82 .build()
83 }22 fn render(&self, cx: Scope) -> View {
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // Stream that yields elapsed seconds
32 let elapsed = stream!(cx, || {
33 (0u64..).inspect(|&s| {
34 if s > 0 {
35 std::thread::sleep(Duration::from_secs(1));
36 }
37 })
38 });
39
40 let seconds = elapsed.get();
41 let is_running = elapsed.is_loading();
42
43 // Format as MM:SS
44 let minutes = seconds / 60;
45 let secs = seconds % 60;
46 let time_display = format!("{:02}:{:02}", minutes, secs);
47
48 View::vstack()
49 .child(View::styled_text("Timer").color(Color::Cyan).bold().build())
50 .child(View::gap(1))
51 .child(
52 View::hstack()
53 .child(View::styled_text(&time_display).bold().build())
54 .child(if is_running {
55 View::styled_text(" ●").color(Color::Green).build()
56 } else {
57 View::styled_text(" ○").dim().build()
58 })
59 .build(),
60 )
61 .child(View::gap(1))
62 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
63 .child(
64 View::modal()
65 .visible(show_help.get())
66 .title("Example 04: Timer")
67 .on_dismiss(with!(show_help => move || show_help.set(false)))
68 .child(
69 View::vstack()
70 .child(View::styled_text("What you're seeing").bold().build())
71 .child(View::text("• stream!() macro for background data"))
72 .child(View::text("• Auto-updating UI without user input"))
73 .child(View::text("• Green dot = stream is running"))
74 .child(View::gap(1))
75 .child(View::styled_text("Key concepts").bold().build())
76 .child(View::text("• Streams run in background threads"))
77 .child(View::text("• Each yielded value triggers a re-render"))
78 .child(View::text("• is_loading() tells you if stream is active"))
79 .child(View::gap(1))
80 .child(View::styled_text("Try this").bold().build())
81 .child(View::text("• Just watch - the timer ticks automatically"))
82 .child(View::text("• No button presses needed for updates"))
83 .child(View::gap(1))
84 .child(View::styled_text("Next up").bold().build())
85 .child(View::text("→ 05_todo_list: text input and list management"))
86 .child(View::gap(1))
87 .child(View::styled_text("Press Escape to close").dim().build())
88 .build(),
89 )
90 .build(),
91 )
92 .build()
93 }19 fn render(&self, cx: Scope) -> View {
20 let count = state!(cx, || 0i32);
21 let show_help = state!(cx, || false);
22
23 let increment = with!(count => move || count.update(|n| *n += 1));
24 let decrement = with!(count => move || count.update(|n| *n -= 1));
25
26 // F1 toggles help
27 cx.use_command(
28 KeyBinding::key(KeyCode::F(1)),
29 with!(show_help => move || show_help.update(|v| *v = !*v)),
30 );
31
32 View::vstack()
33 .child(View::styled_text("Counter").bold().build())
34 .child(View::gap(1))
35 .child(View::text(format!("Count: {}", count.get())))
36 .child(View::gap(1))
37 .child(
38 View::hstack()
39 .child(
40 View::button()
41 .label("Decrement")
42 .on_press(decrement)
43 .build(),
44 )
45 .child(View::text(" "))
46 .child(
47 View::button()
48 .label("Increment")
49 .on_press(increment)
50 .build(),
51 )
52 .build(),
53 )
54 .child(View::gap(1))
55 .child(
56 View::styled_text("Tab to switch • Enter to press • F1 for help • Ctrl+Q to quit")
57 .dim()
58 .build(),
59 )
60 .child(
61 View::modal()
62 .visible(show_help.get())
63 .title("Example 02: Counter")
64 .on_dismiss(with!(show_help => move || show_help.set(false)))
65 .child(
66 View::vstack()
67 .child(View::styled_text("What you're seeing").bold().build())
68 .child(View::text("• state!() macro for reactive state"))
69 .child(View::text("• View::button() with on_press callbacks"))
70 .child(View::text(
71 "• The with!() macro for capturing state in closures",
72 ))
73 .child(View::gap(1))
74 .child(View::styled_text("Key concepts").bold().build())
75 .child(View::text("• State persists across renders"))
76 .child(View::text("• Updating state triggers a re-render"))
77 .child(View::text("• Tab navigates between focusable elements"))
78 .child(View::gap(1))
79 .child(View::styled_text("Try this").bold().build())
80 .child(View::text("• Press +/- rapidly - notice instant updates"))
81 .child(View::text(
82 "• The UI stays in sync with state automatically",
83 ))
84 .child(View::gap(1))
85 .child(View::styled_text("Next up").bold().build())
86 .child(View::text("→ 03_theme_switcher: styling and colors"))
87 .child(View::gap(1))
88 .child(View::styled_text("Press Escape to close").dim().build())
89 .build(),
90 )
91 .build(),
92 )
93 .build()
94 }24 fn render(&self, cx: Scope) -> View {
25 let show_help = state!(cx, || false);
26
27 // F1 toggles help
28 cx.use_command(
29 KeyBinding::key(KeyCode::F(1)),
30 with!(show_help => move || show_help.update(|v| *v = !*v)),
31 );
32 View::vstack()
33 .child(View::styled_text("Status Bar Examples").bold().build())
34 .child(View::text(""))
35 .child(View::text("Basic status bar (left only):"))
36 .child(View::status_bar().left("NORMAL").build())
37 .child(View::text(""))
38 .child(View::text("Left and right sections:"))
39 .child(
40 View::status_bar()
41 .left("INSERT")
42 .right("Ln 42, Col 8")
43 .build(),
44 )
45 .child(View::text(""))
46 .child(View::text("All three sections:"))
47 .child(
48 View::status_bar()
49 .left("VISUAL")
50 .center("main.rs")
51 .right("UTF-8 | LF | Rust")
52 .build(),
53 )
54 .child(View::text(""))
55 .child(View::text("Custom colors (green on dark):"))
56 .child(
57 View::status_bar()
58 .left("SUCCESS")
59 .center("All tests passed")
60 .right("100%")
61 .fg(Color::Green)
62 .bg(Color::DarkGreen)
63 .build(),
64 )
65 .child(View::text(""))
66 .child(View::text("Editor-style status bar:"))
67 .child(
68 View::status_bar()
69 .left("-- INSERT --")
70 .center("~/projects/myapp/src/main.rs [+]")
71 .right("1/100 | 50%")
72 .build(),
73 )
74 .child(View::spacer())
75 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
76 .child(
77 View::modal()
78 .visible(show_help.get())
79 .title("Example 19: Status Bar")
80 .on_dismiss(with!(show_help => move || show_help.set(false)))
81 .child(
82 View::vstack()
83 .child(View::styled_text("What you're seeing").bold().build())
84 .child(View::text("• Status bars with left/center/right sections"))
85 .child(View::text("• Custom foreground and background colors"))
86 .child(View::text("• Editor-style status line example"))
87 .child(View::gap(1))
88 .child(View::styled_text("Key concepts").bold().build())
89 .child(View::text("• View::status_bar() creates status lines"))
90 .child(View::text("• .left(), .center(), .right() for sections"))
91 .child(View::text("• .fg() and .bg() for custom colors"))
92 .child(View::text("• Great for showing mode, file info, etc."))
93 .child(View::gap(1))
94 .child(View::styled_text("Try this").bold().build())
95 .child(View::text("• Compare different status bar styles"))
96 .child(View::text("• Notice how sections align"))
97 .child(View::gap(1))
98 .child(View::styled_text("Next up").bold().build())
99 .child(View::text("→ 20_menu_bar: dropdown menus"))
100 .child(View::gap(1))
101 .child(View::styled_text("Press Escape to close").dim().build())
102 .build(),
103 )
104 .build(),
105 )
106 .build()
107 }24 fn render(&self, cx: Scope) -> View {
25 let use_jsx = state!(cx, || false);
26 let show_help = state!(cx, || false);
27
28 // F1 toggles help
29 cx.use_command(
30 KeyBinding::key(KeyCode::F(1)),
31 with!(show_help => move || show_help.update(|v| *v = !*v)),
32 );
33
34 let toggle = with!(use_jsx => move || use_jsx.set(!use_jsx.get()));
35
36 // Show which syntax is currently displayed
37 let syntax_name = if use_jsx.get() {
38 "view! macro (JSX-like)"
39 } else {
40 "Builder pattern"
41 };
42
43 View::vstack()
44 .child(
45 View::styled_text("Syntax Comparison")
46 .color(Color::Cyan)
47 .bold()
48 .build(),
49 )
50 .child(
51 View::styled_text("Same UI, two ways to write it")
52 .dim()
53 .build(),
54 )
55 .child(View::gap(1))
56 .child(
57 View::hstack()
58 .child(View::text("Current syntax: "))
59 .child(
60 View::styled_text(syntax_name)
61 .color(Color::Yellow)
62 .bold()
63 .build(),
64 )
65 .build(),
66 )
67 .child(View::gap(1))
68 .child(
69 View::boxed()
70 .border(true)
71 .padding(1)
72 .child(if use_jsx.get() {
73 counter_jsx(cx.clone())
74 } else {
75 counter_builder(cx.clone())
76 })
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::button()
82 .label("Toggle Syntax")
83 .on_press(toggle)
84 .build(),
85 )
86 .child(View::gap(1))
87 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
88 .child(
89 View::modal()
90 .visible(show_help.get())
91 .title("Example 09: Syntax Comparison")
92 .on_dismiss(with!(show_help => move || show_help.set(false)))
93 .child(
94 View::vstack()
95 .child(View::styled_text("What you're seeing").bold().build())
96 .child(View::text("• Two syntaxes that produce identical output"))
97 .child(View::text("• Builder: View::vstack().child(...).build()"))
98 .child(View::text("• Macro: view! { <VStack>...</VStack> }"))
99 .child(View::gap(1))
100 .child(View::styled_text("Key concepts").bold().build())
101 .child(View::text("• Builder is Rust-native, IDE-friendly"))
102 .child(View::text("• view! macro is JSX-like, less boilerplate"))
103 .child(View::text("• Choose based on your preference"))
104 .child(View::gap(1))
105 .child(View::styled_text("Try this").bold().build())
106 .child(View::text("• Toggle between syntaxes"))
107 .child(View::text("• Notice the output is identical"))
108 .child(View::text("• Check the source code to see both styles"))
109 .child(View::gap(1))
110 .child(View::styled_text("Next up").bold().build())
111 .child(View::text("→ 10_state_explained: deep dive into state"))
112 .child(View::gap(1))
113 .child(View::styled_text("Press Escape to close").dim().build())
114 .build(),
115 )
116 .build(),
117 )
118 .build()
119 }
120}
121
122// =============================================================================
123// Builder Pattern
124// =============================================================================
125// Rust-native, explicit, IDE-friendly. Each method call is clear.
126// Good for: Complex conditional logic, learning the API, debugging.
127
128fn counter_builder(cx: Scope) -> View {
129 let count = state!(cx, || 0i32);
130
131 // with! macro clones the handle for you - much cleaner!
132 let increment = with!(count => move || count.update(|n| *n += 1));
133 let decrement = with!(count => move || count.update(|n| *n -= 1));
134
135 View::vstack()
136 .child(
137 View::styled_text("// Built with builder pattern")
138 .color(Color::DarkGrey)
139 .build(),
140 )
141 .child(View::gap(1))
142 .child(
143 View::styled_text(format!("Count: {}", count.get()))
144 .bold()
145 .build(),
146 )
147 .child(View::gap(1))
148 .child(
149 View::hstack()
150 .child(View::button().label(" - ").on_press(decrement).build())
151 .child(View::text(" "))
152 .child(View::button().label(" + ").on_press(increment).build())
153 .build(),
154 )
155 .build()
156}- examples/35_slider.rs
- examples/15_markdown.rs
- examples/18_progress_bar.rs
- examples/16_tree.rs
- examples/05_todo_list.rs
- examples/37_error_boundary.rs
- examples/38_custom_widget.rs
- examples/10_state_explained.rs
- examples/32_effects.rs
- examples/13_split_panes.rs
- examples/07_file_browser.rs
- examples/12_text_area.rs
- examples/06_log_viewer.rs
- examples/24_async_data.rs
- examples/34_channels_and_intervals.rs
- examples/08_system_monitor.rs
- examples/39_port.rs
- examples/25_context.rs
- examples/14_tabs.rs
- examples/11_checkbox.rs
- examples/17_table.rs
- examples/29_canvas.rs
- examples/20_menu_bar.rs
- examples/26_radio_buttons.rs
- examples/21_toasts.rs
- examples/36_reducer.rs
- examples/03_theme_switcher.rs
- examples/28_shared_state.rs
- examples/27_keyed_state.rs
- examples/31_animated_canvas.rs
- examples/22_forms.rs
- examples/23_modal.rs
Sourcepub fn list() -> ListBuilder
pub fn list() -> ListBuilder
Create a list builder.
Examples found in repository?
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
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 }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 }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 text_input() -> TextInputBuilder
pub fn text_input() -> TextInputBuilder
Create a text input builder.
Examples found in repository?
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
23 fn render(&self, cx: Scope) -> View {
24 let show_help = state!(cx, || false);
25
26 // F1 toggles help
27 cx.use_command(
28 KeyBinding::key(KeyCode::F(1)),
29 with!(show_help => move || show_help.update(|v| *v = !*v)),
30 );
31
32 let count = state!(cx, || 0);
33 let name = state!(cx, String::new);
34 let last_effect = state!(cx, || String::from("(none yet)"));
35 let init_done = state!(cx, || false);
36
37 // Effect that runs only once - initialization
38 // Using effect_once! macro (order-independent)
39 effect_once!(cx, with!(init_done, last_effect => move || {
40 init_done.set(true);
41 last_effect.set("effect_once!: initialized!".to_string());
42 || {}
43 }));
44
45 // Effect that runs when count changes
46 // Using effect! macro (order-independent)
47 effect!(cx, count.get(), with!(last_effect => move |&val| {
48 last_effect.set(format!("effect!: count → {}", val));
49 || {}
50 }));
51
52 // Effect that runs when name changes
53 effect!(cx, name.get(), with!(last_effect => move |n: &String| {
54 if !n.is_empty() {
55 last_effect.set(format!("effect!: name → \"{}\"", n));
56 }
57 || {}
58 }));
59
60 View::vstack()
61 .spacing(1)
62 .child(View::styled_text("effect! Demo").bold().build())
63 .child(View::text(""))
64 .child(View::text(format!("Counter: {}", count.get())))
65 .child(
66 View::hstack()
67 .spacing(1)
68 .child(
69 View::button()
70 .label("[ - ]")
71 .on_press(with!(count => move || count.update(|n| *n -= 1)))
72 .build(),
73 )
74 .child(
75 View::button()
76 .label("[ + ]")
77 .on_press(with!(count => move || count.update(|n| *n += 1)))
78 .build(),
79 )
80 .build(),
81 )
82 .child(View::text(""))
83 .child({
84 let n = name.get();
85 View::text(format!(
86 "Name: {}",
87 if n.is_empty() { "(empty)" } else { &n }
88 ))
89 })
90 .child(
91 View::text_input()
92 .value(name.get())
93 .placeholder("Type your name...")
94 .on_change(with!(name => move |s| name.set(s)))
95 .build(),
96 )
97 .child(View::text(""))
98 .child(View::styled_text("─── Effect Status ───").dim().build())
99 .child(View::text(""))
100 .child(View::text(format!(
101 "Initialized: {}",
102 if init_done.get() { "✓ yes" } else { "no" }
103 )))
104 .child(View::text(format!("Last effect: {}", last_effect.get())))
105 .child(View::text(""))
106 .child(View::styled_text("─── How it works ───").dim().build())
107 .child(View::text(""))
108 .child(View::text("effect_once! → Ran once at startup"))
109 .child(View::text("effect! → Runs when deps change"))
110 .child(View::text(""))
111 .child(
112 View::styled_text("Press +/- or type to see effects trigger")
113 .dim()
114 .build(),
115 )
116 .child(View::text(""))
117 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
118 .child(
119 View::modal()
120 .visible(show_help.get())
121 .title("Example 32: Effects")
122 .on_dismiss(with!(show_help => move || show_help.set(false)))
123 .child(
124 View::vstack()
125 .child(View::styled_text("What you're seeing").bold().build())
126 .child(View::text("• effect_once! runs at startup"))
127 .child(View::text("• effect! runs when deps change"))
128 .child(View::text("• Last effect shows what triggered"))
129 .child(View::gap(1))
130 .child(View::styled_text("Key concepts").bold().build())
131 .child(View::text("• effect_once!(cx, || { cleanup })"))
132 .child(View::text("• effect!(cx, deps, |&d| { cleanup })"))
133 .child(View::text("• Return || {} for cleanup"))
134 .child(View::text("• Effects run AFTER render"))
135 .child(View::text("• Safe in conditionals!"))
136 .child(View::gap(1))
137 .child(View::styled_text("Try this").bold().build())
138 .child(View::text("• Click +/- to change counter"))
139 .child(View::text("• Type in the name field"))
140 .child(View::text("• Watch 'Last effect' update"))
141 .child(View::gap(1))
142 .child(View::styled_text("Press Escape to close").dim().build())
143 .build(),
144 )
145 .build(),
146 )
147 .build()
148 }38 fn render(&self, cx: Scope) -> View {
39 let show_help = state!(cx, || false);
40
41 cx.use_command(
42 KeyBinding::key(KeyCode::F(1)),
43 with!(show_help => move || show_help.update(|v| *v = !*v)),
44 );
45
46 let (wizard, dispatch) = reducer!(cx, WizardState::Welcome, |state: WizardState, action: WizardAction| {
47 match (state, action) {
48 (_, WizardAction::Reset) => WizardState::Welcome,
49 (WizardState::Welcome, WizardAction::Next) => WizardState::Name(String::new()),
50 (WizardState::Name(_), WizardAction::SetName(n)) => WizardState::Name(n),
51 (WizardState::Name(name), WizardAction::Next) => {
52 let name = if name.is_empty() { "Anonymous".to_string() } else { name };
53 WizardState::Color(name, String::new())
54 }
55 (WizardState::Name(_), WizardAction::Back) => WizardState::Welcome,
56 (WizardState::Color(name, _), WizardAction::SetColor(c)) => WizardState::Color(name, c),
57 (WizardState::Color(name, color), WizardAction::Next) => {
58 let color = if color.is_empty() { "Blue".to_string() } else { color };
59 WizardState::Done(name, color)
60 }
61 (WizardState::Color(_, _), WizardAction::Back) => WizardState::Name(String::new()),
62 (WizardState::Done(_, _), WizardAction::Back) => WizardState::Welcome,
63 (s, _) => s,
64 }
65 });
66
67 let step = match &wizard.get() {
68 WizardState::Welcome => 1,
69 WizardState::Name(_) => 2,
70 WizardState::Color(_, _) => 3,
71 WizardState::Done(_, _) => 4,
72 };
73
74 let progress = format!("Step {} of 4", step);
75 let dots: String = (1..=4)
76 .map(|i| if i <= step { "●" } else { "○" })
77 .collect::<Vec<_>>()
78 .join(" ");
79
80 let content = match &wizard.get() {
81 WizardState::Welcome => {
82 let d = dispatch.clone();
83 View::vstack()
84 .spacing(1)
85 .child(View::styled_text("Welcome to the Wizard!").color(Color::Cyan).bold().build())
86 .child(View::text("This example shows centralized state"))
87 .child(View::text("management with reducer!"))
88 .child(
89 View::button()
90 .label("[ Start -> ]")
91 .on_press(move || d(WizardAction::Next))
92 .build(),
93 )
94 .build()
95 }
96 WizardState::Name(name) => {
97 let d1 = dispatch.clone();
98 let d2 = dispatch.clone();
99 let d3 = dispatch.clone();
100 View::vstack()
101 .spacing(1)
102 .child(View::styled_text("What's your name?").color(Color::Cyan).bold().build())
103 .child(
104 View::text_input()
105 .value(name.clone())
106 .placeholder("Enter your name...")
107 .on_change(move |s: String| d1(WizardAction::SetName(s)))
108 .build(),
109 )
110 .child(
111 View::hstack()
112 .spacing(1)
113 .child(
114 View::button()
115 .label("[ <- Back ]")
116 .on_press(move || d2(WizardAction::Back))
117 .build(),
118 )
119 .child(
120 View::button()
121 .label("[ Next -> ]")
122 .on_press(move || d3(WizardAction::Next))
123 .build(),
124 )
125 .build(),
126 )
127 .build()
128 }
129 WizardState::Color(name, color) => {
130 let d1 = dispatch.clone();
131 let d2 = dispatch.clone();
132 let d3 = dispatch.clone();
133 View::vstack()
134 .spacing(1)
135 .child(View::styled_text(format!("Hi, {}! Pick a color:", name)).color(Color::Cyan).bold().build())
136 .child(
137 View::text_input()
138 .value(color.clone())
139 .placeholder("Enter a color (e.g. Blue)...")
140 .on_change(move |s: String| d1(WizardAction::SetColor(s)))
141 .build(),
142 )
143 .child(
144 View::hstack()
145 .spacing(1)
146 .child(
147 View::button()
148 .label("[ <- Back ]")
149 .on_press(move || d2(WizardAction::Back))
150 .build(),
151 )
152 .child(
153 View::button()
154 .label("[ Finish -> ]")
155 .on_press(move || d3(WizardAction::Next))
156 .build(),
157 )
158 .build(),
159 )
160 .build()
161 }
162 WizardState::Done(name, color) => {
163 let d = dispatch.clone();
164 View::vstack()
165 .spacing(1)
166 .child(View::styled_text("All done!").color(Color::Green).bold().build())
167 .child(View::text(format!("Name: {}", name)))
168 .child(View::text(format!("Color: {}", color)))
169 .child(
170 View::button()
171 .label("[ Start Over ]")
172 .on_press(move || d(WizardAction::Reset))
173 .build(),
174 )
175 .build()
176 }
177 };
178
179 View::vstack()
180 .spacing(1)
181 .child(View::styled_text("Reducer Wizard").bold().build())
182 .child(
183 View::hstack()
184 .spacing(1)
185 .child(View::styled_text(&progress).dim().build())
186 .child(View::styled_text(&dots).color(Color::Cyan).build())
187 .build(),
188 )
189 .child(View::styled_text("────────────────────────").dim().build())
190 .child(content)
191 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
192 .child(
193 View::modal()
194 .visible(show_help.get())
195 .title("Example 36: Reducer")
196 .on_dismiss(with!(show_help => move || show_help.set(false)))
197 .child(
198 View::vstack()
199 .child(View::styled_text("What you're seeing").bold().build())
200 .child(View::text("• Multi-step wizard state machine"))
201 .child(View::text("• All transitions in one reducer fn"))
202 .child(View::text("• No scattered booleans"))
203 .child(View::gap(1))
204 .child(View::styled_text("Key concepts").bold().build())
205 .child(View::text("• reducer!(cx, init, |state, action| ...)"))
206 .child(View::text("• Returns (State<S>, Rc<dyn Fn(A)>)"))
207 .child(View::text("• dispatch(action) to transition"))
208 .child(View::text("• Pattern match (state, action) pairs"))
209 .child(View::gap(1))
210 .child(View::styled_text("Try this").bold().build())
211 .child(View::text("• Walk through all 4 steps"))
212 .child(View::text("• Go back and change answers"))
213 .child(View::text("• Watch the progress dots"))
214 .child(View::gap(1))
215 .child(View::styled_text("Next up").bold().build())
216 .child(View::text("-> 37_error_boundary: crash protection"))
217 .child(View::gap(1))
218 .child(View::styled_text("Press Escape to close").dim().build())
219 .build(),
220 )
221 .build(),
222 )
223 .build()
224 }21 fn render(&self, cx: Scope) -> View {
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 // Modal visibility states
31 let show_confirm = state!(cx, || false);
32 let show_alert = state!(cx, || false);
33 let show_custom = state!(cx, || false);
34
35 // App state that modals can modify
36 let deleted_count = state!(cx, || 0);
37 let custom_input = state!(cx, String::new);
38 let last_action = state!(cx, || "No action yet".to_string());
39
40 // Handlers
41 let open_confirm = with!(show_confirm => move || {
42 show_confirm.set(true);
43 });
44
45 let open_alert = with!(show_alert => move || {
46 show_alert.set(true);
47 });
48
49 let open_custom = with!(show_custom => move || {
50 show_custom.set(true);
51 });
52
53 let on_confirm_yes = with!(show_confirm, deleted_count, last_action => move || {
54 deleted_count.set(deleted_count.get() + 1);
55 last_action.set(format!("Deleted item #{}", deleted_count.get()));
56 show_confirm.set(false);
57 });
58
59 let on_confirm_no = with!(show_confirm, last_action => move || {
60 last_action.set("Cancelled delete".to_string());
61 show_confirm.set(false);
62 });
63
64 let on_alert_dismiss = with!(show_alert, last_action => move || {
65 last_action.set("Dismissed alert".to_string());
66 show_alert.set(false);
67 });
68
69 let on_custom_save = with!(show_custom, custom_input, last_action => move || {
70 let value = custom_input.get();
71 if !value.is_empty() {
72 last_action.set(format!("Saved: {}", value));
73 custom_input.set(String::new());
74 }
75 show_custom.set(false);
76 });
77
78 let on_custom_dismiss = with!(show_custom => move || {
79 show_custom.set(false);
80 });
81
82 View::vstack()
83 .spacing(1)
84 .child(
85 // Header
86 View::boxed()
87 .border(true)
88 .padding(1)
89 .child(
90 View::vstack()
91 .child(View::styled_text("Modal Dialogs Demo").bold().build())
92 .child(
93 View::styled_text("Click buttons to open different modal types")
94 .dim()
95 .build(),
96 )
97 .build(),
98 )
99 .build(),
100 )
101 .child(
102 // Main content
103 View::boxed()
104 .flex(1)
105 .border(true)
106 .padding(1)
107 .child(
108 View::vstack()
109 .spacing(1)
110 .child(View::text("Modal Types:"))
111 .child(
112 View::hstack()
113 .spacing(2)
114 .child(
115 View::button()
116 .label("Confirm Dialog")
117 .on_press(open_confirm)
118 .build(),
119 )
120 .child(
121 View::button()
122 .label("Alert Dialog")
123 .on_press(open_alert)
124 .build(),
125 )
126 .child(
127 View::button()
128 .label("Custom Modal")
129 .on_press(open_custom)
130 .build(),
131 )
132 .build(),
133 )
134 .child(View::spacer())
135 .child(
136 View::styled_text(format!(
137 "Deleted items: {}",
138 deleted_count.get()
139 ))
140 .color(Color::Yellow)
141 .build(),
142 )
143 .child(
144 View::styled_text(format!("Last action: {}", last_action.get()))
145 .dim()
146 .build(),
147 )
148 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
149 .build(),
150 )
151 .build(),
152 )
153 // Help modal
154 .child(
155 View::modal()
156 .visible(show_help.get())
157 .title("Example 23: Modal")
158 .on_dismiss(with!(show_help => move || show_help.set(false)))
159 .child(
160 View::vstack()
161 .child(View::styled_text("What you're seeing").bold().build())
162 .child(View::text("• Confirm, alert, and custom modals"))
163 .child(View::text("• Modal focus containment"))
164 .child(View::text("• Escape to dismiss"))
165 .child(View::gap(1))
166 .child(View::styled_text("Key concepts").bold().build())
167 .child(View::text("• View::modal() creates overlay"))
168 .child(View::text("• .visible() controls show/hide"))
169 .child(View::text("• .on_dismiss() handles Escape"))
170 .child(View::text("• Focus trapped in open modal"))
171 .child(View::gap(1))
172 .child(View::styled_text("Try this").bold().build())
173 .child(View::text("• Open confirm, click Yes/No"))
174 .child(View::text("• Custom modal has text input"))
175 .child(View::text("• Press Escape to close modals"))
176 .child(View::gap(1))
177 .child(View::styled_text("Next up").bold().build())
178 .child(View::text("→ 24_async_data: async loading"))
179 .child(View::gap(1))
180 .child(View::styled_text("Press Escape to close").dim().build())
181 .build(),
182 )
183 .build(),
184 )
185 // Confirm dialog modal
186 .child(
187 View::modal()
188 .visible(show_confirm.get())
189 .title("Confirm Delete")
190 .on_dismiss(on_confirm_no.clone())
191 .width(40)
192 .height(30)
193 .child(
194 View::vstack()
195 .spacing(1)
196 .child(View::text("Are you sure you want to delete this item?"))
197 .child(View::text("This action cannot be undone."))
198 .child(View::spacer())
199 .child(
200 View::hstack()
201 .spacing(2)
202 .child(
203 View::button()
204 .label("Yes, Delete")
205 .on_press(on_confirm_yes)
206 .build(),
207 )
208 .child(
209 View::button()
210 .label("Cancel")
211 .on_press(on_confirm_no)
212 .build(),
213 )
214 .build(),
215 )
216 .build(),
217 )
218 .build(),
219 )
220 // Alert dialog modal
221 .child(
222 View::modal()
223 .visible(show_alert.get())
224 .title("Alert")
225 .on_dismiss(on_alert_dismiss.clone())
226 .width(50)
227 .height(25)
228 .child(
229 View::vstack()
230 .spacing(1)
231 .child(
232 View::styled_text("Operation completed successfully!")
233 .color(Color::Green)
234 .build(),
235 )
236 .child(View::text("Your changes have been saved."))
237 .child(View::spacer())
238 .child(
239 View::button()
240 .label("OK")
241 .on_press(on_alert_dismiss)
242 .build(),
243 )
244 .build(),
245 )
246 .build(),
247 )
248 // Custom modal with input
249 .child(
250 View::modal()
251 .visible(show_custom.get())
252 .title("Enter Details")
253 .on_dismiss(on_custom_dismiss.clone())
254 .width(50)
255 .height(40)
256 .child(
257 View::vstack()
258 .spacing(1)
259 .child(View::text("Enter a value:"))
260 .child(
261 View::text_input()
262 .value(custom_input.get())
263 .placeholder("Type something...")
264 .on_change(with!(custom_input => move |v: String| {
265 custom_input.set(v);
266 }))
267 .build(),
268 )
269 .child(View::spacer())
270 .child(
271 View::hstack()
272 .spacing(2)
273 .child(
274 View::button()
275 .label("Save")
276 .on_press(on_custom_save)
277 .build(),
278 )
279 .child(
280 View::button()
281 .label("Cancel")
282 .on_press(on_custom_dismiss)
283 .build(),
284 )
285 .build(),
286 )
287 .build(),
288 )
289 .build(),
290 )
291 .build()
292 }Sourcepub fn checkbox() -> CheckboxBuilder
pub fn checkbox() -> CheckboxBuilder
Create a checkbox builder.
Examples found in repository?
22 fn render(&self, cx: Scope) -> View {
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 let active_tab = state!(cx, || 0usize);
32
33 // Settings state
34 let notifications = state!(cx, || true);
35 let dark_mode = state!(cx, || true);
36 let auto_save = state!(cx, || true);
37
38 let on_change = with!(active_tab => move |idx: usize| {
39 active_tab.set(idx);
40 });
41
42 // Checkbox handlers
43 let on_notifications = with!(notifications => move |checked: bool| {
44 notifications.set(checked);
45 });
46
47 let on_dark_mode = with!(dark_mode => move |checked: bool| {
48 dark_mode.set(checked);
49 if checked {
50 set_theme(Theme::dark());
51 } else {
52 set_theme(Theme::light());
53 }
54 });
55
56 let on_auto_save = with!(auto_save => move |checked: bool| {
57 auto_save.set(checked);
58 });
59
60 View::vstack()
61 .child(
62 View::styled_text("Tabbed Interface Demo")
63 .color(Color::Cyan)
64 .bold()
65 .build(),
66 )
67 .child(
68 View::boxed()
69 .flex(1)
70 .child(
71 View::tabs()
72 .tab(
73 "Overview",
74 View::vstack()
75 .child(View::styled_text("Welcome!").bold().build())
76 .child(View::text(
77 "\nThis is the Overview tab.\n\n\
78 Use the keyboard to switch tabs:\n\
79 - Left/Right arrows\n\
80 - [ and ] keys\n\
81 - Number keys 1-3",
82 ))
83 .build(),
84 )
85 .tab(
86 "Settings",
87 View::vstack()
88 .child(View::styled_text("Settings").bold().build())
89 .child(View::text(""))
90 .child(
91 View::checkbox()
92 .label("Enable notifications")
93 .checked(notifications.get())
94 .on_toggle(on_notifications)
95 .build(),
96 )
97 .child(
98 View::checkbox()
99 .label("Dark mode")
100 .checked(dark_mode.get())
101 .on_toggle(on_dark_mode)
102 .build(),
103 )
104 .child(
105 View::checkbox()
106 .label("Auto-save")
107 .checked(auto_save.get())
108 .on_toggle(on_auto_save)
109 .build(),
110 )
111 .build(),
112 )
113 .tab(
114 "About",
115 View::vstack()
116 .child(View::styled_text("About").bold().build())
117 .child(View::text(""))
118 .child(View::text("Telex TUI Framework"))
119 .child(View::text("Version: 0.2.1"))
120 .child(View::text(""))
121 .child(
122 View::styled_text("A React-style TUI framework for Rust")
123 .dim()
124 .build(),
125 )
126 .build(),
127 )
128 .active(active_tab.get())
129 .on_change(on_change)
130 .build(),
131 )
132 .build(),
133 )
134 .child(
135 View::styled_text("←→ or []: switch tabs | F1 help | Ctrl+Q: quit")
136 .dim()
137 .build(),
138 )
139 .child(
140 View::modal()
141 .visible(show_help.get())
142 .title("Example 14: Tabs")
143 .on_dismiss(with!(show_help => move || show_help.set(false)))
144 .child(
145 View::vstack()
146 .child(View::styled_text("What you're seeing").bold().build())
147 .child(View::text("• Tabbed interface with three tabs"))
148 .child(View::text("• Settings tab with checkboxes"))
149 .child(View::text("• Keyboard navigation between tabs"))
150 .child(View::gap(1))
151 .child(View::styled_text("Key concepts").bold().build())
152 .child(View::text("• View::tabs() creates tabbed container"))
153 .child(View::text("• .tab(\"Title\", content) adds each tab"))
154 .child(View::text("• .active() and .on_change() for state"))
155 .child(View::text("• Arrow keys, [ ], or 1-3 switch tabs"))
156 .child(View::gap(1))
157 .child(View::styled_text("Try this").bold().build())
158 .child(View::text("• Switch tabs with arrow keys"))
159 .child(View::text("• Toggle checkboxes in Settings"))
160 .child(View::text("• Try [ and ] keys for tab switching"))
161 .child(View::gap(1))
162 .child(View::styled_text("Next up").bold().build())
163 .child(View::text("→ 15_markdown: markdown rendering"))
164 .child(View::gap(1))
165 .child(View::styled_text("Press Escape to close").dim().build())
166 .build(),
167 )
168 .build(),
169 )
170 .build()
171 }More examples
21 fn render(&self, cx: Scope) -> View {
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 // Settings state
31 let dark_mode = state!(cx, || true);
32 let notifications = state!(cx, || true);
33 let auto_save = state!(cx, || false);
34 let telemetry = state!(cx, || false);
35
36 View::vstack()
37 .spacing(1)
38 .child(
39 View::styled_text("Settings")
40 .color(Color::Cyan)
41 .bold()
42 .build(),
43 )
44 .child(
45 View::styled_text("Use Tab to navigate, Enter/Space to toggle")
46 .dim()
47 .build(),
48 )
49 .child(View::gap(1))
50 .child(
51 View::boxed()
52 .border(true)
53 .padding(1)
54 .child(
55 View::vstack()
56 .spacing(1)
57 .child(View::styled_text("Appearance").bold().build())
58 .child(
59 View::checkbox()
60 .checked(dark_mode.get())
61 .label("Dark mode")
62 .on_toggle(with!(dark_mode => move |checked| {
63 dark_mode.set(checked);
64 if checked {
65 set_theme(Theme::dark());
66 } else {
67 set_theme(Theme::light());
68 }
69 }))
70 .build(),
71 )
72 .build(),
73 )
74 .build(),
75 )
76 .child(
77 View::boxed()
78 .border(true)
79 .padding(1)
80 .child(
81 View::vstack()
82 .spacing(1)
83 .child(View::styled_text("Behavior").bold().build())
84 .child(
85 View::checkbox()
86 .checked(notifications.get())
87 .label("Enable notifications")
88 .on_toggle(with!(notifications => move |checked| {
89 notifications.set(checked);
90 }))
91 .build(),
92 )
93 .child(
94 View::checkbox()
95 .checked(auto_save.get())
96 .label("Auto-save documents")
97 .on_toggle(with!(auto_save => move |checked| {
98 auto_save.set(checked);
99 }))
100 .build(),
101 )
102 .build(),
103 )
104 .build(),
105 )
106 .child(
107 View::boxed()
108 .border(true)
109 .padding(1)
110 .child(
111 View::vstack()
112 .spacing(1)
113 .child(View::styled_text("Privacy").bold().build())
114 .child(
115 View::checkbox()
116 .checked(telemetry.get())
117 .label("Send anonymous usage data")
118 .on_toggle(with!(telemetry => move |checked| {
119 telemetry.set(checked);
120 }))
121 .build(),
122 )
123 .build(),
124 )
125 .build(),
126 )
127 .child(View::gap(1))
128 .child(
129 View::hstack()
130 .spacing(2)
131 .child(View::text("Current settings:"))
132 .child(
133 View::styled_text(format!(
134 "dark={} notify={} autosave={} telemetry={}",
135 dark_mode.get(),
136 notifications.get(),
137 auto_save.get(),
138 telemetry.get()
139 ))
140 .color(Color::Yellow)
141 .build(),
142 )
143 .build(),
144 )
145 .child(View::gap(1))
146 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
147 .child(
148 View::modal()
149 .visible(show_help.get())
150 .title("Example 11: Checkbox")
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("• Checkbox widget for boolean toggles"))
156 .child(View::text("• Grouped settings in boxed sections"))
157 .child(View::text("• Dark mode toggle that changes theme live"))
158 .child(View::gap(1))
159 .child(View::styled_text("Key concepts").bold().build())
160 .child(View::text("• View::checkbox() with checked state"))
161 .child(View::text("• on_toggle callback receives new value"))
162 .child(View::text("• set_theme() for live theme switching"))
163 .child(View::gap(1))
164 .child(View::styled_text("Try this").bold().build())
165 .child(View::text("• Toggle Dark mode to see theme change"))
166 .child(View::text("• Watch the status line update"))
167 .child(View::text("• Tab between checkboxes"))
168 .child(View::gap(1))
169 .child(View::styled_text("Next up").bold().build())
170 .child(View::text("→ 12_text_area: multi-line text editing"))
171 .child(View::gap(1))
172 .child(View::styled_text("Press Escape to close").dim().build())
173 .build(),
174 )
175 .build(),
176 )
177 .build()
178 }42 fn render(&self, cx: Scope) -> View {
43 let show_help = state!(cx, || false);
44
45 // F1 toggles help
46 cx.use_command(
47 KeyBinding::key(KeyCode::F(1)),
48 with!(show_help => move || show_help.update(|v| *v = !*v)),
49 );
50
51 // Two independent toggles
52 let show_a = state!(cx, || true);
53 let show_b = state!(cx, || true);
54
55 // COUNTER A - state created inside conditional
56 let counter_a = if show_a.get() {
57 let count = state!(cx, || 0);
58 let inc = with!(count => move || count.update(|n| *n += 1));
59
60 View::hstack()
61 .spacing(1)
62 .child(
63 View::styled_text(format!("{}", count.get()))
64 .color(Color::Yellow)
65 .bold()
66 .build(),
67 )
68 .child(View::button().label("+").on_press(inc).build())
69 .build()
70 } else {
71 View::styled_text("--").dim().build()
72 };
73
74 // COUNTER B - state created inside a DIFFERENT conditional
75 let counter_b = if show_b.get() {
76 let count = state!(cx, || 0);
77 let inc = with!(count => move || count.update(|n| *n += 1));
78
79 View::hstack()
80 .spacing(1)
81 .child(
82 View::styled_text(format!("{}", count.get()))
83 .color(Color::Magenta)
84 .bold()
85 .build(),
86 )
87 .child(View::button().label("+").on_press(inc).build())
88 .build()
89 } else {
90 View::styled_text("--").dim().build()
91 };
92
93 let toggle_a = with!(show_a => move |_: bool| show_a.update(|b| *b = !*b));
94 let toggle_b = with!(show_b => move |_: bool| show_b.update(|b| *b = !*b));
95
96 View::vstack()
97 .spacing(1)
98 .child(
99 View::styled_text("state! Demo")
100 .color(Color::Cyan)
101 .bold()
102 .build(),
103 )
104 .child(View::gap(1))
105 .child(
106 View::hstack()
107 .spacing(2)
108 // Counter A box
109 .child(
110 View::boxed()
111 .border(true)
112 .padding(1)
113 .max_width(25)
114 .child(
115 View::vstack()
116 .child(View::styled_text("Counter A").bold().build())
117 .child(View::gap(1))
118 .child(
119 View::hstack()
120 .spacing(1)
121 .child(View::text("Value:"))
122 .child(counter_a)
123 .build(),
124 )
125 .child(
126 View::hstack()
127 .spacing(1)
128 .child(View::text("Show:"))
129 .child(
130 View::checkbox()
131 .checked(show_a.get())
132 .on_toggle(toggle_a)
133 .build(),
134 )
135 .build(),
136 )
137 .build(),
138 )
139 .build(),
140 )
141 // Counter B box
142 .child(
143 View::boxed()
144 .border(true)
145 .padding(1)
146 .max_width(25)
147 .child(
148 View::vstack()
149 .child(View::styled_text("Counter B").bold().build())
150 .child(View::gap(1))
151 .child(
152 View::hstack()
153 .spacing(1)
154 .child(View::text("Value:"))
155 .child(counter_b)
156 .build(),
157 )
158 .child(
159 View::hstack()
160 .spacing(1)
161 .child(View::text("Show:"))
162 .child(
163 View::checkbox()
164 .checked(show_b.get())
165 .on_toggle(toggle_b)
166 .build(),
167 )
168 .build(),
169 )
170 .build(),
171 )
172 .build(),
173 )
174 .build(),
175 )
176 .child(View::gap(1))
177 .child(View::styled_text("Try this:").bold().build())
178 .child(View::text(
179 " 1. Increment both counters to different values",
180 ))
181 .child(View::text(" 2. Hide counter A (uncheck its box)"))
182 .child(View::text(" 3. Counter B continues to work just fine!"))
183 .child(View::text(" 4. Show A again - it remembers its value"))
184 .child(View::gap(1))
185 .child(
186 View::styled_text("They don't interfere with each other.")
187 .color(Color::Green)
188 .build(),
189 )
190 .child(View::gap(1))
191 .child(
192 View::boxed()
193 .border(true)
194 .padding(1)
195 .child(
196 View::vstack()
197 .child(View::styled_text("The code:").bold().build())
198 .child(View::gap(1))
199 .child(
200 View::styled_text("if show_a.get() {")
201 .color(Color::DarkGrey)
202 .build(),
203 )
204 .child(
205 View::styled_text(" let count = state!(cx, || 0);")
206 .color(Color::Yellow)
207 .build(),
208 )
209 .child(View::styled_text("}").color(Color::DarkGrey).build())
210 .child(
211 View::styled_text("if show_b.get() {")
212 .color(Color::DarkGrey)
213 .build(),
214 )
215 .child(
216 View::styled_text(" let count = state!(cx, || 0);")
217 .color(Color::Magenta)
218 .build(),
219 )
220 .child(View::styled_text("}").color(Color::DarkGrey).build())
221 .child(View::gap(1))
222 .child(View::text("With use_state, hiding A would CRASH B"))
223 .child(View::text("(hook indices would shift)."))
224 .build(),
225 )
226 .build(),
227 )
228 .child(View::gap(1))
229 .child(
230 View::styled_text("Tab: navigate | F1 help | Ctrl+Q: quit")
231 .dim()
232 .build(),
233 )
234 .child(
235 View::modal()
236 .visible(show_help.get())
237 .title("Example 27: Keyed State")
238 .on_dismiss(with!(show_help => move || show_help.set(false)))
239 .child(
240 View::vstack()
241 .child(View::styled_text("What you're seeing").bold().build())
242 .child(View::text("• state! macro for order-independent hooks"))
243 .child(View::text("• Conditional state that doesn't crash"))
244 .child(View::text("• Two counters with hide/show toggles"))
245 .child(View::gap(1))
246 .child(View::styled_text("Key concepts").bold().build())
247 .child(View::text("• state!(cx, || init) creates keyed state"))
248 .child(View::text("• Each call site gets unique key"))
249 .child(View::text("• Safe to use inside if blocks"))
250 .child(View::text("• Values persist when hidden/shown"))
251 .child(View::gap(1))
252 .child(View::styled_text("Try this").bold().build())
253 .child(View::text("• Increment both counters"))
254 .child(View::text("• Hide counter A"))
255 .child(View::text("• Counter B still works!"))
256 .child(View::text("• Show A again - value preserved"))
257 .child(View::gap(1))
258 .child(View::styled_text("Next up").bold().build())
259 .child(View::text("→ 28_shared_state: shared state via keys"))
260 .child(View::gap(1))
261 .child(View::styled_text("Press Escape to close").dim().build())
262 .build(),
263 )
264 .build(),
265 )
266 .build()
267 }Sourcepub fn radio_group() -> RadioGroupBuilder
pub fn radio_group() -> RadioGroupBuilder
Create a radio group builder.
Examples found in repository?
21 fn render(&self, cx: Scope) -> View {
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 // State for different radio groups
31 let theme = state!(cx, || 0usize); // 0=Light, 1=Dark, 2=System
32 let font_size = state!(cx, || 1usize); // 0=Small, 1=Medium, 2=Large
33 let notification = state!(cx, || 0usize); // 0=All, 1=Important, 2=None
34
35 let theme_options = vec!["Light", "Dark", "System"];
36 let font_options = vec!["Small (12px)", "Medium (14px)", "Large (16px)"];
37 let notification_options = vec!["All notifications", "Important only", "None"];
38
39 View::vstack()
40 .spacing(1)
41 .child(
42 // Header
43 View::boxed()
44 .border(true)
45 .padding(1)
46 .child(
47 View::vstack()
48 .child(View::styled_text("Radio Buttons Demo").bold().build())
49 .child(
50 View::styled_text(
51 "Use Tab to switch groups, Up/Down or j/k to select",
52 )
53 .dim()
54 .build(),
55 )
56 .build(),
57 )
58 .build(),
59 )
60 .child(
61 // Main content - settings panel
62 View::boxed()
63 .flex(1)
64 .border(true)
65 .padding(1)
66 .child(
67 View::hstack()
68 .spacing(4)
69 // Theme selection
70 .child(
71 View::vstack()
72 .spacing(1)
73 .child(
74 View::styled_text("Theme")
75 .bold()
76 .color(Color::Cyan)
77 .build(),
78 )
79 .child(
80 View::radio_group()
81 .options(theme_options)
82 .selected(theme.get())
83 .on_change(with!(theme => move |idx| {
84 theme.set(idx);
85 }))
86 .build(),
87 )
88 .build(),
89 )
90 // Font size selection
91 .child(
92 View::vstack()
93 .spacing(1)
94 .child(
95 View::styled_text("Font Size")
96 .bold()
97 .color(Color::Green)
98 .build(),
99 )
100 .child(
101 View::radio_group()
102 .options(font_options)
103 .selected(font_size.get())
104 .on_change(with!(font_size => move |idx| {
105 font_size.set(idx);
106 }))
107 .build(),
108 )
109 .build(),
110 )
111 // Notification selection
112 .child(
113 View::vstack()
114 .spacing(1)
115 .child(
116 View::styled_text("Notifications")
117 .bold()
118 .color(Color::Yellow)
119 .build(),
120 )
121 .child(
122 View::radio_group()
123 .options(notification_options)
124 .selected(notification.get())
125 .on_change(with!(notification => move |idx| {
126 notification.set(idx);
127 }))
128 .build(),
129 )
130 .build(),
131 )
132 .build(),
133 )
134 .build(),
135 )
136 .child(
137 // Current selections display
138 View::boxed()
139 .border(true)
140 .padding(1)
141 .child(
142 View::vstack()
143 .child(View::styled_text("Current Settings:").bold().build())
144 .child(View::text(format!(
145 "Theme: {} | Font: {} | Notifications: {}",
146 match theme.get() {
147 0 => "Light",
148 1 => "Dark",
149 _ => "System",
150 },
151 match font_size.get() {
152 0 => "Small",
153 1 => "Medium",
154 _ => "Large",
155 },
156 match notification.get() {
157 0 => "All",
158 1 => "Important",
159 _ => "None",
160 }
161 )))
162 .build(),
163 )
164 .build(),
165 )
166 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
167 .child(
168 View::modal()
169 .visible(show_help.get())
170 .title("Example 26: Radio Buttons")
171 .on_dismiss(with!(show_help => move || show_help.set(false)))
172 .child(
173 View::vstack()
174 .child(View::styled_text("What you're seeing").bold().build())
175 .child(View::text("• Radio groups for mutually exclusive options"))
176 .child(View::text("• Three independent groups"))
177 .child(View::text("• Current selection shown below"))
178 .child(View::gap(1))
179 .child(View::styled_text("Key concepts").bold().build())
180 .child(View::text("• View::radio_group() creates groups"))
181 .child(View::text("• .options() takes Vec<&str>"))
182 .child(View::text("• .selected() binds to state (usize)"))
183 .child(View::text("• on_change receives new index"))
184 .child(View::gap(1))
185 .child(View::styled_text("Try this").bold().build())
186 .child(View::text("• Tab between groups"))
187 .child(View::text("• Up/Down or j/k to select"))
188 .child(View::text("• Watch current settings update"))
189 .child(View::gap(1))
190 .child(View::styled_text("Next up").bold().build())
191 .child(View::text("→ 27_keyed_state: order-independent hooks"))
192 .child(View::gap(1))
193 .child(View::styled_text("Press Escape to close").dim().build())
194 .build(),
195 )
196 .build(),
197 )
198 .build()
199 }Sourcepub fn text_area() -> TextAreaBuilder
pub fn text_area() -> TextAreaBuilder
Create a text area builder.
Examples found in repository?
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23 let theme_idx = state!(cx, || 0usize);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // F2 cycles through themes
32 cx.use_command(
33 KeyBinding::key(KeyCode::F(2)),
34 with!(theme_idx => move || {
35 let next = (theme_idx.get() + 1) % 6;
36 theme_idx.set(next);
37 let theme = match next {
38 0 => Theme::nord(),
39 1 => Theme::dark(),
40 2 => Theme::light(),
41 3 => Theme::dracula(),
42 4 => Theme::gruvbox_dark(),
43 _ => Theme::catppuccin_mocha(),
44 };
45 set_theme(theme);
46 }),
47 );
48
49 let content = state!(cx, String::new);
50 let cursor_line = state!(cx, || 0usize);
51 let cursor_col = state!(cx, || 0usize);
52
53 // Track changes and cursor position
54 let on_change = with!(content => move |text: String| {
55 content.set(text);
56 });
57
58 let on_cursor_change = with!(cursor_line, cursor_col => move |line: usize, col: usize| {
59 cursor_line.set(line);
60 cursor_col.set(col);
61 });
62
63 // Calculate stats
64 let text = content.get();
65 let line_count = if text.is_empty() {
66 0
67 } else {
68 text.lines().count()
69 };
70 let char_count = text.chars().count();
71 let word_count = text.split_whitespace().count();
72
73 let theme_name = match theme_idx.get() {
74 0 => "Nord",
75 1 => "Dark",
76 2 => "Light",
77 3 => "Dracula",
78 4 => "Gruvbox Dark",
79 _ => "Catppuccin Mocha",
80 };
81
82 View::vstack()
83 .spacing(1)
84 .child(
85 View::hstack()
86 .child(View::styled_text("Notes").color(Color::Cyan).bold().build())
87 .child(View::spacer())
88 .child(View::styled_text(format!("Theme: {}", theme_name)).dim().build())
89 .build(),
90 )
91 .child(
92 View::styled_text("A simple multi-line text editor")
93 .dim()
94 .build(),
95 )
96 .child(View::gap(1))
97 .child(
98 View::text_area()
99 .value(content.get())
100 .placeholder("Start typing your notes here...")
101 .rows(12)
102 .cursor_line(cursor_line.get())
103 .cursor_col(cursor_col.get())
104 .on_change(on_change)
105 .on_cursor_change(on_cursor_change)
106 .build(),
107 )
108 .child(View::gap(1))
109 .child(
110 View::hstack()
111 .spacing(3)
112 .child(
113 View::styled_text(format!("Lines: {}", line_count))
114 .color(Color::DarkGrey)
115 .build(),
116 )
117 .child(
118 View::styled_text(format!("Words: {}", word_count))
119 .color(Color::DarkGrey)
120 .build(),
121 )
122 .child(
123 View::styled_text(format!("Chars: {}", char_count))
124 .color(Color::DarkGrey)
125 .build(),
126 )
127 .build(),
128 )
129 .child(View::gap(1))
130 .child(View::styled_text("F1 help • F2 theme • Ctrl+Q quit").dim().build())
131 .child(
132 View::modal()
133 .visible(show_help.get())
134 .title("Example 12: TextArea")
135 .on_dismiss(with!(show_help => move || show_help.set(false)))
136 .child(
137 View::vstack()
138 .child(View::styled_text("What you're seeing").bold().build())
139 .child(View::text("• Multi-line text editing with TextArea"))
140 .child(View::text("• Real-time line/word/char counts"))
141 .child(View::text("• Cursor position tracking"))
142 .child(View::gap(1))
143 .child(View::styled_text("Key concepts").bold().build())
144 .child(View::text("• View::text_area() for multi-line input"))
145 .child(View::text("• on_change callback for text updates"))
146 .child(View::text("• on_cursor_change for cursor tracking"))
147 .child(View::text("• placeholder text when empty"))
148 .child(View::gap(1))
149 .child(View::styled_text("Try this").bold().build())
150 .child(View::text("• Type multiple lines of text"))
151 .child(View::text("• Watch the stats update in real-time"))
152 .child(View::text("• Use arrow keys to navigate"))
153 .child(View::gap(1))
154 .child(View::styled_text("Next up").bold().build())
155 .child(View::text("→ 13_split_panes: resizable panel layouts"))
156 .child(View::gap(1))
157 .child(View::styled_text("Press Escape to close").dim().build())
158 .build(),
159 )
160 .build(),
161 )
162 .build()
163 }Sourcepub fn modal() -> ModalBuilder
pub fn modal() -> ModalBuilder
Create a modal dialog builder.
Examples found in repository?
19 fn render(&self, cx: Scope) -> View {
20 let show_help = state!(cx, || false);
21
22 // F1 toggles help
23 cx.use_command(
24 KeyBinding::key(KeyCode::F(1)),
25 with!(show_help => move || show_help.update(|v| *v = !*v)),
26 );
27
28 View::vstack()
29 .child(View::styled_text("Hello World").bold().build())
30 .child(View::gap(1))
31 .child(View::text("Welcome to Telex!"))
32 .child(View::gap(1))
33 .child(
34 View::styled_text("F1 for help • Ctrl+Q to quit")
35 .dim()
36 .build(),
37 )
38 .child(
39 View::modal()
40 .visible(show_help.get())
41 .title("Example 01: Hello World")
42 .on_dismiss(with!(show_help => move || show_help.set(false)))
43 .child(
44 View::vstack()
45 .child(View::styled_text("What you're seeing").bold().build())
46 .child(View::text(
47 "• Basic app structure with struct + Component trait",
48 ))
49 .child(View::text(
50 "• View::text() and View::styled_text() for display",
51 ))
52 .child(View::text("• View::vstack() for vertical layout"))
53 .child(View::gap(1))
54 .child(View::styled_text("Key concepts").bold().build())
55 .child(View::text("• Every Telex app implements Component"))
56 .child(View::text("• render() returns a View tree"))
57 .child(View::text("• No state yet - this is purely static"))
58 .child(View::gap(1))
59 .child(View::styled_text("Next up").bold().build())
60 .child(View::text("→ 02_counter: add state and interactivity"))
61 .child(View::gap(1))
62 .child(View::styled_text("Press Escape to close").dim().build())
63 .build(),
64 )
65 .build(),
66 )
67 .build()
68 }More examples
30 fn render(&self, cx: Scope) -> View {
31 let show_help = state!(cx, || false);
32
33 // F1 toggles help
34 cx.use_command(
35 KeyBinding::key(KeyCode::F(1)),
36 with!(show_help => move || show_help.update(|v| *v = !*v)),
37 );
38 View::vstack()
39 .spacing(1)
40 .child(
41 View::styled_text("Image Widget Demo (Kitty Graphics)")
42 .bold()
43 .build(),
44 )
45 .child(View::text("Requires Kitty, Ghostty, or WezTerm terminal"))
46 .child(View::text(""))
47 // Load image from file path
48 .child(View::text("Logo (from file path):"))
49 .child(View::image().file("assets/telex-tui.png").build())
50 .child(View::text(""))
51 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
52 .child(
53 View::modal()
54 .visible(show_help.get())
55 .title("Example 30: Image")
56 .on_dismiss(with!(show_help => move || show_help.set(false)))
57 .child(
58 View::vstack()
59 .child(View::styled_text("What you're seeing").bold().build())
60 .child(View::text("• Image display via Kitty protocol"))
61 .child(View::text("• PNG/JPEG/GIF support"))
62 .child(View::text("• Loaded from file path"))
63 .child(View::gap(1))
64 .child(View::styled_text("Key concepts").bold().build())
65 .child(View::text("• View::image() displays images"))
66 .child(View::text("• .file(\"path\") loads from disk"))
67 .child(View::text("• .bytes(data) for embedded images"))
68 .child(View::text("• Works in Kitty/Ghostty/WezTerm"))
69 .child(View::gap(1))
70 .child(View::styled_text("Try this").bold().build())
71 .child(View::text("• Run in compatible terminal"))
72 .child(View::text("• See the Telex logo rendered"))
73 .child(View::gap(1))
74 .child(View::styled_text("Next up").bold().build())
75 .child(View::text("→ 31_animated_canvas: animations"))
76 .child(View::gap(1))
77 .child(View::styled_text("Press Escape to close").dim().build())
78 .build(),
79 )
80 .build(),
81 )
82 .build()
83 }22 fn render(&self, cx: Scope) -> View {
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // Stream that yields elapsed seconds
32 let elapsed = stream!(cx, || {
33 (0u64..).inspect(|&s| {
34 if s > 0 {
35 std::thread::sleep(Duration::from_secs(1));
36 }
37 })
38 });
39
40 let seconds = elapsed.get();
41 let is_running = elapsed.is_loading();
42
43 // Format as MM:SS
44 let minutes = seconds / 60;
45 let secs = seconds % 60;
46 let time_display = format!("{:02}:{:02}", minutes, secs);
47
48 View::vstack()
49 .child(View::styled_text("Timer").color(Color::Cyan).bold().build())
50 .child(View::gap(1))
51 .child(
52 View::hstack()
53 .child(View::styled_text(&time_display).bold().build())
54 .child(if is_running {
55 View::styled_text(" ●").color(Color::Green).build()
56 } else {
57 View::styled_text(" ○").dim().build()
58 })
59 .build(),
60 )
61 .child(View::gap(1))
62 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
63 .child(
64 View::modal()
65 .visible(show_help.get())
66 .title("Example 04: Timer")
67 .on_dismiss(with!(show_help => move || show_help.set(false)))
68 .child(
69 View::vstack()
70 .child(View::styled_text("What you're seeing").bold().build())
71 .child(View::text("• stream!() macro for background data"))
72 .child(View::text("• Auto-updating UI without user input"))
73 .child(View::text("• Green dot = stream is running"))
74 .child(View::gap(1))
75 .child(View::styled_text("Key concepts").bold().build())
76 .child(View::text("• Streams run in background threads"))
77 .child(View::text("• Each yielded value triggers a re-render"))
78 .child(View::text("• is_loading() tells you if stream is active"))
79 .child(View::gap(1))
80 .child(View::styled_text("Try this").bold().build())
81 .child(View::text("• Just watch - the timer ticks automatically"))
82 .child(View::text("• No button presses needed for updates"))
83 .child(View::gap(1))
84 .child(View::styled_text("Next up").bold().build())
85 .child(View::text("→ 05_todo_list: text input and list management"))
86 .child(View::gap(1))
87 .child(View::styled_text("Press Escape to close").dim().build())
88 .build(),
89 )
90 .build(),
91 )
92 .build()
93 }19 fn render(&self, cx: Scope) -> View {
20 let count = state!(cx, || 0i32);
21 let show_help = state!(cx, || false);
22
23 let increment = with!(count => move || count.update(|n| *n += 1));
24 let decrement = with!(count => move || count.update(|n| *n -= 1));
25
26 // F1 toggles help
27 cx.use_command(
28 KeyBinding::key(KeyCode::F(1)),
29 with!(show_help => move || show_help.update(|v| *v = !*v)),
30 );
31
32 View::vstack()
33 .child(View::styled_text("Counter").bold().build())
34 .child(View::gap(1))
35 .child(View::text(format!("Count: {}", count.get())))
36 .child(View::gap(1))
37 .child(
38 View::hstack()
39 .child(
40 View::button()
41 .label("Decrement")
42 .on_press(decrement)
43 .build(),
44 )
45 .child(View::text(" "))
46 .child(
47 View::button()
48 .label("Increment")
49 .on_press(increment)
50 .build(),
51 )
52 .build(),
53 )
54 .child(View::gap(1))
55 .child(
56 View::styled_text("Tab to switch • Enter to press • F1 for help • Ctrl+Q to quit")
57 .dim()
58 .build(),
59 )
60 .child(
61 View::modal()
62 .visible(show_help.get())
63 .title("Example 02: Counter")
64 .on_dismiss(with!(show_help => move || show_help.set(false)))
65 .child(
66 View::vstack()
67 .child(View::styled_text("What you're seeing").bold().build())
68 .child(View::text("• state!() macro for reactive state"))
69 .child(View::text("• View::button() with on_press callbacks"))
70 .child(View::text(
71 "• The with!() macro for capturing state in closures",
72 ))
73 .child(View::gap(1))
74 .child(View::styled_text("Key concepts").bold().build())
75 .child(View::text("• State persists across renders"))
76 .child(View::text("• Updating state triggers a re-render"))
77 .child(View::text("• Tab navigates between focusable elements"))
78 .child(View::gap(1))
79 .child(View::styled_text("Try this").bold().build())
80 .child(View::text("• Press +/- rapidly - notice instant updates"))
81 .child(View::text(
82 "• The UI stays in sync with state automatically",
83 ))
84 .child(View::gap(1))
85 .child(View::styled_text("Next up").bold().build())
86 .child(View::text("→ 03_theme_switcher: styling and colors"))
87 .child(View::gap(1))
88 .child(View::styled_text("Press Escape to close").dim().build())
89 .build(),
90 )
91 .build(),
92 )
93 .build()
94 }24 fn render(&self, cx: Scope) -> View {
25 let show_help = state!(cx, || false);
26
27 // F1 toggles help
28 cx.use_command(
29 KeyBinding::key(KeyCode::F(1)),
30 with!(show_help => move || show_help.update(|v| *v = !*v)),
31 );
32 View::vstack()
33 .child(View::styled_text("Status Bar Examples").bold().build())
34 .child(View::text(""))
35 .child(View::text("Basic status bar (left only):"))
36 .child(View::status_bar().left("NORMAL").build())
37 .child(View::text(""))
38 .child(View::text("Left and right sections:"))
39 .child(
40 View::status_bar()
41 .left("INSERT")
42 .right("Ln 42, Col 8")
43 .build(),
44 )
45 .child(View::text(""))
46 .child(View::text("All three sections:"))
47 .child(
48 View::status_bar()
49 .left("VISUAL")
50 .center("main.rs")
51 .right("UTF-8 | LF | Rust")
52 .build(),
53 )
54 .child(View::text(""))
55 .child(View::text("Custom colors (green on dark):"))
56 .child(
57 View::status_bar()
58 .left("SUCCESS")
59 .center("All tests passed")
60 .right("100%")
61 .fg(Color::Green)
62 .bg(Color::DarkGreen)
63 .build(),
64 )
65 .child(View::text(""))
66 .child(View::text("Editor-style status bar:"))
67 .child(
68 View::status_bar()
69 .left("-- INSERT --")
70 .center("~/projects/myapp/src/main.rs [+]")
71 .right("1/100 | 50%")
72 .build(),
73 )
74 .child(View::spacer())
75 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
76 .child(
77 View::modal()
78 .visible(show_help.get())
79 .title("Example 19: Status Bar")
80 .on_dismiss(with!(show_help => move || show_help.set(false)))
81 .child(
82 View::vstack()
83 .child(View::styled_text("What you're seeing").bold().build())
84 .child(View::text("• Status bars with left/center/right sections"))
85 .child(View::text("• Custom foreground and background colors"))
86 .child(View::text("• Editor-style status line example"))
87 .child(View::gap(1))
88 .child(View::styled_text("Key concepts").bold().build())
89 .child(View::text("• View::status_bar() creates status lines"))
90 .child(View::text("• .left(), .center(), .right() for sections"))
91 .child(View::text("• .fg() and .bg() for custom colors"))
92 .child(View::text("• Great for showing mode, file info, etc."))
93 .child(View::gap(1))
94 .child(View::styled_text("Try this").bold().build())
95 .child(View::text("• Compare different status bar styles"))
96 .child(View::text("• Notice how sections align"))
97 .child(View::gap(1))
98 .child(View::styled_text("Next up").bold().build())
99 .child(View::text("→ 20_menu_bar: dropdown menus"))
100 .child(View::gap(1))
101 .child(View::styled_text("Press Escape to close").dim().build())
102 .build(),
103 )
104 .build(),
105 )
106 .build()
107 }24 fn render(&self, cx: Scope) -> View {
25 let use_jsx = state!(cx, || false);
26 let show_help = state!(cx, || false);
27
28 // F1 toggles help
29 cx.use_command(
30 KeyBinding::key(KeyCode::F(1)),
31 with!(show_help => move || show_help.update(|v| *v = !*v)),
32 );
33
34 let toggle = with!(use_jsx => move || use_jsx.set(!use_jsx.get()));
35
36 // Show which syntax is currently displayed
37 let syntax_name = if use_jsx.get() {
38 "view! macro (JSX-like)"
39 } else {
40 "Builder pattern"
41 };
42
43 View::vstack()
44 .child(
45 View::styled_text("Syntax Comparison")
46 .color(Color::Cyan)
47 .bold()
48 .build(),
49 )
50 .child(
51 View::styled_text("Same UI, two ways to write it")
52 .dim()
53 .build(),
54 )
55 .child(View::gap(1))
56 .child(
57 View::hstack()
58 .child(View::text("Current syntax: "))
59 .child(
60 View::styled_text(syntax_name)
61 .color(Color::Yellow)
62 .bold()
63 .build(),
64 )
65 .build(),
66 )
67 .child(View::gap(1))
68 .child(
69 View::boxed()
70 .border(true)
71 .padding(1)
72 .child(if use_jsx.get() {
73 counter_jsx(cx.clone())
74 } else {
75 counter_builder(cx.clone())
76 })
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::button()
82 .label("Toggle Syntax")
83 .on_press(toggle)
84 .build(),
85 )
86 .child(View::gap(1))
87 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
88 .child(
89 View::modal()
90 .visible(show_help.get())
91 .title("Example 09: Syntax Comparison")
92 .on_dismiss(with!(show_help => move || show_help.set(false)))
93 .child(
94 View::vstack()
95 .child(View::styled_text("What you're seeing").bold().build())
96 .child(View::text("• Two syntaxes that produce identical output"))
97 .child(View::text("• Builder: View::vstack().child(...).build()"))
98 .child(View::text("• Macro: view! { <VStack>...</VStack> }"))
99 .child(View::gap(1))
100 .child(View::styled_text("Key concepts").bold().build())
101 .child(View::text("• Builder is Rust-native, IDE-friendly"))
102 .child(View::text("• view! macro is JSX-like, less boilerplate"))
103 .child(View::text("• Choose based on your preference"))
104 .child(View::gap(1))
105 .child(View::styled_text("Try this").bold().build())
106 .child(View::text("• Toggle between syntaxes"))
107 .child(View::text("• Notice the output is identical"))
108 .child(View::text("• Check the source code to see both styles"))
109 .child(View::gap(1))
110 .child(View::styled_text("Next up").bold().build())
111 .child(View::text("→ 10_state_explained: deep dive into state"))
112 .child(View::gap(1))
113 .child(View::styled_text("Press Escape to close").dim().build())
114 .build(),
115 )
116 .build(),
117 )
118 .build()
119 }- examples/35_slider.rs
- examples/15_markdown.rs
- examples/18_progress_bar.rs
- examples/16_tree.rs
- examples/05_todo_list.rs
- examples/37_error_boundary.rs
- examples/38_custom_widget.rs
- examples/10_state_explained.rs
- examples/32_effects.rs
- examples/13_split_panes.rs
- examples/07_file_browser.rs
- examples/12_text_area.rs
- examples/06_log_viewer.rs
- examples/24_async_data.rs
- examples/34_channels_and_intervals.rs
- examples/08_system_monitor.rs
- examples/39_port.rs
- examples/25_context.rs
- examples/14_tabs.rs
- examples/11_checkbox.rs
- examples/17_table.rs
- examples/29_canvas.rs
- examples/20_menu_bar.rs
- examples/26_radio_buttons.rs
- examples/21_toasts.rs
- examples/36_reducer.rs
- examples/03_theme_switcher.rs
- examples/28_shared_state.rs
- examples/27_keyed_state.rs
- examples/31_animated_canvas.rs
- examples/22_forms.rs
- examples/23_modal.rs
Sourcepub fn split() -> SplitBuilder
pub fn split() -> SplitBuilder
Create a split pane builder.
Examples found in repository?
54 fn render(&self, cx: Scope) -> View {
55 let show_help = state!(cx, || false);
56
57 // F1 toggles help
58 cx.use_command(
59 KeyBinding::key(KeyCode::F(1)),
60 with!(show_help => move || show_help.update(|v| *v = !*v)),
61 );
62
63 let rendered = telex::markdown::render(DEMO_MARKDOWN);
64
65 View::vstack()
66 .child(
67 View::styled_text("Markdown Rendering Demo")
68 .color(Color::Cyan)
69 .bold()
70 .build(),
71 )
72 .child(
73 View::boxed()
74 .flex(1)
75 .child(
76 View::split()
77 .horizontal()
78 .ratio(0.4)
79 .first(
80 View::vstack()
81 .child(View::styled_text(" Source ").bold().build())
82 .child(
83 View::boxed()
84 .flex(1)
85 .border(true)
86 .scroll(true)
87 .child(View::text(DEMO_MARKDOWN))
88 .build(),
89 )
90 .build(),
91 )
92 .second(
93 View::vstack()
94 .child(View::styled_text(" Rendered ").bold().build())
95 .child(
96 View::boxed()
97 .flex(1)
98 .border(true)
99 .scroll(true)
100 .child(rendered)
101 .build(),
102 )
103 .build(),
104 )
105 .build(),
106 )
107 .build(),
108 )
109 .child(
110 View::styled_text("Tab: switch panes | ↑↓/jk: scroll | F1 help | Ctrl+Q: quit")
111 .dim()
112 .build(),
113 )
114 .child(
115 View::modal()
116 .visible(show_help.get())
117 .title("Example 15: Markdown")
118 .on_dismiss(with!(show_help => move || show_help.set(false)))
119 .child(
120 View::vstack()
121 .child(View::styled_text("What you're seeing").bold().build())
122 .child(View::text("• Side-by-side markdown source and rendered"))
123 .child(View::text("• Full markdown syntax support"))
124 .child(View::text("• Scrollable panes for long content"))
125 .child(View::gap(1))
126 .child(View::styled_text("Key concepts").bold().build())
127 .child(View::text("• telex::markdown::render() parses markdown"))
128 .child(View::text("• Returns a View tree with styled text"))
129 .child(View::text("• Code blocks, lists, quotes, headers"))
130 .child(View::text("• Split view for comparison"))
131 .child(View::gap(1))
132 .child(View::styled_text("Try this").bold().build())
133 .child(View::text("• Tab between source and rendered panes"))
134 .child(View::text("• Scroll with arrow keys or j/k"))
135 .child(View::text("• Compare source with rendered output"))
136 .child(View::gap(1))
137 .child(View::styled_text("Next up").bold().build())
138 .child(View::text("→ 16_progress: progress bars"))
139 .child(View::gap(1))
140 .child(View::styled_text("Press Escape to close").dim().build())
141 .build(),
142 )
143 .build(),
144 )
145 .build()
146 }More examples
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 }Sourcepub fn tabs() -> TabsBuilder
pub fn tabs() -> TabsBuilder
Create a tabs builder.
Examples found in repository?
22 fn render(&self, cx: Scope) -> View {
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 let active_tab = state!(cx, || 0usize);
32
33 // Settings state
34 let notifications = state!(cx, || true);
35 let dark_mode = state!(cx, || true);
36 let auto_save = state!(cx, || true);
37
38 let on_change = with!(active_tab => move |idx: usize| {
39 active_tab.set(idx);
40 });
41
42 // Checkbox handlers
43 let on_notifications = with!(notifications => move |checked: bool| {
44 notifications.set(checked);
45 });
46
47 let on_dark_mode = with!(dark_mode => move |checked: bool| {
48 dark_mode.set(checked);
49 if checked {
50 set_theme(Theme::dark());
51 } else {
52 set_theme(Theme::light());
53 }
54 });
55
56 let on_auto_save = with!(auto_save => move |checked: bool| {
57 auto_save.set(checked);
58 });
59
60 View::vstack()
61 .child(
62 View::styled_text("Tabbed Interface Demo")
63 .color(Color::Cyan)
64 .bold()
65 .build(),
66 )
67 .child(
68 View::boxed()
69 .flex(1)
70 .child(
71 View::tabs()
72 .tab(
73 "Overview",
74 View::vstack()
75 .child(View::styled_text("Welcome!").bold().build())
76 .child(View::text(
77 "\nThis is the Overview tab.\n\n\
78 Use the keyboard to switch tabs:\n\
79 - Left/Right arrows\n\
80 - [ and ] keys\n\
81 - Number keys 1-3",
82 ))
83 .build(),
84 )
85 .tab(
86 "Settings",
87 View::vstack()
88 .child(View::styled_text("Settings").bold().build())
89 .child(View::text(""))
90 .child(
91 View::checkbox()
92 .label("Enable notifications")
93 .checked(notifications.get())
94 .on_toggle(on_notifications)
95 .build(),
96 )
97 .child(
98 View::checkbox()
99 .label("Dark mode")
100 .checked(dark_mode.get())
101 .on_toggle(on_dark_mode)
102 .build(),
103 )
104 .child(
105 View::checkbox()
106 .label("Auto-save")
107 .checked(auto_save.get())
108 .on_toggle(on_auto_save)
109 .build(),
110 )
111 .build(),
112 )
113 .tab(
114 "About",
115 View::vstack()
116 .child(View::styled_text("About").bold().build())
117 .child(View::text(""))
118 .child(View::text("Telex TUI Framework"))
119 .child(View::text("Version: 0.2.1"))
120 .child(View::text(""))
121 .child(
122 View::styled_text("A React-style TUI framework for Rust")
123 .dim()
124 .build(),
125 )
126 .build(),
127 )
128 .active(active_tab.get())
129 .on_change(on_change)
130 .build(),
131 )
132 .build(),
133 )
134 .child(
135 View::styled_text("←→ or []: switch tabs | F1 help | Ctrl+Q: quit")
136 .dim()
137 .build(),
138 )
139 .child(
140 View::modal()
141 .visible(show_help.get())
142 .title("Example 14: Tabs")
143 .on_dismiss(with!(show_help => move || show_help.set(false)))
144 .child(
145 View::vstack()
146 .child(View::styled_text("What you're seeing").bold().build())
147 .child(View::text("• Tabbed interface with three tabs"))
148 .child(View::text("• Settings tab with checkboxes"))
149 .child(View::text("• Keyboard navigation between tabs"))
150 .child(View::gap(1))
151 .child(View::styled_text("Key concepts").bold().build())
152 .child(View::text("• View::tabs() creates tabbed container"))
153 .child(View::text("• .tab(\"Title\", content) adds each tab"))
154 .child(View::text("• .active() and .on_change() for state"))
155 .child(View::text("• Arrow keys, [ ], or 1-3 switch tabs"))
156 .child(View::gap(1))
157 .child(View::styled_text("Try this").bold().build())
158 .child(View::text("• Switch tabs with arrow keys"))
159 .child(View::text("• Toggle checkboxes in Settings"))
160 .child(View::text("• Try [ and ] keys for tab switching"))
161 .child(View::gap(1))
162 .child(View::styled_text("Next up").bold().build())
163 .child(View::text("→ 15_markdown: markdown rendering"))
164 .child(View::gap(1))
165 .child(View::styled_text("Press Escape to close").dim().build())
166 .build(),
167 )
168 .build(),
169 )
170 .build()
171 }Sourcepub fn tree() -> TreeBuilder
pub fn tree() -> TreeBuilder
Create a tree builder.
Examples found in repository?
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 // Track selected path
30 let selected = state!(cx, || vec![0usize]);
31
32 // Track expanded state for each node (by path prefix)
33 let expanded_paths = state!(cx, || {
34 vec![
35 vec![0], // src/ expanded
36 vec![0, 0], // src/components/ expanded
37 ]
38 });
39
40 // Build tree items with current expanded state
41 let items = build_tree(&expanded_paths.get());
42
43 let on_select = with!(selected => move |path: TreePath| {
44 selected.set(path);
45 });
46
47 let on_activate = with!(expanded_paths => move |path: TreePath| {
48 // Toggle expand/collapse for the activated item
49 let mut paths = expanded_paths.get().clone();
50 if let Some(pos) = paths.iter().position(|p| *p == path) {
51 // Currently expanded, collapse it
52 paths.remove(pos);
53 } else {
54 // Currently collapsed, expand it
55 paths.push(path.clone());
56 }
57 expanded_paths.set(paths);
58 });
59
60 let selected_label = get_item_at_path(&items, &selected.get())
61 .map(|item| item.label.clone())
62 .unwrap_or_else(|| "Nothing".to_string());
63
64 View::vstack()
65 .child(
66 View::styled_text("File Browser")
67 .color(Color::Cyan)
68 .bold()
69 .build(),
70 )
71 .child(
72 View::styled_text(format!("Selected: {}", selected_label))
73 .dim()
74 .build(),
75 )
76 .child(
77 View::boxed()
78 .flex(1)
79 .border(true)
80 .child(
81 View::tree()
82 .items(items)
83 .selected(selected.get().clone())
84 .on_select(on_select)
85 .on_activate(on_activate)
86 .build(),
87 )
88 .build(),
89 )
90 .child(
91 View::styled_text(
92 "↑↓/jk: navigate | Enter: expand/collapse | F1 help | Ctrl+Q: quit",
93 )
94 .dim()
95 .build(),
96 )
97 .child(
98 View::modal()
99 .visible(show_help.get())
100 .title("Example 16: Tree View")
101 .on_dismiss(with!(show_help => move || show_help.set(false)))
102 .child(
103 View::vstack()
104 .child(View::styled_text("What you're seeing").bold().build())
105 .child(View::text("• Hierarchical tree widget"))
106 .child(View::text("• Expand/collapse folders"))
107 .child(View::text("• Path-based selection tracking"))
108 .child(View::gap(1))
109 .child(View::styled_text("Key concepts").bold().build())
110 .child(View::text("• View::tree() for hierarchical data"))
111 .child(View::text("• TreeItem::new().child() builds hierarchy"))
112 .child(View::text("• on_select returns TreePath (Vec<usize>)"))
113 .child(View::text("• on_activate for expand/collapse"))
114 .child(View::gap(1))
115 .child(View::styled_text("Try this").bold().build())
116 .child(View::text("• Navigate with arrow keys"))
117 .child(View::text("• Press Enter to expand/collapse folders"))
118 .child(View::text("• Watch the 'Selected:' text update"))
119 .child(View::gap(1))
120 .child(View::styled_text("Next up").bold().build())
121 .child(View::text("→ 17_table: data tables with sorting"))
122 .child(View::gap(1))
123 .child(View::styled_text("Press Escape to close").dim().build())
124 .build(),
125 )
126 .build(),
127 )
128 .build()
129 }Sourcepub fn table() -> TableBuilder
pub fn table() -> TableBuilder
Create a table builder.
Examples found in repository?
21 fn render(&self, cx: Scope) -> View {
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 // Track selected row
31 let selected = state!(cx, || 0usize);
32
33 // Track sort state (column index, ascending)
34 let sort_state = state!(cx, || None::<(usize, bool)>);
35
36 // Sample pod data (like k9s)
37 let base_data = vec![
38 vec![
39 "nginx-pod".to_string(),
40 "Running".to_string(),
41 "12%".to_string(),
42 "256Mi".to_string(),
43 "2h".to_string(),
44 ],
45 vec![
46 "redis-cache".to_string(),
47 "Running".to_string(),
48 "8%".to_string(),
49 "128Mi".to_string(),
50 "5d".to_string(),
51 ],
52 vec![
53 "api-server".to_string(),
54 "Running".to_string(),
55 "45%".to_string(),
56 "512Mi".to_string(),
57 "1h".to_string(),
58 ],
59 vec![
60 "db-postgres".to_string(),
61 "Running".to_string(),
62 "23%".to_string(),
63 "1Gi".to_string(),
64 "3d".to_string(),
65 ],
66 vec![
67 "worker-1".to_string(),
68 "Pending".to_string(),
69 "0%".to_string(),
70 "0Mi".to_string(),
71 "5m".to_string(),
72 ],
73 vec![
74 "worker-2".to_string(),
75 "Running".to_string(),
76 "67%".to_string(),
77 "384Mi".to_string(),
78 "45m".to_string(),
79 ],
80 vec![
81 "frontend".to_string(),
82 "Running".to_string(),
83 "5%".to_string(),
84 "64Mi".to_string(),
85 "12h".to_string(),
86 ],
87 vec![
88 "metrics".to_string(),
89 "CrashLoop".to_string(),
90 "0%".to_string(),
91 "32Mi".to_string(),
92 "2m".to_string(),
93 ],
94 ];
95
96 // Sort data based on current sort state
97 let mut rows = base_data.clone();
98 if let Some((col, ascending)) = sort_state.get() {
99 rows.sort_by(|a, b| {
100 let a_val = a.get(col).map(|s| s.as_str()).unwrap_or("");
101 let b_val = b.get(col).map(|s| s.as_str()).unwrap_or("");
102 if ascending {
103 a_val.cmp(b_val)
104 } else {
105 b_val.cmp(a_val)
106 }
107 });
108 }
109
110 let on_select = with!(selected => move |idx: usize| {
111 selected.set(idx);
112 });
113
114 let on_sort = with!(sort_state => move |col: usize, asc: bool| {
115 sort_state.set(Some((col, asc)));
116 });
117
118 let on_activate = with!(selected => move |idx: usize| {
119 // In a real app, this might open a details view
120 selected.set(idx);
121 });
122
123 let selected_name = rows
124 .get(selected.get())
125 .and_then(|r| r.first())
126 .map(|s| s.as_str())
127 .unwrap_or("None");
128
129 let sort_info = match sort_state.get() {
130 Some((col, asc)) => {
131 let col_name = match col {
132 0 => "NAME",
133 1 => "STATUS",
134 2 => "CPU",
135 3 => "MEMORY",
136 4 => "AGE",
137 _ => "?",
138 };
139 format!(
140 "Sorted by {} ({})",
141 col_name,
142 if asc { "asc" } else { "desc" }
143 )
144 }
145 None => "Unsorted".to_string(),
146 };
147
148 View::vstack()
149 .child(
150 View::styled_text("Pod Dashboard")
151 .color(Color::Cyan)
152 .bold()
153 .build(),
154 )
155 .child(
156 View::styled_text(format!("Selected: {} | {}", selected_name, sort_info))
157 .dim()
158 .build(),
159 )
160 .child(
161 View::boxed()
162 .flex(1)
163 .border(true)
164 .child(
165 View::table()
166 .column("NAME")
167 .column_with(TableColumn::new("STATUS").width(ColumnWidth::Fixed(12)))
168 .column_with(
169 TableColumn::new("CPU")
170 .width(ColumnWidth::Fixed(8))
171 .align(TextAlign::Right),
172 )
173 .column_with(
174 TableColumn::new("MEMORY")
175 .width(ColumnWidth::Fixed(10))
176 .align(TextAlign::Right),
177 )
178 .column_with(
179 TableColumn::new("AGE")
180 .width(ColumnWidth::Fixed(8))
181 .align(TextAlign::Right),
182 )
183 .rows(rows)
184 .selected(selected.get())
185 .sort(sort_state.get())
186 .on_select(on_select)
187 .on_sort(on_sort)
188 .on_activate(on_activate)
189 .build(),
190 )
191 .build(),
192 )
193 .child(
194 View::styled_text("↑↓/jk: navigate | Enter: activate | F1 help | Ctrl+Q: quit")
195 .dim()
196 .build(),
197 )
198 .child(
199 View::modal()
200 .visible(show_help.get())
201 .title("Example 17: Table")
202 .on_dismiss(with!(show_help => move || show_help.set(false)))
203 .child(
204 View::vstack()
205 .child(View::styled_text("What you're seeing").bold().build())
206 .child(View::text("• Data table with sortable columns"))
207 .child(View::text("• Row selection and activation"))
208 .child(View::text("• Fixed and flexible column widths"))
209 .child(View::gap(1))
210 .child(View::styled_text("Key concepts").bold().build())
211 .child(View::text("• View::table() for tabular data"))
212 .child(View::text("• TableColumn for column config"))
213 .child(View::text("• ColumnWidth::Fixed or ColumnWidth::Flex"))
214 .child(View::text("• on_sort callback for sorting"))
215 .child(View::gap(1))
216 .child(View::styled_text("Try this").bold().build())
217 .child(View::text("• Navigate rows with arrow keys"))
218 .child(View::text("• Press Enter to activate a row"))
219 .child(View::text("• Sorting is managed via on_sort"))
220 .child(View::gap(1))
221 .child(View::styled_text("Next up").bold().build())
222 .child(View::text("→ 18_progress_bar: progress indicators"))
223 .child(View::gap(1))
224 .child(View::styled_text("Press Escape to close").dim().build())
225 .build(),
226 )
227 .build(),
228 )
229 .build()
230 }Sourcepub fn progress_bar() -> ProgressBarBuilder
pub fn progress_bar() -> ProgressBarBuilder
Create a progress bar builder.
Examples found in repository?
25 fn render(&self, cx: Scope) -> View {
26 let show_help = state!(cx, || false);
27
28 // F1 toggles help
29 cx.use_command(
30 KeyBinding::key(KeyCode::F(1)),
31 with!(show_help => move || show_help.update(|v| *v = !*v)),
32 );
33
34 // Animated progress value using stream
35 let progress = stream!(cx, || {
36 (0u64..).map(|i| {
37 if i > 0 {
38 std::thread::sleep(Duration::from_millis(50));
39 }
40 // Progress cycles from 0.0 to 1.0
41 (i % 100) as f32 / 100.0
42 })
43 });
44
45 let current_progress = progress.get();
46
47 View::vstack()
48 .spacing(1)
49 .child(View::styled_text("Progress Bar Examples").bold().build())
50 .child(View::text(""))
51 // Basic progress bar
52 .child(View::text("Basic (75%):"))
53 .child(View::progress_bar().value(0.75).build())
54 // With label
55 .child(View::text("With label (50%):"))
56 .child(View::progress_bar().value(0.5).label("Loading").build())
57 // Without percentage
58 .child(View::text("No percentage (33%):"))
59 .child(
60 View::progress_bar()
61 .value(0.33)
62 .show_percentage(false)
63 .build(),
64 )
65 // Fixed width
66 .child(View::text("Fixed width (20 chars, 60%):"))
67 .child(View::progress_bar().value(0.6).width(20).build())
68 // Custom characters
69 .child(View::text("Custom characters (80%):"))
70 .child(
71 View::progress_bar()
72 .value(0.8)
73 .filled_char('=')
74 .empty_char('-')
75 .width(20)
76 .build(),
77 )
78 // Another style
79 .child(View::text("Block style (65%):"))
80 .child(
81 View::progress_bar()
82 .value(0.65)
83 .filled_char('#')
84 .empty_char('.')
85 .width(25)
86 .build(),
87 )
88 // Animated progress
89 .child(View::text("Animated (loops 0-100%):"))
90 .child(
91 View::progress_bar()
92 .value(current_progress)
93 .label("Progress")
94 .build(),
95 )
96 .child(View::text(""))
97 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
98 .child(
99 View::modal()
100 .visible(show_help.get())
101 .title("Example 18: Progress Bar")
102 .on_dismiss(with!(show_help => move || show_help.set(false)))
103 .child(
104 View::vstack()
105 .child(View::styled_text("What you're seeing").bold().build())
106 .child(View::text("• Progress bars with various styles"))
107 .child(View::text("• Animated progress using stream!() macro"))
108 .child(View::text("• Custom fill and empty characters"))
109 .child(View::gap(1))
110 .child(View::styled_text("Key concepts").bold().build())
111 .child(View::text("• View::progress_bar() creates bars"))
112 .child(View::text("• .value(0.0 to 1.0) sets progress"))
113 .child(View::text("• .label() adds text label"))
114 .child(View::text("• .filled_char() / .empty_char() customize"))
115 .child(View::gap(1))
116 .child(View::styled_text("Try this").bold().build())
117 .child(View::text("• Watch the animated bar loop"))
118 .child(View::text("• Compare different bar styles"))
119 .child(View::gap(1))
120 .child(View::styled_text("Next up").bold().build())
121 .child(View::text("→ 19_status_bar: status bar widget"))
122 .child(View::gap(1))
123 .child(View::styled_text("Press Escape to close").dim().build())
124 .build(),
125 )
126 .build(),
127 )
128 .build()
129 }Sourcepub fn status_bar() -> StatusBarBuilder
pub fn status_bar() -> StatusBarBuilder
Create a status bar builder.
Examples found in repository?
24 fn render(&self, cx: Scope) -> View {
25 let show_help = state!(cx, || false);
26
27 // F1 toggles help
28 cx.use_command(
29 KeyBinding::key(KeyCode::F(1)),
30 with!(show_help => move || show_help.update(|v| *v = !*v)),
31 );
32 View::vstack()
33 .child(View::styled_text("Status Bar Examples").bold().build())
34 .child(View::text(""))
35 .child(View::text("Basic status bar (left only):"))
36 .child(View::status_bar().left("NORMAL").build())
37 .child(View::text(""))
38 .child(View::text("Left and right sections:"))
39 .child(
40 View::status_bar()
41 .left("INSERT")
42 .right("Ln 42, Col 8")
43 .build(),
44 )
45 .child(View::text(""))
46 .child(View::text("All three sections:"))
47 .child(
48 View::status_bar()
49 .left("VISUAL")
50 .center("main.rs")
51 .right("UTF-8 | LF | Rust")
52 .build(),
53 )
54 .child(View::text(""))
55 .child(View::text("Custom colors (green on dark):"))
56 .child(
57 View::status_bar()
58 .left("SUCCESS")
59 .center("All tests passed")
60 .right("100%")
61 .fg(Color::Green)
62 .bg(Color::DarkGreen)
63 .build(),
64 )
65 .child(View::text(""))
66 .child(View::text("Editor-style status bar:"))
67 .child(
68 View::status_bar()
69 .left("-- INSERT --")
70 .center("~/projects/myapp/src/main.rs [+]")
71 .right("1/100 | 50%")
72 .build(),
73 )
74 .child(View::spacer())
75 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
76 .child(
77 View::modal()
78 .visible(show_help.get())
79 .title("Example 19: Status Bar")
80 .on_dismiss(with!(show_help => move || show_help.set(false)))
81 .child(
82 View::vstack()
83 .child(View::styled_text("What you're seeing").bold().build())
84 .child(View::text("• Status bars with left/center/right sections"))
85 .child(View::text("• Custom foreground and background colors"))
86 .child(View::text("• Editor-style status line example"))
87 .child(View::gap(1))
88 .child(View::styled_text("Key concepts").bold().build())
89 .child(View::text("• View::status_bar() creates status lines"))
90 .child(View::text("• .left(), .center(), .right() for sections"))
91 .child(View::text("• .fg() and .bg() for custom colors"))
92 .child(View::text("• Great for showing mode, file info, etc."))
93 .child(View::gap(1))
94 .child(View::styled_text("Try this").bold().build())
95 .child(View::text("• Compare different status bar styles"))
96 .child(View::text("• Notice how sections align"))
97 .child(View::gap(1))
98 .child(View::styled_text("Next up").bold().build())
99 .child(View::text("→ 20_menu_bar: dropdown menus"))
100 .child(View::gap(1))
101 .child(View::styled_text("Press Escape to close").dim().build())
102 .build(),
103 )
104 .build(),
105 )
106 .build()
107 }Sourcepub fn command_palette() -> CommandPaletteBuilder
pub fn command_palette() -> CommandPaletteBuilder
Create a command palette builder.
Create a menu bar builder.
Examples found in repository?
26 fn render(&self, cx: Scope) -> View {
27 let show_help = state!(cx, || false);
28
29 // F1 toggles help
30 cx.use_command(
31 KeyBinding::key(KeyCode::F(1)),
32 with!(show_help => move || show_help.update(|v| *v = !*v)),
33 );
34
35 // Menu state
36 let active_menu = state!(cx, || Option::<usize>::None);
37 let highlighted_menu = state!(cx, || 0usize);
38 let selected_item = state!(cx, || 0usize);
39
40 // App state
41 let message = state!(cx, || "Use Tab to focus menu bar, then Enter to open".to_string());
42 let counter = state!(cx, || 0i32);
43
44 // Command handler - executes menu commands
45 let handle_command = with!(message, counter, active_menu, selected_item => move |cmd_id: &'static str| {
46 let msg = match cmd_id {
47 "file.new" => "Created new file".to_string(),
48 "file.open" => "Opening file...".to_string(),
49 "file.save" => "File saved".to_string(),
50 "file.quit" => "Use Ctrl+Q to quit".to_string(),
51 "edit.undo" => "Undone".to_string(),
52 "edit.redo" => "Redone".to_string(),
53 "edit.cut" => "Cut to clipboard".to_string(),
54 "edit.copy" => "Copied to clipboard".to_string(),
55 "edit.paste" => "Pasted from clipboard".to_string(),
56 "counter.increment" => {
57 counter.update(|n| *n += 1);
58 format!("Counter: {}", counter.get())
59 }
60 "counter.decrement" => {
61 counter.update(|n| *n -= 1);
62 format!("Counter: {}", counter.get())
63 }
64 "counter.reset" => {
65 counter.set(0);
66 "Counter reset".to_string()
67 }
68 _ => format!("Unknown: {}", cmd_id),
69 };
70 message.set(msg);
71 // Close menu after executing command
72 active_menu.set(None);
73 selected_item.set(0);
74 });
75
76 // Menu change handler - opens/closes menus
77 let on_menu_change = with!(active_menu, highlighted_menu, selected_item => move |idx: usize| {
78 if active_menu.get() == Some(idx) {
79 // Clicking same menu toggles it closed
80 active_menu.set(None);
81 } else {
82 active_menu.set(Some(idx));
83 highlighted_menu.set(idx); // Keep highlight in sync
84 selected_item.set(0);
85 }
86 });
87
88 // Highlight change handler - arrow key navigation when no menu is open
89 let on_highlight_change = with!(highlighted_menu => move |idx: usize| {
90 highlighted_menu.set(idx);
91 });
92
93 // Item change handler - navigates within menu
94 let on_item_change = with!(selected_item => move |idx: usize| {
95 selected_item.set(idx);
96 });
97
98 // Build menus
99 let file_menu = Menu::new("File")
100 .command_with_shortcut("file.new", "New", "Ctrl+N")
101 .command_with_shortcut("file.open", "Open", "Ctrl+O")
102 .command_with_shortcut("file.save", "Save", "Ctrl+S")
103 .separator()
104 .command_with_shortcut("file.quit", "Quit", "Ctrl+Q");
105
106 let edit_menu = Menu::new("Edit")
107 .command_with_shortcut("edit.undo", "Undo", "Ctrl+Z")
108 .command_with_shortcut("edit.redo", "Redo", "Ctrl+Y")
109 .separator()
110 .command_with_shortcut("edit.cut", "Cut", "Ctrl+X")
111 .command_with_shortcut("edit.copy", "Copy", "Ctrl+C")
112 .command_with_shortcut("edit.paste", "Paste", "Ctrl+V");
113
114 let counter_menu = Menu::new("Counter")
115 .command("counter.increment", "Increment")
116 .command("counter.decrement", "Decrement")
117 .separator()
118 .command("counter.reset", "Reset to Zero");
119
120 View::vstack()
121 .child(
122 View::menu_bar()
123 .menu(file_menu)
124 .menu(edit_menu)
125 .menu(counter_menu)
126 .active_menu(active_menu.get())
127 .highlighted_menu(highlighted_menu.get())
128 .selected_item(selected_item.get())
129 .on_select(handle_command)
130 .on_menu_change(on_menu_change)
131 .on_highlight_change(on_highlight_change)
132 .on_item_change(on_item_change)
133 .build(),
134 )
135 .child(
136 View::boxed()
137 .flex(1)
138 .border(true)
139 .padding(2)
140 .child(
141 View::vstack()
142 .spacing(1)
143 .child(View::styled_text("Menu Bar Demo").bold().build())
144 .child(
145 View::styled_text(format!("Counter: {}", counter.get()))
146 .color(Color::Cyan)
147 .bold()
148 .build(),
149 )
150 .child(
151 View::styled_text(format!("Status: {}", message.get()))
152 .dim()
153 .build(),
154 )
155 .child(View::spacer())
156 .child(View::styled_text("Keyboard Navigation:").bold().build())
157 .child(View::text(" Tab Focus menu bar"))
158 .child(View::text(" Enter Open menu / Execute item"))
159 .child(View::text(" Up/Down Navigate menu items"))
160 .child(View::text(" Left/Right Switch between menus"))
161 .child(View::text(" Escape Close menu"))
162 .child(View::text(" Ctrl+Q Quit"))
163 .child(View::text(" F1 Help"))
164 .build(),
165 )
166 .build(),
167 )
168 .child(
169 View::modal()
170 .visible(show_help.get())
171 .title("Example 20: Menu Bar")
172 .on_dismiss(with!(show_help => move || show_help.set(false)))
173 .child(
174 View::vstack()
175 .child(View::styled_text("What you're seeing").bold().build())
176 .child(View::text("• Dropdown menu bar with keyboard nav"))
177 .child(View::text("• Menu items with shortcuts"))
178 .child(View::text("• Separators in menus"))
179 .child(View::gap(1))
180 .child(View::styled_text("Key concepts").bold().build())
181 .child(View::text("• View::menu_bar() creates menu system"))
182 .child(View::text("• Menu::new().command() adds items"))
183 .child(View::text("• .command_with_shortcut() shows key hints"))
184 .child(View::text("• on_select receives command ID"))
185 .child(View::gap(1))
186 .child(View::styled_text("Try this").bold().build())
187 .child(View::text("• Tab to menu, Enter to open"))
188 .child(View::text("• Arrow keys navigate menus"))
189 .child(View::text("• Try the Counter menu"))
190 .child(View::gap(1))
191 .child(View::styled_text("Next up").bold().build())
192 .child(View::text("→ 21_toasts: toast notifications"))
193 .child(View::gap(1))
194 .child(View::styled_text("Press Escape to close").dim().build())
195 .build(),
196 )
197 .build(),
198 )
199 .build()
200 }Sourcepub fn toast_container() -> ToastContainerBuilder
pub fn toast_container() -> ToastContainerBuilder
Create a toast container builder.
Examples found in repository?
22 fn render(&self, cx: Scope) -> View {
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // Create a toast queue with 3 second default duration
32 let toasts = state!(cx, || ToastQueue::with_duration(Duration::from_secs(3)));
33 let position = state!(cx, || ToastPosition::BottomRight);
34
35 // Buttons to trigger different toast types
36 let show_info = with!(toasts => move || {
37 toasts.get().info("This is an informational message");
38 });
39
40 let show_success = with!(toasts => move || {
41 toasts.get().success("Operation completed successfully!");
42 });
43
44 let show_warning = with!(toasts => move || {
45 toasts.get().warning("Warning: This action cannot be undone");
46 });
47
48 let show_error = with!(toasts => move || {
49 toasts.get().error("Error: Connection failed");
50 });
51
52 let show_long_error = with!(toasts => move || {
53 toasts.get().error_long("Critical Error: Server not responding. Check your network.");
54 });
55
56 let clear_all = with!(toasts => move || {
57 toasts.get().clear();
58 });
59
60 // Position cycling
61 let cycle_position = with!(position => move || {
62 let next = match position.get() {
63 ToastPosition::TopRight => ToastPosition::TopLeft,
64 ToastPosition::TopLeft => ToastPosition::BottomLeft,
65 ToastPosition::BottomLeft => ToastPosition::BottomRight,
66 ToastPosition::BottomRight => ToastPosition::TopRight,
67 };
68 position.set(next);
69 });
70
71 let position_name = match position.get() {
72 ToastPosition::TopRight => "Top Right",
73 ToastPosition::TopLeft => "Top Left",
74 ToastPosition::BottomLeft => "Bottom Left",
75 ToastPosition::BottomRight => "Bottom Right",
76 };
77
78 View::vstack()
79 .spacing(1)
80 .child(
81 // Header
82 View::boxed()
83 .border(true)
84 .padding(1)
85 .child(
86 View::vstack()
87 .child(View::styled_text("Toast Notifications Demo").bold().build())
88 .child(
89 View::styled_text("Click buttons to show different toast types")
90 .dim()
91 .build(),
92 )
93 .build(),
94 )
95 .build(),
96 )
97 .child(
98 // Main content
99 View::boxed()
100 .flex(1)
101 .border(true)
102 .padding(1)
103 .child(
104 View::vstack()
105 .spacing(1)
106 .child(View::text("Toast Types:"))
107 .child(
108 View::hstack()
109 .spacing(2)
110 .child(View::button().label("Info").on_press(show_info).build())
111 .child(
112 View::button()
113 .label("Success")
114 .on_press(show_success)
115 .build(),
116 )
117 .child(
118 View::button()
119 .label("Warning")
120 .on_press(show_warning)
121 .build(),
122 )
123 .child(
124 View::button().label("Error").on_press(show_error).build(),
125 )
126 .build(),
127 )
128 .child(View::gap(1))
129 .child(View::text("Other Actions:"))
130 .child(
131 View::hstack()
132 .spacing(2)
133 .child(
134 View::button()
135 .label("Long Error")
136 .on_press(show_long_error)
137 .build(),
138 )
139 .child(
140 View::button()
141 .label("Clear All")
142 .on_press(clear_all)
143 .build(),
144 )
145 .build(),
146 )
147 .child(View::gap(1))
148 .child(View::text(format!("Position: {}", position_name)))
149 .child(
150 View::button()
151 .label("Change Position")
152 .on_press(cycle_position)
153 .build(),
154 )
155 .child(View::spacer())
156 .child(
157 View::styled_text(format!("Active toasts: {}", toasts.get().len()))
158 .color(Color::Yellow)
159 .build(),
160 )
161 .child(
162 View::styled_text("Toasts auto-dismiss after 3 seconds")
163 .dim()
164 .build(),
165 )
166 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
167 .build(),
168 )
169 .build(),
170 )
171 .child(
172 // Toast container - renders the toast stack
173 View::toast_container()
174 .from_queue(&toasts.get())
175 .position(position.get())
176 .max_visible(5)
177 .width(40)
178 .build(),
179 )
180 .child(
181 View::modal()
182 .visible(show_help.get())
183 .title("Example 21: Toasts")
184 .on_dismiss(with!(show_help => move || show_help.set(false)))
185 .child(
186 View::vstack()
187 .child(View::styled_text("What you're seeing").bold().build())
188 .child(View::text("• Toast notifications in corner"))
189 .child(View::text("• Auto-dismiss after 3 seconds"))
190 .child(View::text(
191 "• Multiple toast types (info/success/warn/error)",
192 ))
193 .child(View::gap(1))
194 .child(View::styled_text("Key concepts").bold().build())
195 .child(View::text("• ToastQueue manages notifications"))
196 .child(View::text("• View::toast_container() renders them"))
197 .child(View::text("• .position() controls corner placement"))
198 .child(View::text("• .max_visible() limits shown toasts"))
199 .child(View::gap(1))
200 .child(View::styled_text("Try this").bold().build())
201 .child(View::text("• Click different toast type buttons"))
202 .child(View::text("• Change position to see toasts move"))
203 .child(View::text("• Spam buttons to stack toasts"))
204 .child(View::gap(1))
205 .child(View::styled_text("Next up").bold().build())
206 .child(View::text("→ 22_forms: form validation"))
207 .child(View::gap(1))
208 .child(View::styled_text("Press Escape to close").dim().build())
209 .build(),
210 )
211 .build(),
212 )
213 .build()
214 }Sourcepub fn form() -> FormBuilder
pub fn form() -> FormBuilder
Create a form builder.
Examples found in repository?
21 fn render(&self, cx: Scope) -> View {
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 // Create form state with validated fields
31 let form = state!(cx, || {
32 FormState::new()
33 .field(
34 FieldBuilder::new("email")
35 .required()
36 .email()
37 .error_message("Please enter a valid email address")
38 .build(),
39 )
40 .field(
41 FieldBuilder::new("password")
42 .required()
43 .min_length(8)
44 .error_message("Password must be at least 8 characters")
45 .build(),
46 )
47 .field(
48 FieldBuilder::new("username")
49 .required()
50 .min_length(3)
51 .max_length(20)
52 .custom(|v| {
53 if v.contains(' ') {
54 Some("Username cannot contain spaces".to_string())
55 } else if !v.chars().all(|c| c.is_alphanumeric() || c == '_') {
56 Some(
57 "Username can only contain letters, numbers, and underscores"
58 .to_string(),
59 )
60 } else {
61 None
62 }
63 })
64 .build(),
65 )
66 .field(
67 FieldBuilder::new("age")
68 .integer()
69 .custom(|v| {
70 if v.is_empty() {
71 return None; // Optional field
72 }
73 match v.parse::<i32>() {
74 Ok(age) if age < 0 => Some("Age cannot be negative".to_string()),
75 Ok(age) if age > 150 => {
76 Some("Please enter a valid age".to_string())
77 }
78 Ok(_) => None,
79 Err(_) => Some("Please enter a valid number".to_string()),
80 }
81 })
82 .build(),
83 )
84 });
85
86 let submit_message = state!(cx, String::new);
87
88 // Get current field values and errors
89 let email = form.get().get_value("email");
90 let password = form.get().get_value("password");
91 let username = form.get().get_value("username");
92 let age = form.get().get_value("age");
93
94 let email_error = form.get().get_error("email");
95 let password_error = form.get().get_error("password");
96 let username_error = form.get().get_error("username");
97 let age_error = form.get().get_error("age");
98
99 // Handlers
100 let on_submit = with!(form, submit_message => move || {
101 if form.get().validate() {
102 let values = form.get().values();
103 submit_message.set(format!(
104 "Form submitted! Email: {}, Username: {}",
105 values.get("email").unwrap_or(&String::new()),
106 values.get("username").unwrap_or(&String::new())
107 ));
108 } else {
109 submit_message.set("Please fix the errors above".to_string());
110 }
111 });
112
113 let on_reset = with!(form, submit_message => move || {
114 form.get().reset();
115 submit_message.set(String::new());
116 });
117
118 View::vstack()
119 .spacing(1)
120 .child(
121 // Header
122 View::boxed()
123 .border(true)
124 .padding(1)
125 .child(
126 View::vstack()
127 .child(View::styled_text("Form Validation Demo").bold().build())
128 .child(
129 View::styled_text("Tab between fields, type to enter values")
130 .dim()
131 .build(),
132 )
133 .build(),
134 )
135 .build(),
136 )
137 .child(
138 // Form
139 View::boxed()
140 .flex(1)
141 .border(true)
142 .padding(1)
143 .child(
144 View::form()
145 .spacing(1)
146 .child(
147 View::form_field("email")
148 .label("Email Address *")
149 .value(email.clone())
150 .placeholder("you@example.com")
151 .error(email_error)
152 .on_change(with!(form => move |v: String| {
153 form.get().set_value("email", v);
154 }))
155 .on_blur(with!(form => move || {
156 form.get().touch("email");
157 }))
158 .build(),
159 )
160 .child(
161 View::form_field("username")
162 .label("Username *")
163 .value(username.clone())
164 .placeholder("johndoe")
165 .error(username_error)
166 .on_change(with!(form => move |v: String| {
167 form.get().set_value("username", v);
168 }))
169 .on_blur(with!(form => move || {
170 form.get().touch("username");
171 }))
172 .build(),
173 )
174 .child(
175 View::form_field("password")
176 .label("Password * (min 8 chars)")
177 .value(password.clone())
178 .placeholder("Enter password")
179 .password(true)
180 .error(password_error)
181 .on_change(with!(form => move |v: String| {
182 form.get().set_value("password", v);
183 }))
184 .on_blur(with!(form => move || {
185 form.get().touch("password");
186 }))
187 .build(),
188 )
189 .child(
190 View::form_field("age")
191 .label("Age (optional)")
192 .value(age.clone())
193 .placeholder("25")
194 .error(age_error)
195 .on_change(with!(form => move |v: String| {
196 form.get().set_value("age", v);
197 }))
198 .on_blur(with!(form => move || {
199 form.get().touch("age");
200 }))
201 .build(),
202 )
203 .build(),
204 )
205 .build(),
206 )
207 .child(
208 // Actions
209 View::hstack()
210 .spacing(2)
211 .child(View::button().label("Submit").on_press(on_submit).build())
212 .child(View::button().label("Reset").on_press(on_reset).build())
213 .build(),
214 )
215 .child(
216 // Status
217 View::boxed()
218 .border(true)
219 .padding(1)
220 .child(
221 View::vstack()
222 .child(View::text(if submit_message.get().is_empty() {
223 "Fill in the form and click Submit".to_string()
224 } else {
225 submit_message.get()
226 }))
227 .child(
228 View::styled_text(format!(
229 "Form valid: {}",
230 if form.get().is_valid() { "Yes" } else { "No" }
231 ))
232 .color(if form.get().is_valid() {
233 Color::Green
234 } else {
235 Color::Red
236 })
237 .build(),
238 )
239 .build(),
240 )
241 .build(),
242 )
243 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
244 .child(
245 View::modal()
246 .visible(show_help.get())
247 .title("Example 22: Forms")
248 .on_dismiss(with!(show_help => move || show_help.set(false)))
249 .child(
250 View::vstack()
251 .child(View::styled_text("What you're seeing").bold().build())
252 .child(View::text("• Declarative form validation"))
253 .child(View::text("• Required, email, length validators"))
254 .child(View::text("• Custom validation functions"))
255 .child(View::gap(1))
256 .child(View::styled_text("Key concepts").bold().build())
257 .child(View::text("• FormState manages all fields"))
258 .child(View::text("• FieldBuilder defines validation"))
259 .child(View::text("• View::form_field() renders inputs"))
260 .child(View::text("• on_blur triggers validation"))
261 .child(View::gap(1))
262 .child(View::styled_text("Try this").bold().build())
263 .child(View::text("• Enter invalid email, see error"))
264 .child(View::text("• Try short password (<8 chars)"))
265 .child(View::text("• Username with spaces shows error"))
266 .child(View::gap(1))
267 .child(View::styled_text("Next up").bold().build())
268 .child(View::text("→ 23_modal: modal dialogs"))
269 .child(View::gap(1))
270 .child(View::styled_text("Press Escape to close").dim().build())
271 .build(),
272 )
273 .build(),
274 )
275 .build()
276 }Sourcepub fn form_field(name: impl Into<String>) -> FormFieldBuilder
pub fn form_field(name: impl Into<String>) -> FormFieldBuilder
Create a form field builder.
Examples found in repository?
21 fn render(&self, cx: Scope) -> View {
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 // Create form state with validated fields
31 let form = state!(cx, || {
32 FormState::new()
33 .field(
34 FieldBuilder::new("email")
35 .required()
36 .email()
37 .error_message("Please enter a valid email address")
38 .build(),
39 )
40 .field(
41 FieldBuilder::new("password")
42 .required()
43 .min_length(8)
44 .error_message("Password must be at least 8 characters")
45 .build(),
46 )
47 .field(
48 FieldBuilder::new("username")
49 .required()
50 .min_length(3)
51 .max_length(20)
52 .custom(|v| {
53 if v.contains(' ') {
54 Some("Username cannot contain spaces".to_string())
55 } else if !v.chars().all(|c| c.is_alphanumeric() || c == '_') {
56 Some(
57 "Username can only contain letters, numbers, and underscores"
58 .to_string(),
59 )
60 } else {
61 None
62 }
63 })
64 .build(),
65 )
66 .field(
67 FieldBuilder::new("age")
68 .integer()
69 .custom(|v| {
70 if v.is_empty() {
71 return None; // Optional field
72 }
73 match v.parse::<i32>() {
74 Ok(age) if age < 0 => Some("Age cannot be negative".to_string()),
75 Ok(age) if age > 150 => {
76 Some("Please enter a valid age".to_string())
77 }
78 Ok(_) => None,
79 Err(_) => Some("Please enter a valid number".to_string()),
80 }
81 })
82 .build(),
83 )
84 });
85
86 let submit_message = state!(cx, String::new);
87
88 // Get current field values and errors
89 let email = form.get().get_value("email");
90 let password = form.get().get_value("password");
91 let username = form.get().get_value("username");
92 let age = form.get().get_value("age");
93
94 let email_error = form.get().get_error("email");
95 let password_error = form.get().get_error("password");
96 let username_error = form.get().get_error("username");
97 let age_error = form.get().get_error("age");
98
99 // Handlers
100 let on_submit = with!(form, submit_message => move || {
101 if form.get().validate() {
102 let values = form.get().values();
103 submit_message.set(format!(
104 "Form submitted! Email: {}, Username: {}",
105 values.get("email").unwrap_or(&String::new()),
106 values.get("username").unwrap_or(&String::new())
107 ));
108 } else {
109 submit_message.set("Please fix the errors above".to_string());
110 }
111 });
112
113 let on_reset = with!(form, submit_message => move || {
114 form.get().reset();
115 submit_message.set(String::new());
116 });
117
118 View::vstack()
119 .spacing(1)
120 .child(
121 // Header
122 View::boxed()
123 .border(true)
124 .padding(1)
125 .child(
126 View::vstack()
127 .child(View::styled_text("Form Validation Demo").bold().build())
128 .child(
129 View::styled_text("Tab between fields, type to enter values")
130 .dim()
131 .build(),
132 )
133 .build(),
134 )
135 .build(),
136 )
137 .child(
138 // Form
139 View::boxed()
140 .flex(1)
141 .border(true)
142 .padding(1)
143 .child(
144 View::form()
145 .spacing(1)
146 .child(
147 View::form_field("email")
148 .label("Email Address *")
149 .value(email.clone())
150 .placeholder("you@example.com")
151 .error(email_error)
152 .on_change(with!(form => move |v: String| {
153 form.get().set_value("email", v);
154 }))
155 .on_blur(with!(form => move || {
156 form.get().touch("email");
157 }))
158 .build(),
159 )
160 .child(
161 View::form_field("username")
162 .label("Username *")
163 .value(username.clone())
164 .placeholder("johndoe")
165 .error(username_error)
166 .on_change(with!(form => move |v: String| {
167 form.get().set_value("username", v);
168 }))
169 .on_blur(with!(form => move || {
170 form.get().touch("username");
171 }))
172 .build(),
173 )
174 .child(
175 View::form_field("password")
176 .label("Password * (min 8 chars)")
177 .value(password.clone())
178 .placeholder("Enter password")
179 .password(true)
180 .error(password_error)
181 .on_change(with!(form => move |v: String| {
182 form.get().set_value("password", v);
183 }))
184 .on_blur(with!(form => move || {
185 form.get().touch("password");
186 }))
187 .build(),
188 )
189 .child(
190 View::form_field("age")
191 .label("Age (optional)")
192 .value(age.clone())
193 .placeholder("25")
194 .error(age_error)
195 .on_change(with!(form => move |v: String| {
196 form.get().set_value("age", v);
197 }))
198 .on_blur(with!(form => move || {
199 form.get().touch("age");
200 }))
201 .build(),
202 )
203 .build(),
204 )
205 .build(),
206 )
207 .child(
208 // Actions
209 View::hstack()
210 .spacing(2)
211 .child(View::button().label("Submit").on_press(on_submit).build())
212 .child(View::button().label("Reset").on_press(on_reset).build())
213 .build(),
214 )
215 .child(
216 // Status
217 View::boxed()
218 .border(true)
219 .padding(1)
220 .child(
221 View::vstack()
222 .child(View::text(if submit_message.get().is_empty() {
223 "Fill in the form and click Submit".to_string()
224 } else {
225 submit_message.get()
226 }))
227 .child(
228 View::styled_text(format!(
229 "Form valid: {}",
230 if form.get().is_valid() { "Yes" } else { "No" }
231 ))
232 .color(if form.get().is_valid() {
233 Color::Green
234 } else {
235 Color::Red
236 })
237 .build(),
238 )
239 .build(),
240 )
241 .build(),
242 )
243 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
244 .child(
245 View::modal()
246 .visible(show_help.get())
247 .title("Example 22: Forms")
248 .on_dismiss(with!(show_help => move || show_help.set(false)))
249 .child(
250 View::vstack()
251 .child(View::styled_text("What you're seeing").bold().build())
252 .child(View::text("• Declarative form validation"))
253 .child(View::text("• Required, email, length validators"))
254 .child(View::text("• Custom validation functions"))
255 .child(View::gap(1))
256 .child(View::styled_text("Key concepts").bold().build())
257 .child(View::text("• FormState manages all fields"))
258 .child(View::text("• FieldBuilder defines validation"))
259 .child(View::text("• View::form_field() renders inputs"))
260 .child(View::text("• on_blur triggers validation"))
261 .child(View::gap(1))
262 .child(View::styled_text("Try this").bold().build())
263 .child(View::text("• Enter invalid email, see error"))
264 .child(View::text("• Try short password (<8 chars)"))
265 .child(View::text("• Username with spaces shows error"))
266 .child(View::gap(1))
267 .child(View::styled_text("Next up").bold().build())
268 .child(View::text("→ 23_modal: modal dialogs"))
269 .child(View::gap(1))
270 .child(View::styled_text("Press Escape to close").dim().build())
271 .build(),
272 )
273 .build(),
274 )
275 .build()
276 }Sourcepub fn canvas() -> CanvasBuilder
pub fn canvas() -> CanvasBuilder
Create a canvas builder for pixel-level drawing.
Experimental Feature
Canvas uses the Kitty graphics protocol for actual pixel rendering. Requires a compatible terminal (Kitty, Ghostty, WezTerm). Other terminals will show a placeholder message.
Examples found in repository?
31 fn render(&self, cx: Scope) -> View {
32 let show_help = state!(cx, || false);
33
34 // F1 toggles help
35 cx.use_command(
36 KeyBinding::key(KeyCode::F(1)),
37 with!(show_help => move || show_help.update(|v| *v = !*v)),
38 );
39
40 // Animated value for the bar chart using stream macro
41 let frame_stream = stream!(cx, || {
42 (0u32..).inspect(|&i| {
43 if i > 0 {
44 std::thread::sleep(std::time::Duration::from_millis(100));
45 }
46 })
47 });
48
49 let current_frame = frame_stream.get();
50
51 View::vstack()
52 .spacing(1)
53 .child(
54 View::styled_text("Canvas Examples (Kitty Graphics)")
55 .bold()
56 .build(),
57 )
58 .child(View::text("Requires Kitty, Ghostty, or WezTerm terminal"))
59 .child(View::text(""))
60 // Basic shapes demo
61 .child(View::text("Basic Shapes:"))
62 .child(
63 View::canvas()
64 .width(200)
65 .height(80)
66 .on_draw(|ctx| {
67 // Clear to dark background
68 ctx.clear(Color::Rgb {
69 r: 30,
70 g: 30,
71 b: 40,
72 });
73
74 // Draw some lines
75 ctx.line(10, 10, 190, 10, Color::Red);
76 ctx.line(10, 10, 10, 70, Color::Green);
77 ctx.line(10, 70, 190, 70, Color::Blue);
78 ctx.line(190, 10, 190, 70, Color::Yellow);
79
80 // Draw diagonal lines
81 ctx.line(10, 10, 190, 70, Color::Cyan);
82 ctx.line(10, 70, 190, 10, Color::Magenta);
83
84 // Draw filled rectangles
85 ctx.fill_rect(30, 25, 30, 20, Color::Red);
86 ctx.fill_rect(80, 25, 30, 20, Color::Green);
87 ctx.fill_rect(130, 25, 30, 20, Color::Blue);
88
89 // Draw stroked rectangles
90 ctx.stroke_rect(30, 50, 30, 15, Color::Yellow);
91 ctx.stroke_rect(80, 50, 30, 15, Color::Cyan);
92 ctx.stroke_rect(130, 50, 30, 15, Color::Magenta);
93 })
94 .build(),
95 )
96 .child(View::text(""))
97 // Circles demo
98 .child(View::text("Circles:"))
99 .child(
100 View::canvas()
101 .width(200)
102 .height(60)
103 .on_draw(|ctx| {
104 ctx.clear(Color::Rgb {
105 r: 20,
106 g: 25,
107 b: 35,
108 });
109
110 // Filled circles
111 ctx.fill_circle(30, 30, 20, Color::Red);
112 ctx.fill_circle(80, 30, 15, Color::Green);
113 ctx.fill_circle(120, 30, 10, Color::Blue);
114
115 // Stroked circles
116 ctx.circle(160, 30, 20, Color::Yellow);
117 ctx.circle(160, 30, 15, Color::Cyan);
118 ctx.circle(160, 30, 10, Color::Magenta);
119 })
120 .build(),
121 )
122 .child(View::text(""))
123 // Animated bar chart
124 .child(View::text("Animated Bar Chart:"))
125 .child(
126 View::canvas()
127 .width(200)
128 .height(80)
129 .on_draw({
130 move |ctx| {
131 ctx.clear(Color::Rgb {
132 r: 25,
133 g: 25,
134 b: 30,
135 });
136
137 // Generate animated data
138 let data: Vec<f32> = (0..8)
139 .map(|i| {
140 let phase = (current_frame as f32 * 0.1) + (i as f32 * 0.5);
141 0.3 + 0.7 * ((phase.sin() + 1.0) / 2.0)
142 })
143 .collect();
144
145 let bar_width = 20u16;
146 let gap = 5u16;
147 let max_height = 60u16;
148 let start_x = 10u16;
149 let baseline = 75u16;
150
151 // Draw baseline
152 ctx.line(5, baseline as i32, 195, baseline as i32, Color::Grey);
153
154 // Draw bars
155 let colors = [
156 Color::Red,
157 Color::Green,
158 Color::Blue,
159 Color::Yellow,
160 Color::Cyan,
161 Color::Magenta,
162 Color::Rgb {
163 r: 255,
164 g: 128,
165 b: 0,
166 },
167 Color::Rgb {
168 r: 128,
169 g: 255,
170 b: 128,
171 },
172 ];
173
174 for (i, &value) in data.iter().enumerate() {
175 let x = start_x + (i as u16) * (bar_width + gap);
176 let height = (value * max_height as f32) as u16;
177 let y = baseline - height;
178 ctx.fill_rect(x, y, bar_width, height, colors[i % colors.len()]);
179 }
180 }
181 })
182 .build(),
183 )
184 .child(View::text(""))
185 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
186 .child(
187 View::modal()
188 .visible(show_help.get())
189 .title("Example 29: Canvas")
190 .on_dismiss(with!(show_help => move || show_help.set(false)))
191 .child(
192 View::vstack()
193 .child(View::styled_text("What you're seeing").bold().build())
194 .child(View::text("• Pixel graphics via Kitty protocol"))
195 .child(View::text("• Lines, rectangles, circles"))
196 .child(View::text("• Animated bar chart"))
197 .child(View::gap(1))
198 .child(View::styled_text("Key concepts").bold().build())
199 .child(View::text("• View::canvas() creates drawing area"))
200 .child(View::text("• .on_draw(|ctx| { ... }) draws pixels"))
201 .child(View::text("• ctx.line(), ctx.fill_rect(), etc."))
202 .child(View::text("• Works in Kitty/Ghostty/WezTerm"))
203 .child(View::gap(1))
204 .child(View::styled_text("Try this").bold().build())
205 .child(View::text("• Watch the animated bar chart"))
206 .child(View::text("• Run in compatible terminal"))
207 .child(View::gap(1))
208 .child(View::styled_text("Next up").bold().build())
209 .child(View::text("→ 30_image: image display"))
210 .child(View::gap(1))
211 .child(View::styled_text("Press Escape to close").dim().build())
212 .build(),
213 )
214 .build(),
215 )
216 .build()
217 }Sourcepub fn image() -> ImageBuilder
pub fn image() -> ImageBuilder
Create an image builder for displaying images.
Experimental Feature
Displays PNG, JPEG, or GIF images using the Kitty graphics protocol. GIF animations are handled natively by Kitty. Requires a compatible terminal (Kitty, Ghostty, WezTerm). Other terminals will show alt text or a placeholder message.
Examples found in repository?
30 fn render(&self, cx: Scope) -> View {
31 let show_help = state!(cx, || false);
32
33 // F1 toggles help
34 cx.use_command(
35 KeyBinding::key(KeyCode::F(1)),
36 with!(show_help => move || show_help.update(|v| *v = !*v)),
37 );
38 View::vstack()
39 .spacing(1)
40 .child(
41 View::styled_text("Image Widget Demo (Kitty Graphics)")
42 .bold()
43 .build(),
44 )
45 .child(View::text("Requires Kitty, Ghostty, or WezTerm terminal"))
46 .child(View::text(""))
47 // Load image from file path
48 .child(View::text("Logo (from file path):"))
49 .child(View::image().file("assets/telex-tui.png").build())
50 .child(View::text(""))
51 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
52 .child(
53 View::modal()
54 .visible(show_help.get())
55 .title("Example 30: Image")
56 .on_dismiss(with!(show_help => move || show_help.set(false)))
57 .child(
58 View::vstack()
59 .child(View::styled_text("What you're seeing").bold().build())
60 .child(View::text("• Image display via Kitty protocol"))
61 .child(View::text("• PNG/JPEG/GIF support"))
62 .child(View::text("• Loaded from file path"))
63 .child(View::gap(1))
64 .child(View::styled_text("Key concepts").bold().build())
65 .child(View::text("• View::image() displays images"))
66 .child(View::text("• .file(\"path\") loads from disk"))
67 .child(View::text("• .bytes(data) for embedded images"))
68 .child(View::text("• Works in Kitty/Ghostty/WezTerm"))
69 .child(View::gap(1))
70 .child(View::styled_text("Try this").bold().build())
71 .child(View::text("• Run in compatible terminal"))
72 .child(View::text("• See the Telex logo rendered"))
73 .child(View::gap(1))
74 .child(View::styled_text("Next up").bold().build())
75 .child(View::text("→ 31_animated_canvas: animations"))
76 .child(View::gap(1))
77 .child(View::styled_text("Press Escape to close").dim().build())
78 .build(),
79 )
80 .build(),
81 )
82 .build()
83 }Sourcepub fn terminal() -> TerminalBuilder
pub fn terminal() -> TerminalBuilder
Create a terminal builder for interactive PTY terminal emulation.
Status: Experimental Preview
Supports running shell commands (bash, vim, htop, etc.) with full keyboard input and ANSI color/style rendering.
§Known Limitations
- No scrollback buffer
- No terminal resize support
- No copy/paste
- No mouse input
Use for prototyping and experimentation. Breaking changes likely.
§Example
let terminal = cx.use_terminal();
if !terminal.is_started() {
terminal.spawn("bash", &[], 80, 24);
}
View::terminal().handle(terminal).build()Examples found in repository?
5fn app(cx: Scope) -> View {
6 let terminal = terminal!(cx);
7
8 // Spawn bash on first render
9 if !terminal.is_started() {
10 if let Err(e) = terminal.spawn("bash", &[], 80, 24) {
11 eprintln!("Failed to spawn terminal: {}", e);
12 }
13 }
14
15 View::vstack()
16 .child(View::text("Telex Terminal Demo"))
17 .child(View::text(
18 "Press Ctrl+Shift+[ to escape terminal focus, Tab to navigate",
19 ))
20 .child(View::terminal().handle(terminal).build())
21 .build()
22}Sourcepub fn custom(widget: Rc<RefCell<dyn Widget>>) -> Self
pub fn custom(widget: Rc<RefCell<dyn Widget>>) -> Self
Create a custom widget view.
Wraps a user-defined Widget implementation in a View.
Use this for custom character-cell rendering that can’t be
composed from built-in widgets.
§Example
let my_widget = Rc::new(RefCell::new(MyWidget::new()));
View::custom(my_widget)Examples found in repository?
38 fn render(&self, cx: Scope) -> View {
39 let show_help = state!(cx, || false);
40
41 cx.use_command(
42 KeyBinding::key(KeyCode::F(1)),
43 with!(show_help => move || show_help.update(|v| *v = !*v)),
44 );
45
46 let count = state!(cx, || 0i32);
47
48 // The panic must happen at render time (inside render_view) so the
49 // error boundary's catch_unwind can catch it. A custom widget defers
50 // execution to the render pass.
51 let risky_view = View::custom(std::rc::Rc::new(std::cell::RefCell::new(RiskyCounter(count.get()))));
52
53 let fallback = View::vstack()
54 .child(View::styled_text("CAUGHT PANIC").color(Color::Red).bold().build())
55 .child(View::text("The child view panicked."))
56 .child(View::text("But the app is still running!"))
57 .build();
58
59 View::vstack()
60 .spacing(1)
61 .child(View::styled_text("Error Boundary Demo").bold().build())
62 .child(
63 View::hstack()
64 .spacing(2)
65 .child(
66 View::vstack()
67 .spacing(1)
68 .child(View::styled_text("Protected Panel").bold().build())
69 .child(
70 View::error_boundary()
71 .child(risky_view)
72 .fallback(fallback)
73 .build(),
74 )
75 .build(),
76 )
77 .child(
78 View::vstack()
79 .spacing(1)
80 .child(View::styled_text("How It Works").bold().build())
81 .child(View::text("The left panel asserts"))
82 .child(View::text("count < 5. When it hits"))
83 .child(View::text("5, the error boundary"))
84 .child(View::text("catches the panic and"))
85 .child(View::text("renders the fallback."))
86 .build(),
87 )
88 .build(),
89 )
90 .child(
91 View::hstack()
92 .spacing(1)
93 .child(
94 View::button()
95 .label("[ + Increment ]")
96 .on_press(with!(count => move || count.update(|n| *n += 1)))
97 .build(),
98 )
99 .child(
100 View::button()
101 .label("[ Reset to 0 ]")
102 .on_press(with!(count => move || count.set(0)))
103 .build(),
104 )
105 .child(View::styled_text(format!("count = {}", count.get())).dim().build())
106 .build(),
107 )
108 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
109 .child(
110 View::modal()
111 .visible(show_help.get())
112 .title("Example 37: Error Boundary")
113 .on_dismiss(with!(show_help => move || show_help.set(false)))
114 .child(
115 View::vstack()
116 .child(View::styled_text("What you're seeing").bold().build())
117 .child(View::text("• A counter that panics at 5"))
118 .child(View::text("• Error boundary catches the panic"))
119 .child(View::text("• Red fallback replaces the crash"))
120 .child(View::gap(1))
121 .child(View::styled_text("Key concepts").bold().build())
122 .child(View::text("• View::error_boundary()"))
123 .child(View::text("• .child(risky) .fallback(safe)"))
124 .child(View::text("• Panics are caught, not propagated"))
125 .child(View::text("• App keeps running after panic"))
126 .child(View::gap(1))
127 .child(View::styled_text("Try this").bold().build())
128 .child(View::text("• Increment to 5 to trigger panic"))
129 .child(View::text("• Reset to 0 to recover"))
130 .child(View::text("• Keep incrementing past 5"))
131 .child(View::gap(1))
132 .child(View::styled_text("Next up").bold().build())
133 .child(View::text("-> 38_custom_widget: Game of Life"))
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
119 fn render(&self, cx: Scope) -> View {
120 let show_help = state!(cx, || false);
121
122 cx.use_command(
123 KeyBinding::key(KeyCode::F(1)),
124 with!(show_help => move || show_help.update(|v| *v = !*v)),
125 );
126
127 let game: State<GameOfLife> = state!(cx, || {
128 let mut g = GameOfLife::new();
129 g.randomize();
130 g
131 });
132 let generation = state!(cx, || 0u64);
133 let playing = state!(cx, || false);
134
135 // Auto-step when playing
136 interval!(cx, Duration::from_millis(150), with!(playing, game, generation => move || {
137 if playing.get() {
138 game.update(|g| g.step());
139 generation.update(|n| *n += 1);
140 }
141 }));
142
143 let widget = Rc::new(RefCell::new(game.get()));
144
145 View::vstack()
146 .spacing(1)
147 .child(View::styled_text("Game of Life").bold().build())
148 .child(
149 View::hstack()
150 .spacing(1)
151 .child(View::styled_text(format!("Gen: {}", generation.get())).dim().build())
152 .child(View::styled_text(format!("Alive: {}", game.get().alive_count())).dim().build())
153 .child(if playing.get() {
154 View::styled_text("PLAYING").color(Color::Green).bold().build()
155 } else {
156 View::styled_text("PAUSED").color(Color::Yellow).build()
157 })
158 .build(),
159 )
160 .child(View::custom(widget))
161 .child(
162 View::hstack()
163 .spacing(1)
164 .child(
165 View::button()
166 .label("[ Step ]")
167 .on_press(with!(game, generation => move || {
168 game.update(|g| g.step());
169 generation.update(|n| *n += 1);
170 }))
171 .build(),
172 )
173 .child(
174 View::button()
175 .label(if playing.get() { "[ Pause ]" } else { "[ Play ]" })
176 .on_press(with!(playing => move || playing.update(|p| *p = !*p)))
177 .build(),
178 )
179 .child(
180 View::button()
181 .label("[ Randomize ]")
182 .on_press(with!(game, generation => move || {
183 game.update(|g| g.randomize());
184 generation.set(0);
185 }))
186 .build(),
187 )
188 .child(
189 View::button()
190 .label("[ Clear ]")
191 .on_press(with!(game, generation => move || {
192 game.update(|g| g.clear());
193 generation.set(0);
194 }))
195 .build(),
196 )
197 .build(),
198 )
199 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
200 .child(
201 View::modal()
202 .visible(show_help.get())
203 .title("Example 38: Custom Widget")
204 .on_dismiss(with!(show_help => move || show_help.set(false)))
205 .child(
206 View::vstack()
207 .child(View::styled_text("What you're seeing").bold().build())
208 .child(View::text("• Conway's Game of Life"))
209 .child(View::text("• Custom Widget renders the grid"))
210 .child(View::text("• interval! drives auto-play"))
211 .child(View::gap(1))
212 .child(View::styled_text("Key concepts").bold().build())
213 .child(View::text("• impl Widget for YourStruct"))
214 .child(View::text("• render(area, buf) draws cells"))
215 .child(View::text("• height_hint / width_hint for sizing"))
216 .child(View::text("• View::custom(Rc<RefCell<W>>)"))
217 .child(View::gap(1))
218 .child(View::styled_text("Try this").bold().build())
219 .child(View::text("• Press Play to auto-step"))
220 .child(View::text("• Step manually one at a time"))
221 .child(View::text("• Randomize for a new pattern"))
222 .child(View::text("• Clear then Step to watch"))
223 .child(View::gap(1))
224 .child(View::styled_text("Next up").bold().build())
225 .child(View::text("-> 39_port: bidirectional comms"))
226 .child(View::gap(1))
227 .child(View::styled_text("Press Escape to close").dim().build())
228 .build(),
229 )
230 .build(),
231 )
232 .build()
233 }Sourcepub fn slider() -> SliderBuilder
pub fn slider() -> SliderBuilder
Create a slider builder for bounded numeric values.
§Example
View::slider()
.min(0.0)
.max(127.0)
.value(64.0)
.step(1.0)
.label("Volume")
.on_change(move |v| vol.set(v))
.build()Examples found in repository?
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
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 r = state!(cx, || 128.0);
30 let g = state!(cx, || 0.0);
31 let b = state!(cx, || 255.0);
32
33 let rv = r.get() as u8;
34 let gv = g.get() as u8;
35 let bv = b.get() as u8;
36
37 View::vstack()
38 .spacing(1)
39 .child(View::styled_text("RGB Color Mixer").bold().build())
40 .child(
41 View::slider()
42 .min(0.0)
43 .max(255.0)
44 .step(1.0)
45 .value(r.get())
46 .label(&format!("Red: {:>3}", rv))
47 .color(Color::Rgb { r: 255, g: 80, b: 80 })
48 .on_change(with!(r => move |v: f64| r.set(v)))
49 .build(),
50 )
51 .child(
52 View::slider()
53 .min(0.0)
54 .max(255.0)
55 .step(1.0)
56 .value(g.get())
57 .label(&format!("Green: {:>3}", gv))
58 .color(Color::Rgb { r: 80, g: 255, b: 80 })
59 .on_change(with!(g => move |v: f64| g.set(v)))
60 .build(),
61 )
62 .child(
63 View::slider()
64 .min(0.0)
65 .max(255.0)
66 .step(1.0)
67 .value(b.get())
68 .label(&format!("Blue: {:>3}", bv))
69 .color(Color::Rgb { r: 80, g: 80, b: 255 })
70 .on_change(with!(b => move |v: f64| b.set(v)))
71 .build(),
72 )
73 .child(
74 View::styled_text("████████████████")
75 .color(Color::Rgb { r: rv, g: gv, b: bv })
76 .bold()
77 .build(),
78 )
79 .child(View::styled_text(format!("#{:02X}{:02X}{:02X}", rv, gv, bv)).bold().build())
80 .child(View::styled_text("Tab: switch slider • Left/Right: adjust • F1: help • Ctrl+Q: quit").dim().build())
81 .child(
82 View::modal()
83 .visible(show_help.get())
84 .title("Example 35: Slider")
85 .on_dismiss(with!(show_help => move || show_help.set(false)))
86 .child(
87 View::vstack()
88 .child(View::styled_text("What you're seeing").bold().build())
89 .child(View::text("• Three sliders for R, G, B"))
90 .child(View::text("• Color preview swatch"))
91 .child(View::text("• Live hex code"))
92 .child(View::gap(1))
93 .child(View::styled_text("Key concepts").bold().build())
94 .child(View::text("• View::slider() with min/max/step"))
95 .child(View::text("• on_change callback with f64"))
96 .child(View::text("• Color::Rgb for true color"))
97 .child(View::gap(1))
98 .child(View::styled_text("Try this").bold().build())
99 .child(View::text("• Tab between sliders"))
100 .child(View::text("• Left/Right arrows to adjust"))
101 .child(View::text("• Watch the preview change"))
102 .child(View::gap(1))
103 .child(View::styled_text("Next up").bold().build())
104 .child(View::text("-> 36_reducer: state machine wizard"))
105 .child(View::gap(1))
106 .child(View::styled_text("Press Escape to close").dim().build())
107 .build(),
108 )
109 .build(),
110 )
111 .build()
112 }Sourcepub fn error_boundary() -> ErrorBoundaryBuilder
pub fn error_boundary() -> ErrorBoundaryBuilder
Create an error boundary builder.
An error boundary catches panics in its child view and displays a fallback view instead of crashing the application.
§Example
View::error_boundary()
.child(risky_component_view)
.fallback(View::text("Something went wrong"))
.build()Examples found in repository?
38 fn render(&self, cx: Scope) -> View {
39 let show_help = state!(cx, || false);
40
41 cx.use_command(
42 KeyBinding::key(KeyCode::F(1)),
43 with!(show_help => move || show_help.update(|v| *v = !*v)),
44 );
45
46 let count = state!(cx, || 0i32);
47
48 // The panic must happen at render time (inside render_view) so the
49 // error boundary's catch_unwind can catch it. A custom widget defers
50 // execution to the render pass.
51 let risky_view = View::custom(std::rc::Rc::new(std::cell::RefCell::new(RiskyCounter(count.get()))));
52
53 let fallback = View::vstack()
54 .child(View::styled_text("CAUGHT PANIC").color(Color::Red).bold().build())
55 .child(View::text("The child view panicked."))
56 .child(View::text("But the app is still running!"))
57 .build();
58
59 View::vstack()
60 .spacing(1)
61 .child(View::styled_text("Error Boundary Demo").bold().build())
62 .child(
63 View::hstack()
64 .spacing(2)
65 .child(
66 View::vstack()
67 .spacing(1)
68 .child(View::styled_text("Protected Panel").bold().build())
69 .child(
70 View::error_boundary()
71 .child(risky_view)
72 .fallback(fallback)
73 .build(),
74 )
75 .build(),
76 )
77 .child(
78 View::vstack()
79 .spacing(1)
80 .child(View::styled_text("How It Works").bold().build())
81 .child(View::text("The left panel asserts"))
82 .child(View::text("count < 5. When it hits"))
83 .child(View::text("5, the error boundary"))
84 .child(View::text("catches the panic and"))
85 .child(View::text("renders the fallback."))
86 .build(),
87 )
88 .build(),
89 )
90 .child(
91 View::hstack()
92 .spacing(1)
93 .child(
94 View::button()
95 .label("[ + Increment ]")
96 .on_press(with!(count => move || count.update(|n| *n += 1)))
97 .build(),
98 )
99 .child(
100 View::button()
101 .label("[ Reset to 0 ]")
102 .on_press(with!(count => move || count.set(0)))
103 .build(),
104 )
105 .child(View::styled_text(format!("count = {}", count.get())).dim().build())
106 .build(),
107 )
108 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
109 .child(
110 View::modal()
111 .visible(show_help.get())
112 .title("Example 37: Error Boundary")
113 .on_dismiss(with!(show_help => move || show_help.set(false)))
114 .child(
115 View::vstack()
116 .child(View::styled_text("What you're seeing").bold().build())
117 .child(View::text("• A counter that panics at 5"))
118 .child(View::text("• Error boundary catches the panic"))
119 .child(View::text("• Red fallback replaces the crash"))
120 .child(View::gap(1))
121 .child(View::styled_text("Key concepts").bold().build())
122 .child(View::text("• View::error_boundary()"))
123 .child(View::text("• .child(risky) .fallback(safe)"))
124 .child(View::text("• Panics are caught, not propagated"))
125 .child(View::text("• App keeps running after panic"))
126 .child(View::gap(1))
127 .child(View::styled_text("Try this").bold().build())
128 .child(View::text("• Increment to 5 to trigger panic"))
129 .child(View::text("• Reset to 0 to recover"))
130 .child(View::text("• Keep incrementing past 5"))
131 .child(View::gap(1))
132 .child(View::styled_text("Next up").bold().build())
133 .child(View::text("-> 38_custom_widget: Game of Life"))
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 }Sourcepub fn empty() -> Self
pub fn empty() -> Self
Create an empty view.
Examples found in repository?
36 fn render(&self, cx: Scope) -> View {
37 let show_help = state!(cx, || false);
38
39 cx.use_command(
40 KeyBinding::key(KeyCode::F(1)),
41 with!(show_help => move || show_help.update(|v| *v = !*v)),
42 );
43
44 let status = state!(cx, || "Idle".to_string());
45 let progress = state!(cx, || 0u8);
46 let result: State<Option<String>> = state!(cx, || None);
47 let running = state!(cx, || false);
48
49 // Bidirectional port: inbound TaskProgress, outbound TaskCommand
50 let port = port!(cx, TaskProgress, TaskCommand);
51
52 // Process incoming progress messages
53 for msg in port.rx.get() {
54 match msg {
55 TaskProgress::Started => {
56 status.set("Working...".to_string());
57 progress.set(0);
58 result.set(None);
59 running.set(true);
60 }
61 TaskProgress::Progress(pct) => {
62 status.set(format!("Progress: {}%", pct));
63 progress.set(pct);
64 }
65 TaskProgress::Done(data) => {
66 status.set("Done!".to_string());
67 progress.set(100);
68 result.set(Some(data));
69 running.set(false);
70 }
71 TaskProgress::Cancelled => {
72 status.set("Cancelled".to_string());
73 running.set(false);
74 }
75 }
76 }
77
78 // Spawn the worker thread on first render
79 let worker_started = state!(cx, || false);
80 if !worker_started.get() {
81 worker_started.set(true);
82 let tx_progress = port.rx.tx();
83 if let Some(rx_commands) = port.take_outbound_rx() {
84 std::thread::spawn(move || {
85 worker_loop(tx_progress, rx_commands);
86 });
87 }
88 }
89
90 let start_task = {
91 let tx = port.tx();
92 with!(running => move || {
93 if !running.get() {
94 let _ = tx.send(TaskCommand::Start);
95 }
96 })
97 };
98
99 let cancel_task = {
100 let tx = port.tx();
101 with!(running => move || {
102 if running.get() {
103 let _ = tx.send(TaskCommand::Cancel);
104 }
105 })
106 };
107
108 let pct = progress.get();
109 let bar_width = 30usize;
110 let filled = (pct as usize * bar_width) / 100;
111 let bar = format!(
112 "[{}{}] {}%",
113 "█".repeat(filled),
114 "░".repeat(bar_width - filled),
115 pct
116 );
117
118 View::vstack()
119 .spacing(1)
120 .child(View::styled_text("Port: Background Task Runner").bold().build())
121 .child(
122 View::hstack()
123 .spacing(1)
124 .child(View::styled_text("Status:").dim().build())
125 .child(View::styled_text(status.get())
126 .color(if running.get() { Color::Yellow } else if pct == 100 { Color::Green } else { Color::Reset })
127 .bold()
128 .build())
129 .build(),
130 )
131 .child(View::styled_text(&bar).color(
132 if pct == 100 { Color::Green }
133 else if pct > 50 { Color::Yellow }
134 else { Color::Cyan }
135 ).build())
136 .child(if let Some(data) = result.get() {
137 View::vstack()
138 .child(View::styled_text("Result:").dim().build())
139 .child(View::styled_text(format!(" {}", data)).color(Color::Green).build())
140 .build()
141 } else {
142 View::empty()
143 })
144 .child(
145 View::hstack()
146 .spacing(1)
147 .child(
148 View::button()
149 .label(if running.get() { "[ Running... ]" } else { "[ Start Task ]" })
150 .on_press(start_task)
151 .build(),
152 )
153 .child(
154 View::button()
155 .label("[ Cancel ]")
156 .on_press(cancel_task)
157 .build(),
158 )
159 .build(),
160 )
161 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
162 .child(
163 View::modal()
164 .visible(show_help.get())
165 .title("Example 39: Port")
166 .on_dismiss(with!(show_help => move || show_help.set(false)))
167 .child(
168 View::vstack()
169 .child(View::styled_text("What you're seeing").bold().build())
170 .child(View::text("• Background task with progress"))
171 .child(View::text("• Bidirectional communication"))
172 .child(View::text("• Start and cancel controls"))
173 .child(View::gap(1))
174 .child(View::styled_text("Key concepts").bold().build())
175 .child(View::text("• port!(cx, InType, OutType)"))
176 .child(View::text("• port.rx.tx() sends to UI"))
177 .child(View::text("• port.tx() sends to worker"))
178 .child(View::text("• port.take_outbound_rx() for worker"))
179 .child(View::text("• port.rx.get() reads this frame"))
180 .child(View::gap(1))
181 .child(View::styled_text("Try this").bold().build())
182 .child(View::text("• Start a task, watch progress"))
183 .child(View::text("• Cancel mid-way"))
184 .child(View::text("• Start another after completion"))
185 .child(View::gap(1))
186 .child(View::styled_text("Press Escape to close").dim().build())
187 .build(),
188 )
189 .build(),
190 )
191 .build()
192 }Sourcepub fn is_focusable(&self) -> bool
pub fn is_focusable(&self) -> bool
Check if this view is focusable.
Sourcepub fn min_height(&self) -> Option<u16>
pub fn min_height(&self) -> Option<u16>
Get the minimum height constraint, if any.
Sourcepub fn max_height(&self) -> Option<u16>
pub fn max_height(&self) -> Option<u16>
Get the maximum height constraint, if any.
Sourcepub fn intrinsic_height(&self) -> Option<u16>
pub fn intrinsic_height(&self) -> Option<u16>
Calculate the intrinsic (natural) height of this view based on its content. Returns None for views that have no intrinsic height (flexible).
Sourcepub fn intrinsic_width(&self) -> Option<u16>
pub fn intrinsic_width(&self) -> Option<u16>
Calculate the intrinsic (natural) width of this view based on its content. Returns None for views that have no intrinsic width (flexible).
Trait Implementations§
Auto Trait Implementations§
impl Freeze for View
impl !RefUnwindSafe for View
impl !Send for View
impl !Sync for View
impl Unpin for View
impl !UnwindSafe for View
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
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
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>
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>
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)
&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)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.