Skip to main content

ButtonBuilder

Struct ButtonBuilder 

Source
pub struct ButtonBuilder { /* private fields */ }
Expand description

Builder for Button views.

Implementations§

Source§

impl ButtonBuilder

Source

pub fn new() -> Self

Source

pub fn label(self, label: impl Into<String>) -> Self

Examples found in repository?
examples/02_counter.rs (line 41)
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
Hide additional examples
examples/09_syntax_comparison.rs (line 82)
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/05_todo_list.rs (line 101)
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    }
examples/37_error_boundary.rs (line 95)
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    }
examples/38_custom_widget.rs (line 166)
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 (line 98)
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    }
Source

pub fn on_press(self, callback: impl Fn() + 'static) -> Self

Examples found in repository?
examples/02_counter.rs (line 42)
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
Hide additional examples
examples/09_syntax_comparison.rs (line 83)
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/05_todo_list.rs (line 101)
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    }
examples/37_error_boundary.rs (line 96)
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    }
examples/38_custom_widget.rs (lines 167-170)
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 (line 98)
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    }
Source

pub fn build(self) -> View

Examples found in repository?
examples/02_counter.rs (line 43)
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
Hide additional examples
examples/09_syntax_comparison.rs (line 84)
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/05_todo_list.rs (line 101)
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    }
examples/37_error_boundary.rs (line 97)
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    }
examples/38_custom_widget.rs (line 171)
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 (line 98)
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    }

Trait Implementations§

Source§

impl Default for ButtonBuilder

Source§

fn default() -> ButtonBuilder

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.