pub struct HStackBuilder { /* private fields */ }Expand description
Builder for HStack views.
Implementations§
Source§impl HStackBuilder
impl HStackBuilder
pub fn new() -> Self
Sourcepub fn child(self, view: View) -> Self
pub fn child(self, view: View) -> Self
Examples found in repository?
examples/04_timer.rs (line 53)
22 fn render(&self, cx: Scope) -> View {
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // Stream that yields elapsed seconds
32 let elapsed = stream!(cx, || {
33 (0u64..).inspect(|&s| {
34 if s > 0 {
35 std::thread::sleep(Duration::from_secs(1));
36 }
37 })
38 });
39
40 let seconds = elapsed.get();
41 let is_running = elapsed.is_loading();
42
43 // Format as MM:SS
44 let minutes = seconds / 60;
45 let secs = seconds % 60;
46 let time_display = format!("{:02}:{:02}", minutes, secs);
47
48 View::vstack()
49 .child(View::styled_text("Timer").color(Color::Cyan).bold().build())
50 .child(View::gap(1))
51 .child(
52 View::hstack()
53 .child(View::styled_text(&time_display).bold().build())
54 .child(if is_running {
55 View::styled_text(" ●").color(Color::Green).build()
56 } else {
57 View::styled_text(" ○").dim().build()
58 })
59 .build(),
60 )
61 .child(View::gap(1))
62 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
63 .child(
64 View::modal()
65 .visible(show_help.get())
66 .title("Example 04: Timer")
67 .on_dismiss(with!(show_help => move || show_help.set(false)))
68 .child(
69 View::vstack()
70 .child(View::styled_text("What you're seeing").bold().build())
71 .child(View::text("• stream!() macro for background data"))
72 .child(View::text("• Auto-updating UI without user input"))
73 .child(View::text("• Green dot = stream is running"))
74 .child(View::gap(1))
75 .child(View::styled_text("Key concepts").bold().build())
76 .child(View::text("• Streams run in background threads"))
77 .child(View::text("• Each yielded value triggers a re-render"))
78 .child(View::text("• is_loading() tells you if stream is active"))
79 .child(View::gap(1))
80 .child(View::styled_text("Try this").bold().build())
81 .child(View::text("• Just watch - the timer ticks automatically"))
82 .child(View::text("• No button presses needed for updates"))
83 .child(View::gap(1))
84 .child(View::styled_text("Next up").bold().build())
85 .child(View::text("→ 05_todo_list: text input and list management"))
86 .child(View::gap(1))
87 .child(View::styled_text("Press Escape to close").dim().build())
88 .build(),
89 )
90 .build(),
91 )
92 .build()
93 }More examples
examples/02_counter.rs (lines 39-44)
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 }examples/09_syntax_comparison.rs (line 58)
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 (lines 65-76)
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 151)
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 }Additional examples can be found in:
- examples/10_state_explained.rs
- examples/32_effects.rs
- examples/12_text_area.rs
- examples/06_log_viewer.rs
- examples/24_async_data.rs
- examples/34_channels_and_intervals.rs
- examples/08_system_monitor.rs
- examples/39_port.rs
- examples/25_context.rs
- examples/11_checkbox.rs
- examples/26_radio_buttons.rs
- examples/21_toasts.rs
- examples/36_reducer.rs
- examples/03_theme_switcher.rs
- examples/28_shared_state.rs
- examples/27_keyed_state.rs
- examples/22_forms.rs
- examples/23_modal.rs
Sourcepub fn spacing(self, spacing: u16) -> Self
pub fn spacing(self, spacing: u16) -> Self
Examples found in repository?
examples/37_error_boundary.rs (line 64)
38 fn render(&self, cx: Scope) -> View {
39 let show_help = state!(cx, || false);
40
41 cx.use_command(
42 KeyBinding::key(KeyCode::F(1)),
43 with!(show_help => move || show_help.update(|v| *v = !*v)),
44 );
45
46 let count = state!(cx, || 0i32);
47
48 // The panic must happen at render time (inside render_view) so the
49 // error boundary's catch_unwind can catch it. A custom widget defers
50 // execution to the render pass.
51 let risky_view = View::custom(std::rc::Rc::new(std::cell::RefCell::new(RiskyCounter(count.get()))));
52
53 let fallback = View::vstack()
54 .child(View::styled_text("CAUGHT PANIC").color(Color::Red).bold().build())
55 .child(View::text("The child view panicked."))
56 .child(View::text("But the app is still running!"))
57 .build();
58
59 View::vstack()
60 .spacing(1)
61 .child(View::styled_text("Error Boundary Demo").bold().build())
62 .child(
63 View::hstack()
64 .spacing(2)
65 .child(
66 View::vstack()
67 .spacing(1)
68 .child(View::styled_text("Protected Panel").bold().build())
69 .child(
70 View::error_boundary()
71 .child(risky_view)
72 .fallback(fallback)
73 .build(),
74 )
75 .build(),
76 )
77 .child(
78 View::vstack()
79 .spacing(1)
80 .child(View::styled_text("How It Works").bold().build())
81 .child(View::text("The left panel asserts"))
82 .child(View::text("count < 5. When it hits"))
83 .child(View::text("5, the error boundary"))
84 .child(View::text("catches the panic and"))
85 .child(View::text("renders the fallback."))
86 .build(),
87 )
88 .build(),
89 )
90 .child(
91 View::hstack()
92 .spacing(1)
93 .child(
94 View::button()
95 .label("[ + Increment ]")
96 .on_press(with!(count => move || count.update(|n| *n += 1)))
97 .build(),
98 )
99 .child(
100 View::button()
101 .label("[ Reset to 0 ]")
102 .on_press(with!(count => move || count.set(0)))
103 .build(),
104 )
105 .child(View::styled_text(format!("count = {}", count.get())).dim().build())
106 .build(),
107 )
108 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
109 .child(
110 View::modal()
111 .visible(show_help.get())
112 .title("Example 37: Error Boundary")
113 .on_dismiss(with!(show_help => move || show_help.set(false)))
114 .child(
115 View::vstack()
116 .child(View::styled_text("What you're seeing").bold().build())
117 .child(View::text("• A counter that panics at 5"))
118 .child(View::text("• Error boundary catches the panic"))
119 .child(View::text("• Red fallback replaces the crash"))
120 .child(View::gap(1))
121 .child(View::styled_text("Key concepts").bold().build())
122 .child(View::text("• View::error_boundary()"))
123 .child(View::text("• .child(risky) .fallback(safe)"))
124 .child(View::text("• Panics are caught, not propagated"))
125 .child(View::text("• App keeps running after panic"))
126 .child(View::gap(1))
127 .child(View::styled_text("Try this").bold().build())
128 .child(View::text("• Increment to 5 to trigger panic"))
129 .child(View::text("• Reset to 0 to recover"))
130 .child(View::text("• Keep incrementing past 5"))
131 .child(View::gap(1))
132 .child(View::styled_text("Next up").bold().build())
133 .child(View::text("-> 38_custom_widget: Game of Life"))
134 .child(View::gap(1))
135 .child(View::styled_text("Press Escape to close").dim().build())
136 .build(),
137 )
138 .build(),
139 )
140 .build()
141 }More examples
examples/38_custom_widget.rs (line 150)
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 85)
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 }examples/32_effects.rs (line 67)
23 fn render(&self, cx: Scope) -> View {
24 let show_help = state!(cx, || false);
25
26 // F1 toggles help
27 cx.use_command(
28 KeyBinding::key(KeyCode::F(1)),
29 with!(show_help => move || show_help.update(|v| *v = !*v)),
30 );
31
32 let count = state!(cx, || 0);
33 let name = state!(cx, String::new);
34 let last_effect = state!(cx, || String::from("(none yet)"));
35 let init_done = state!(cx, || false);
36
37 // Effect that runs only once - initialization
38 // Using effect_once! macro (order-independent)
39 effect_once!(cx, with!(init_done, last_effect => move || {
40 init_done.set(true);
41 last_effect.set("effect_once!: initialized!".to_string());
42 || {}
43 }));
44
45 // Effect that runs when count changes
46 // Using effect! macro (order-independent)
47 effect!(cx, count.get(), with!(last_effect => move |&val| {
48 last_effect.set(format!("effect!: count → {}", val));
49 || {}
50 }));
51
52 // Effect that runs when name changes
53 effect!(cx, name.get(), with!(last_effect => move |n: &String| {
54 if !n.is_empty() {
55 last_effect.set(format!("effect!: name → \"{}\"", n));
56 }
57 || {}
58 }));
59
60 View::vstack()
61 .spacing(1)
62 .child(View::styled_text("effect! Demo").bold().build())
63 .child(View::text(""))
64 .child(View::text(format!("Counter: {}", count.get())))
65 .child(
66 View::hstack()
67 .spacing(1)
68 .child(
69 View::button()
70 .label("[ - ]")
71 .on_press(with!(count => move || count.update(|n| *n -= 1)))
72 .build(),
73 )
74 .child(
75 View::button()
76 .label("[ + ]")
77 .on_press(with!(count => move || count.update(|n| *n += 1)))
78 .build(),
79 )
80 .build(),
81 )
82 .child(View::text(""))
83 .child({
84 let n = name.get();
85 View::text(format!(
86 "Name: {}",
87 if n.is_empty() { "(empty)" } else { &n }
88 ))
89 })
90 .child(
91 View::text_input()
92 .value(name.get())
93 .placeholder("Type your name...")
94 .on_change(with!(name => move |s| name.set(s)))
95 .build(),
96 )
97 .child(View::text(""))
98 .child(View::styled_text("─── Effect Status ───").dim().build())
99 .child(View::text(""))
100 .child(View::text(format!(
101 "Initialized: {}",
102 if init_done.get() { "✓ yes" } else { "no" }
103 )))
104 .child(View::text(format!("Last effect: {}", last_effect.get())))
105 .child(View::text(""))
106 .child(View::styled_text("─── How it works ───").dim().build())
107 .child(View::text(""))
108 .child(View::text("effect_once! → Ran once at startup"))
109 .child(View::text("effect! → Runs when deps change"))
110 .child(View::text(""))
111 .child(
112 View::styled_text("Press +/- or type to see effects trigger")
113 .dim()
114 .build(),
115 )
116 .child(View::text(""))
117 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
118 .child(
119 View::modal()
120 .visible(show_help.get())
121 .title("Example 32: Effects")
122 .on_dismiss(with!(show_help => move || show_help.set(false)))
123 .child(
124 View::vstack()
125 .child(View::styled_text("What you're seeing").bold().build())
126 .child(View::text("• effect_once! runs at startup"))
127 .child(View::text("• effect! runs when deps change"))
128 .child(View::text("• Last effect shows what triggered"))
129 .child(View::gap(1))
130 .child(View::styled_text("Key concepts").bold().build())
131 .child(View::text("• effect_once!(cx, || { cleanup })"))
132 .child(View::text("• effect!(cx, deps, |&d| { cleanup })"))
133 .child(View::text("• Return || {} for cleanup"))
134 .child(View::text("• Effects run AFTER render"))
135 .child(View::text("• Safe in conditionals!"))
136 .child(View::gap(1))
137 .child(View::styled_text("Try this").bold().build())
138 .child(View::text("• Click +/- to change counter"))
139 .child(View::text("• Type in the name field"))
140 .child(View::text("• Watch 'Last effect' update"))
141 .child(View::gap(1))
142 .child(View::styled_text("Press Escape to close").dim().build())
143 .build(),
144 )
145 .build(),
146 )
147 .build()
148 }examples/12_text_area.rs (line 111)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23 let theme_idx = state!(cx, || 0usize);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // F2 cycles through themes
32 cx.use_command(
33 KeyBinding::key(KeyCode::F(2)),
34 with!(theme_idx => move || {
35 let next = (theme_idx.get() + 1) % 6;
36 theme_idx.set(next);
37 let theme = match next {
38 0 => Theme::nord(),
39 1 => Theme::dark(),
40 2 => Theme::light(),
41 3 => Theme::dracula(),
42 4 => Theme::gruvbox_dark(),
43 _ => Theme::catppuccin_mocha(),
44 };
45 set_theme(theme);
46 }),
47 );
48
49 let content = state!(cx, String::new);
50 let cursor_line = state!(cx, || 0usize);
51 let cursor_col = state!(cx, || 0usize);
52
53 // Track changes and cursor position
54 let on_change = with!(content => move |text: String| {
55 content.set(text);
56 });
57
58 let on_cursor_change = with!(cursor_line, cursor_col => move |line: usize, col: usize| {
59 cursor_line.set(line);
60 cursor_col.set(col);
61 });
62
63 // Calculate stats
64 let text = content.get();
65 let line_count = if text.is_empty() {
66 0
67 } else {
68 text.lines().count()
69 };
70 let char_count = text.chars().count();
71 let word_count = text.split_whitespace().count();
72
73 let theme_name = match theme_idx.get() {
74 0 => "Nord",
75 1 => "Dark",
76 2 => "Light",
77 3 => "Dracula",
78 4 => "Gruvbox Dark",
79 _ => "Catppuccin Mocha",
80 };
81
82 View::vstack()
83 .spacing(1)
84 .child(
85 View::hstack()
86 .child(View::styled_text("Notes").color(Color::Cyan).bold().build())
87 .child(View::spacer())
88 .child(View::styled_text(format!("Theme: {}", theme_name)).dim().build())
89 .build(),
90 )
91 .child(
92 View::styled_text("A simple multi-line text editor")
93 .dim()
94 .build(),
95 )
96 .child(View::gap(1))
97 .child(
98 View::text_area()
99 .value(content.get())
100 .placeholder("Start typing your notes here...")
101 .rows(12)
102 .cursor_line(cursor_line.get())
103 .cursor_col(cursor_col.get())
104 .on_change(on_change)
105 .on_cursor_change(on_cursor_change)
106 .build(),
107 )
108 .child(View::gap(1))
109 .child(
110 View::hstack()
111 .spacing(3)
112 .child(
113 View::styled_text(format!("Lines: {}", line_count))
114 .color(Color::DarkGrey)
115 .build(),
116 )
117 .child(
118 View::styled_text(format!("Words: {}", word_count))
119 .color(Color::DarkGrey)
120 .build(),
121 )
122 .child(
123 View::styled_text(format!("Chars: {}", char_count))
124 .color(Color::DarkGrey)
125 .build(),
126 )
127 .build(),
128 )
129 .child(View::gap(1))
130 .child(View::styled_text("F1 help • F2 theme • Ctrl+Q quit").dim().build())
131 .child(
132 View::modal()
133 .visible(show_help.get())
134 .title("Example 12: TextArea")
135 .on_dismiss(with!(show_help => move || show_help.set(false)))
136 .child(
137 View::vstack()
138 .child(View::styled_text("What you're seeing").bold().build())
139 .child(View::text("• Multi-line text editing with TextArea"))
140 .child(View::text("• Real-time line/word/char counts"))
141 .child(View::text("• Cursor position tracking"))
142 .child(View::gap(1))
143 .child(View::styled_text("Key concepts").bold().build())
144 .child(View::text("• View::text_area() for multi-line input"))
145 .child(View::text("• on_change callback for text updates"))
146 .child(View::text("• on_cursor_change for cursor tracking"))
147 .child(View::text("• placeholder text when empty"))
148 .child(View::gap(1))
149 .child(View::styled_text("Try this").bold().build())
150 .child(View::text("• Type multiple lines of text"))
151 .child(View::text("• Watch the stats update in real-time"))
152 .child(View::text("• Use arrow keys to navigate"))
153 .child(View::gap(1))
154 .child(View::styled_text("Next up").bold().build())
155 .child(View::text("→ 13_split_panes: resizable panel layouts"))
156 .child(View::gap(1))
157 .child(View::styled_text("Press Escape to close").dim().build())
158 .build(),
159 )
160 .build(),
161 )
162 .build()
163 }examples/24_async_data.rs (line 97)
38 fn render(&self, cx: Scope) -> View {
39 let show_help = state!(cx, || false);
40
41 // F1 toggles help
42 cx.use_command(
43 KeyBinding::key(KeyCode::F(1)),
44 with!(show_help => move || show_help.update(|v| *v = !*v)),
45 );
46
47 // Simulate fetching user profile (slow - 2 seconds)
48 let profile = async_data!(cx, || {
49 thread::sleep(Duration::from_secs(2));
50 Ok(UserProfile {
51 name: "Alice Johnson".to_string(),
52 email: "alice@example.com".to_string(),
53 member_since: "January 2024".to_string(),
54 })
55 });
56
57 // Simulate fetching user stats (medium - 1 second)
58 let stats = async_data!(cx, || {
59 thread::sleep(Duration::from_secs(1));
60 Ok(Stats {
61 posts: 142,
62 followers: 1234,
63 following: 567,
64 })
65 });
66
67 // Simulate a failing request
68 let failing_data = async_data!(cx, || {
69 thread::sleep(Duration::from_millis(500));
70 Err::<String, _>("Network error: Connection refused".to_string())
71 });
72
73 View::vstack()
74 .spacing(1)
75 .child(
76 // Header
77 View::boxed()
78 .border(true)
79 .padding(1)
80 .child(
81 View::vstack()
82 .child(View::styled_text("Async Data Loading Demo").bold().build())
83 .child(
84 View::styled_text(
85 "Demonstrates use_async for loading data with loading/error states",
86 )
87 .dim()
88 .build(),
89 )
90 .build(),
91 )
92 .build(),
93 )
94 .child(
95 // Main content - three columns
96 View::hstack()
97 .spacing(1)
98 // Profile section
99 .child(
100 View::boxed()
101 .flex(1)
102 .border(true)
103 .padding(1)
104 .child(render_profile_section(&profile))
105 .build(),
106 )
107 // Stats section
108 .child(
109 View::boxed()
110 .flex(1)
111 .border(true)
112 .padding(1)
113 .child(render_stats_section(&stats))
114 .build(),
115 )
116 // Error section
117 .child(
118 View::boxed()
119 .flex(1)
120 .border(true)
121 .padding(1)
122 .child(render_error_section(&failing_data))
123 .build(),
124 )
125 .build(),
126 )
127 .child(
128 // Status footer
129 View::boxed()
130 .border(true)
131 .padding(1)
132 .child(
133 View::vstack()
134 .child(View::text(format!(
135 "Overall status: {}",
136 if profile.is_loading() || stats.is_loading() {
137 "Loading..."
138 } else if profile.is_error() || stats.is_error() || failing_data.is_error() {
139 "Some requests failed"
140 } else {
141 "All data loaded"
142 }
143 )))
144 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
145 .build(),
146 )
147 .build(),
148 )
149 .child(
150 View::modal()
151 .visible(show_help.get())
152 .title("Example 24: Async Data")
153 .on_dismiss(with!(show_help => move || show_help.set(false)))
154 .child(
155 View::vstack()
156 .child(View::styled_text("What you're seeing").bold().build())
157 .child(View::text("• Three async data loads in parallel"))
158 .child(View::text("• Loading, success, and error states"))
159 .child(View::text("• Different load times for each"))
160 .child(View::gap(1))
161 .child(View::styled_text("Key concepts").bold().build())
162 .child(View::text("• async_data!() macro runs in background"))
163 .child(View::text("• Returns Async<T> enum"))
164 .child(View::text("• .is_loading() / .is_error() helpers"))
165 .child(View::text("• Pattern match for state handling"))
166 .child(View::gap(1))
167 .child(View::styled_text("Try this").bold().build())
168 .child(View::text("• Watch data load progressively"))
169 .child(View::text("• Notice the failing request"))
170 .child(View::gap(1))
171 .child(View::styled_text("Next up").bold().build())
172 .child(View::text("→ 25_context: context API"))
173 .child(View::gap(1))
174 .child(View::styled_text("Press Escape to close").dim().build())
175 .build()
176 )
177 .build()
178 )
179 .build()
180 }Additional examples can be found in:
Sourcepub fn justify(self, justify: Justify) -> Self
pub fn justify(self, justify: Justify) -> Self
Set justify (main axis alignment for HStack = horizontal).
Sourcepub fn align(self, align: Align) -> Self
pub fn align(self, align: Align) -> Self
Set align (cross axis alignment for HStack = vertical).
Sourcepub fn layout_mode(self, mode: LayoutMode) -> Self
pub fn layout_mode(self, mode: LayoutMode) -> Self
Set layout mode (algorithm for distributing space).
Sourcepub fn build(self) -> View
pub fn build(self) -> View
Examples found in repository?
examples/04_timer.rs (line 59)
22 fn render(&self, cx: Scope) -> View {
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // Stream that yields elapsed seconds
32 let elapsed = stream!(cx, || {
33 (0u64..).inspect(|&s| {
34 if s > 0 {
35 std::thread::sleep(Duration::from_secs(1));
36 }
37 })
38 });
39
40 let seconds = elapsed.get();
41 let is_running = elapsed.is_loading();
42
43 // Format as MM:SS
44 let minutes = seconds / 60;
45 let secs = seconds % 60;
46 let time_display = format!("{:02}:{:02}", minutes, secs);
47
48 View::vstack()
49 .child(View::styled_text("Timer").color(Color::Cyan).bold().build())
50 .child(View::gap(1))
51 .child(
52 View::hstack()
53 .child(View::styled_text(&time_display).bold().build())
54 .child(if is_running {
55 View::styled_text(" ●").color(Color::Green).build()
56 } else {
57 View::styled_text(" ○").dim().build()
58 })
59 .build(),
60 )
61 .child(View::gap(1))
62 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
63 .child(
64 View::modal()
65 .visible(show_help.get())
66 .title("Example 04: Timer")
67 .on_dismiss(with!(show_help => move || show_help.set(false)))
68 .child(
69 View::vstack()
70 .child(View::styled_text("What you're seeing").bold().build())
71 .child(View::text("• stream!() macro for background data"))
72 .child(View::text("• Auto-updating UI without user input"))
73 .child(View::text("• Green dot = stream is running"))
74 .child(View::gap(1))
75 .child(View::styled_text("Key concepts").bold().build())
76 .child(View::text("• Streams run in background threads"))
77 .child(View::text("• Each yielded value triggers a re-render"))
78 .child(View::text("• is_loading() tells you if stream is active"))
79 .child(View::gap(1))
80 .child(View::styled_text("Try this").bold().build())
81 .child(View::text("• Just watch - the timer ticks automatically"))
82 .child(View::text("• No button presses needed for updates"))
83 .child(View::gap(1))
84 .child(View::styled_text("Next up").bold().build())
85 .child(View::text("→ 05_todo_list: text input and list management"))
86 .child(View::gap(1))
87 .child(View::styled_text("Press Escape to close").dim().build())
88 .build(),
89 )
90 .build(),
91 )
92 .build()
93 }More examples
examples/02_counter.rs (line 52)
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 }examples/09_syntax_comparison.rs (line 65)
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 102)
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 88)
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 158)
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 }Additional examples can be found in:
- examples/10_state_explained.rs
- examples/32_effects.rs
- examples/12_text_area.rs
- examples/06_log_viewer.rs
- examples/24_async_data.rs
- examples/34_channels_and_intervals.rs
- examples/08_system_monitor.rs
- examples/39_port.rs
- examples/25_context.rs
- examples/11_checkbox.rs
- examples/26_radio_buttons.rs
- examples/21_toasts.rs
- examples/36_reducer.rs
- examples/03_theme_switcher.rs
- examples/28_shared_state.rs
- examples/27_keyed_state.rs
- examples/22_forms.rs
- examples/23_modal.rs
Trait Implementations§
Source§impl Debug for HStackBuilder
impl Debug for HStackBuilder
Source§impl Default for HStackBuilder
impl Default for HStackBuilder
Source§fn default() -> HStackBuilder
fn default() -> HStackBuilder
Returns the “default value” for a type. Read more
Auto Trait Implementations§
impl Freeze for HStackBuilder
impl !RefUnwindSafe for HStackBuilder
impl !Send for HStackBuilder
impl !Sync for HStackBuilder
impl Unpin for HStackBuilder
impl !UnwindSafe for HStackBuilder
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Convert
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Convert
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Convert
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.