pub struct ButtonBuilder { /* private fields */ }Expand description
Builder for Button views.
Implementations§
Source§impl ButtonBuilder
impl ButtonBuilder
pub fn new() -> Self
Sourcepub fn label(self, label: impl Into<String>) -> Self
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
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/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 }examples/32_effects.rs (line 70)
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/25_context.rs (line 146)
71 fn render(&self, cx: Scope) -> View {
72 let show_help = state!(cx, || false);
73
74 // F1 toggles help
75 cx.use_command(
76 KeyBinding::key(KeyCode::F(1)),
77 with!(show_help => move || show_help.update(|v| *v = !*v)),
78 );
79
80 // State that we'll provide via context
81 let theme = state!(cx, || ColorTheme::Default);
82 let user = state!(cx, || User {
83 name: "Guest".to_string(),
84 logged_in: false,
85 });
86
87 // Provide static config via context
88 cx.provide_context(AppConfig {
89 app_name: "Context Demo".to_string(),
90 version: "1.0.0".to_string(),
91 });
92
93 // Provide dynamic state via context (current values)
94 cx.provide_context(theme.get());
95 cx.provide_context(user.get());
96
97 // Theme switching handlers
98 let set_default = with!(theme => move || theme.set(ColorTheme::Default));
99 let set_ocean = with!(theme => move || theme.set(ColorTheme::Ocean));
100 let set_forest = with!(theme => move || theme.set(ColorTheme::Forest));
101 let set_sunset = with!(theme => move || theme.set(ColorTheme::Sunset));
102
103 // Login/logout handlers
104 let toggle_login = with!(user => move || {
105 let current = user.get();
106 if current.logged_in {
107 user.set(User {
108 name: "Guest".to_string(),
109 logged_in: false,
110 });
111 } else {
112 user.set(User {
113 name: "Alice".to_string(),
114 logged_in: true,
115 });
116 }
117 });
118
119 // Get current theme for styling
120 let current_theme = theme.get();
121
122 View::vstack()
123 .spacing(1)
124 // Header - uses context
125 .child(render_header(&cx))
126 .child(
127 // Main content
128 View::boxed()
129 .flex(1)
130 .border(true)
131 .padding(1)
132 .child(
133 View::vstack()
134 .spacing(1)
135 .child(
136 View::styled_text("Theme Selection:")
137 .color(current_theme.primary())
138 .bold()
139 .build(),
140 )
141 .child(
142 View::hstack()
143 .spacing(2)
144 .child(
145 View::button()
146 .label("Default")
147 .on_press(set_default)
148 .build(),
149 )
150 .child(
151 View::button().label("Ocean").on_press(set_ocean).build(),
152 )
153 .child(
154 View::button().label("Forest").on_press(set_forest).build(),
155 )
156 .child(
157 View::button().label("Sunset").on_press(set_sunset).build(),
158 )
159 .build(),
160 )
161 .child(View::gap(1))
162 .child(
163 View::styled_text("User Actions:")
164 .color(current_theme.primary())
165 .bold()
166 .build(),
167 )
168 .child(
169 View::button()
170 .label(if user.get().logged_in {
171 "Logout"
172 } else {
173 "Login as Alice"
174 })
175 .on_press(toggle_login)
176 .build(),
177 )
178 .child(View::spacer())
179 // User info panel - uses context
180 .child(render_user_panel(&cx))
181 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
182 .build(),
183 )
184 .build(),
185 )
186 // Status bar - uses context
187 .child(render_status_bar(&cx))
188 .child(
189 View::modal()
190 .visible(show_help.get())
191 .title("Example 25: Context")
192 .on_dismiss(with!(show_help => move || show_help.set(false)))
193 .child(
194 View::vstack()
195 .child(View::styled_text("What you're seeing").bold().build())
196 .child(View::text("• Context API for global state"))
197 .child(View::text("• Theme colors propagate everywhere"))
198 .child(View::text("• User state shared across components"))
199 .child(View::gap(1))
200 .child(View::styled_text("Key concepts").bold().build())
201 .child(View::text("• cx.provide_context() adds to context"))
202 .child(View::text("• cx.use_context::<T>() reads context"))
203 .child(View::text("• Avoids prop drilling"))
204 .child(View::text("• Great for themes, user, config"))
205 .child(View::gap(1))
206 .child(View::styled_text("Try this").bold().build())
207 .child(View::text("• Switch themes - colors update"))
208 .child(View::text("• Login/logout - panel updates"))
209 .child(View::text("• Header and status bar read context"))
210 .child(View::gap(1))
211 .child(View::styled_text("Next up").bold().build())
212 .child(View::text("→ 26_radio_buttons: radio selections"))
213 .child(View::gap(1))
214 .child(View::styled_text("Press Escape to close").dim().build())
215 .build(),
216 )
217 .build(),
218 )
219 .build()
220 }Additional examples can be found in:
Sourcepub fn on_press(self, callback: impl Fn() + 'static) -> Self
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
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/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 }examples/32_effects.rs (line 71)
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/25_context.rs (line 147)
71 fn render(&self, cx: Scope) -> View {
72 let show_help = state!(cx, || false);
73
74 // F1 toggles help
75 cx.use_command(
76 KeyBinding::key(KeyCode::F(1)),
77 with!(show_help => move || show_help.update(|v| *v = !*v)),
78 );
79
80 // State that we'll provide via context
81 let theme = state!(cx, || ColorTheme::Default);
82 let user = state!(cx, || User {
83 name: "Guest".to_string(),
84 logged_in: false,
85 });
86
87 // Provide static config via context
88 cx.provide_context(AppConfig {
89 app_name: "Context Demo".to_string(),
90 version: "1.0.0".to_string(),
91 });
92
93 // Provide dynamic state via context (current values)
94 cx.provide_context(theme.get());
95 cx.provide_context(user.get());
96
97 // Theme switching handlers
98 let set_default = with!(theme => move || theme.set(ColorTheme::Default));
99 let set_ocean = with!(theme => move || theme.set(ColorTheme::Ocean));
100 let set_forest = with!(theme => move || theme.set(ColorTheme::Forest));
101 let set_sunset = with!(theme => move || theme.set(ColorTheme::Sunset));
102
103 // Login/logout handlers
104 let toggle_login = with!(user => move || {
105 let current = user.get();
106 if current.logged_in {
107 user.set(User {
108 name: "Guest".to_string(),
109 logged_in: false,
110 });
111 } else {
112 user.set(User {
113 name: "Alice".to_string(),
114 logged_in: true,
115 });
116 }
117 });
118
119 // Get current theme for styling
120 let current_theme = theme.get();
121
122 View::vstack()
123 .spacing(1)
124 // Header - uses context
125 .child(render_header(&cx))
126 .child(
127 // Main content
128 View::boxed()
129 .flex(1)
130 .border(true)
131 .padding(1)
132 .child(
133 View::vstack()
134 .spacing(1)
135 .child(
136 View::styled_text("Theme Selection:")
137 .color(current_theme.primary())
138 .bold()
139 .build(),
140 )
141 .child(
142 View::hstack()
143 .spacing(2)
144 .child(
145 View::button()
146 .label("Default")
147 .on_press(set_default)
148 .build(),
149 )
150 .child(
151 View::button().label("Ocean").on_press(set_ocean).build(),
152 )
153 .child(
154 View::button().label("Forest").on_press(set_forest).build(),
155 )
156 .child(
157 View::button().label("Sunset").on_press(set_sunset).build(),
158 )
159 .build(),
160 )
161 .child(View::gap(1))
162 .child(
163 View::styled_text("User Actions:")
164 .color(current_theme.primary())
165 .bold()
166 .build(),
167 )
168 .child(
169 View::button()
170 .label(if user.get().logged_in {
171 "Logout"
172 } else {
173 "Login as Alice"
174 })
175 .on_press(toggle_login)
176 .build(),
177 )
178 .child(View::spacer())
179 // User info panel - uses context
180 .child(render_user_panel(&cx))
181 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
182 .build(),
183 )
184 .build(),
185 )
186 // Status bar - uses context
187 .child(render_status_bar(&cx))
188 .child(
189 View::modal()
190 .visible(show_help.get())
191 .title("Example 25: Context")
192 .on_dismiss(with!(show_help => move || show_help.set(false)))
193 .child(
194 View::vstack()
195 .child(View::styled_text("What you're seeing").bold().build())
196 .child(View::text("• Context API for global state"))
197 .child(View::text("• Theme colors propagate everywhere"))
198 .child(View::text("• User state shared across components"))
199 .child(View::gap(1))
200 .child(View::styled_text("Key concepts").bold().build())
201 .child(View::text("• cx.provide_context() adds to context"))
202 .child(View::text("• cx.use_context::<T>() reads context"))
203 .child(View::text("• Avoids prop drilling"))
204 .child(View::text("• Great for themes, user, config"))
205 .child(View::gap(1))
206 .child(View::styled_text("Try this").bold().build())
207 .child(View::text("• Switch themes - colors update"))
208 .child(View::text("• Login/logout - panel updates"))
209 .child(View::text("• Header and status bar read context"))
210 .child(View::gap(1))
211 .child(View::styled_text("Next up").bold().build())
212 .child(View::text("→ 26_radio_buttons: radio selections"))
213 .child(View::gap(1))
214 .child(View::styled_text("Press Escape to close").dim().build())
215 .build(),
216 )
217 .build(),
218 )
219 .build()
220 }Additional examples can be found in:
Sourcepub fn build(self) -> View
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
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/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 }examples/32_effects.rs (line 72)
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/25_context.rs (line 148)
71 fn render(&self, cx: Scope) -> View {
72 let show_help = state!(cx, || false);
73
74 // F1 toggles help
75 cx.use_command(
76 KeyBinding::key(KeyCode::F(1)),
77 with!(show_help => move || show_help.update(|v| *v = !*v)),
78 );
79
80 // State that we'll provide via context
81 let theme = state!(cx, || ColorTheme::Default);
82 let user = state!(cx, || User {
83 name: "Guest".to_string(),
84 logged_in: false,
85 });
86
87 // Provide static config via context
88 cx.provide_context(AppConfig {
89 app_name: "Context Demo".to_string(),
90 version: "1.0.0".to_string(),
91 });
92
93 // Provide dynamic state via context (current values)
94 cx.provide_context(theme.get());
95 cx.provide_context(user.get());
96
97 // Theme switching handlers
98 let set_default = with!(theme => move || theme.set(ColorTheme::Default));
99 let set_ocean = with!(theme => move || theme.set(ColorTheme::Ocean));
100 let set_forest = with!(theme => move || theme.set(ColorTheme::Forest));
101 let set_sunset = with!(theme => move || theme.set(ColorTheme::Sunset));
102
103 // Login/logout handlers
104 let toggle_login = with!(user => move || {
105 let current = user.get();
106 if current.logged_in {
107 user.set(User {
108 name: "Guest".to_string(),
109 logged_in: false,
110 });
111 } else {
112 user.set(User {
113 name: "Alice".to_string(),
114 logged_in: true,
115 });
116 }
117 });
118
119 // Get current theme for styling
120 let current_theme = theme.get();
121
122 View::vstack()
123 .spacing(1)
124 // Header - uses context
125 .child(render_header(&cx))
126 .child(
127 // Main content
128 View::boxed()
129 .flex(1)
130 .border(true)
131 .padding(1)
132 .child(
133 View::vstack()
134 .spacing(1)
135 .child(
136 View::styled_text("Theme Selection:")
137 .color(current_theme.primary())
138 .bold()
139 .build(),
140 )
141 .child(
142 View::hstack()
143 .spacing(2)
144 .child(
145 View::button()
146 .label("Default")
147 .on_press(set_default)
148 .build(),
149 )
150 .child(
151 View::button().label("Ocean").on_press(set_ocean).build(),
152 )
153 .child(
154 View::button().label("Forest").on_press(set_forest).build(),
155 )
156 .child(
157 View::button().label("Sunset").on_press(set_sunset).build(),
158 )
159 .build(),
160 )
161 .child(View::gap(1))
162 .child(
163 View::styled_text("User Actions:")
164 .color(current_theme.primary())
165 .bold()
166 .build(),
167 )
168 .child(
169 View::button()
170 .label(if user.get().logged_in {
171 "Logout"
172 } else {
173 "Login as Alice"
174 })
175 .on_press(toggle_login)
176 .build(),
177 )
178 .child(View::spacer())
179 // User info panel - uses context
180 .child(render_user_panel(&cx))
181 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
182 .build(),
183 )
184 .build(),
185 )
186 // Status bar - uses context
187 .child(render_status_bar(&cx))
188 .child(
189 View::modal()
190 .visible(show_help.get())
191 .title("Example 25: Context")
192 .on_dismiss(with!(show_help => move || show_help.set(false)))
193 .child(
194 View::vstack()
195 .child(View::styled_text("What you're seeing").bold().build())
196 .child(View::text("• Context API for global state"))
197 .child(View::text("• Theme colors propagate everywhere"))
198 .child(View::text("• User state shared across components"))
199 .child(View::gap(1))
200 .child(View::styled_text("Key concepts").bold().build())
201 .child(View::text("• cx.provide_context() adds to context"))
202 .child(View::text("• cx.use_context::<T>() reads context"))
203 .child(View::text("• Avoids prop drilling"))
204 .child(View::text("• Great for themes, user, config"))
205 .child(View::gap(1))
206 .child(View::styled_text("Try this").bold().build())
207 .child(View::text("• Switch themes - colors update"))
208 .child(View::text("• Login/logout - panel updates"))
209 .child(View::text("• Header and status bar read context"))
210 .child(View::gap(1))
211 .child(View::styled_text("Next up").bold().build())
212 .child(View::text("→ 26_radio_buttons: radio selections"))
213 .child(View::gap(1))
214 .child(View::styled_text("Press Escape to close").dim().build())
215 .build(),
216 )
217 .build(),
218 )
219 .build()
220 }Additional examples can be found in:
Trait Implementations§
Source§impl Default for ButtonBuilder
impl Default for ButtonBuilder
Source§fn default() -> ButtonBuilder
fn default() -> ButtonBuilder
Returns the “default value” for a type. Read more
Auto Trait Implementations§
impl Freeze for ButtonBuilder
impl !RefUnwindSafe for ButtonBuilder
impl !Send for ButtonBuilder
impl !Sync for ButtonBuilder
impl Unpin for ButtonBuilder
impl !UnwindSafe for ButtonBuilder
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.