pub struct TextInputBuilder { /* private fields */ }Expand description
Builder for TextInput views.
Implementations§
Source§impl TextInputBuilder
impl TextInputBuilder
pub fn new() -> Self
Sourcepub fn value(self, value: impl Into<String>) -> Self
pub fn value(self, value: impl Into<String>) -> Self
Examples found in repository?
examples/05_todo_list.rs (line 82)
20 fn render(&self, cx: Scope) -> View {
21 let items = state!(cx, || {
22 vec![
23 "Learn Telex".to_string(),
24 "Build something cool".to_string(),
25 ]
26 });
27 let input_value = state!(cx, String::new);
28 let selected = state!(cx, || 0usize);
29 let show_help = state!(cx, || false);
30
31 // F1 toggles help
32 cx.use_command(
33 KeyBinding::key(KeyCode::F(1)),
34 with!(show_help => move || show_help.update(|v| *v = !*v)),
35 );
36
37 // Add new item on submit
38 let on_submit = with!(items, input_value => move || {
39 let text = input_value.get();
40 if !text.is_empty() {
41 items.update(|v| v.push(text));
42 input_value.set(String::new());
43 }
44 });
45
46 // Handle input changes
47 let on_change = with!(input_value => move |text: String| {
48 input_value.set(text);
49 });
50
51 // Delete selected item
52 let on_delete = with!(items, selected => move || {
53 let idx = selected.get();
54 items.update(|v| {
55 if idx < v.len() {
56 v.remove(idx);
57 // Adjust selection if needed
58 if idx > 0 && idx >= v.len() {
59 selected.set(idx - 1);
60 }
61 }
62 });
63 });
64
65 // Track selection
66 let on_select = with!(selected => move |idx: usize| {
67 selected.set(idx);
68 });
69
70 let item_count = items.get().len();
71
72 View::vstack()
73 .child(
74 View::styled_text("Todo List")
75 .color(Color::Cyan)
76 .bold()
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::text_input()
82 .value(input_value.get())
83 .placeholder("Type something to add...")
84 .on_change(on_change)
85 .on_submit(on_submit)
86 .build(),
87 )
88 .child(View::gap(1))
89 .child(if item_count > 0 {
90 View::list()
91 .items(items.get())
92 .selected(selected.get())
93 .on_select(on_select)
94 .build()
95 } else {
96 View::styled_text("No items yet").dim().build()
97 })
98 .child(View::gap(1))
99 .child(
100 View::hstack()
101 .child(View::button().label("Delete").on_press(on_delete).build())
102 .build(),
103 )
104 .child(View::gap(1))
105 .child(
106 View::styled_text("Tab navigate • Enter add/select • F1 help • Ctrl+Q quit")
107 .dim()
108 .build(),
109 )
110 .child(
111 View::modal()
112 .visible(show_help.get())
113 .title("Example 05: Todo List")
114 .on_dismiss(with!(show_help => move || show_help.set(false)))
115 .child(
116 View::vstack()
117 .child(View::styled_text("What you're seeing").bold().build())
118 .child(View::text("• View::text_input() for text entry"))
119 .child(View::text("• View::list() for displaying items"))
120 .child(View::text("• on_submit callback for Enter key"))
121 .child(View::gap(1))
122 .child(View::styled_text("Key concepts").bold().build())
123 .child(View::text("• Controlled input: value + on_change"))
124 .child(View::text("• Vec<String> state for list items"))
125 .child(View::text("• Conditional rendering (empty state)"))
126 .child(View::gap(1))
127 .child(View::styled_text("Try this").bold().build())
128 .child(View::text("• Type something and press Enter to add"))
129 .child(View::text("• Use ↑/↓ to select, then Delete button"))
130 .child(View::text("• Delete all items to see empty state"))
131 .child(View::gap(1))
132 .child(View::styled_text("Next up").bold().build())
133 .child(View::text("→ 06_log_viewer: streaming text content"))
134 .child(View::gap(1))
135 .child(View::styled_text("Press Escape to close").dim().build())
136 .build(),
137 )
138 .build(),
139 )
140 .build()
141 }More examples
examples/32_effects.rs (line 92)
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/36_reducer.rs (line 105)
38 fn render(&self, cx: Scope) -> View {
39 let show_help = state!(cx, || false);
40
41 cx.use_command(
42 KeyBinding::key(KeyCode::F(1)),
43 with!(show_help => move || show_help.update(|v| *v = !*v)),
44 );
45
46 let (wizard, dispatch) = reducer!(cx, WizardState::Welcome, |state: WizardState, action: WizardAction| {
47 match (state, action) {
48 (_, WizardAction::Reset) => WizardState::Welcome,
49 (WizardState::Welcome, WizardAction::Next) => WizardState::Name(String::new()),
50 (WizardState::Name(_), WizardAction::SetName(n)) => WizardState::Name(n),
51 (WizardState::Name(name), WizardAction::Next) => {
52 let name = if name.is_empty() { "Anonymous".to_string() } else { name };
53 WizardState::Color(name, String::new())
54 }
55 (WizardState::Name(_), WizardAction::Back) => WizardState::Welcome,
56 (WizardState::Color(name, _), WizardAction::SetColor(c)) => WizardState::Color(name, c),
57 (WizardState::Color(name, color), WizardAction::Next) => {
58 let color = if color.is_empty() { "Blue".to_string() } else { color };
59 WizardState::Done(name, color)
60 }
61 (WizardState::Color(_, _), WizardAction::Back) => WizardState::Name(String::new()),
62 (WizardState::Done(_, _), WizardAction::Back) => WizardState::Welcome,
63 (s, _) => s,
64 }
65 });
66
67 let step = match &wizard.get() {
68 WizardState::Welcome => 1,
69 WizardState::Name(_) => 2,
70 WizardState::Color(_, _) => 3,
71 WizardState::Done(_, _) => 4,
72 };
73
74 let progress = format!("Step {} of 4", step);
75 let dots: String = (1..=4)
76 .map(|i| if i <= step { "●" } else { "○" })
77 .collect::<Vec<_>>()
78 .join(" ");
79
80 let content = match &wizard.get() {
81 WizardState::Welcome => {
82 let d = dispatch.clone();
83 View::vstack()
84 .spacing(1)
85 .child(View::styled_text("Welcome to the Wizard!").color(Color::Cyan).bold().build())
86 .child(View::text("This example shows centralized state"))
87 .child(View::text("management with reducer!"))
88 .child(
89 View::button()
90 .label("[ Start -> ]")
91 .on_press(move || d(WizardAction::Next))
92 .build(),
93 )
94 .build()
95 }
96 WizardState::Name(name) => {
97 let d1 = dispatch.clone();
98 let d2 = dispatch.clone();
99 let d3 = dispatch.clone();
100 View::vstack()
101 .spacing(1)
102 .child(View::styled_text("What's your name?").color(Color::Cyan).bold().build())
103 .child(
104 View::text_input()
105 .value(name.clone())
106 .placeholder("Enter your name...")
107 .on_change(move |s: String| d1(WizardAction::SetName(s)))
108 .build(),
109 )
110 .child(
111 View::hstack()
112 .spacing(1)
113 .child(
114 View::button()
115 .label("[ <- Back ]")
116 .on_press(move || d2(WizardAction::Back))
117 .build(),
118 )
119 .child(
120 View::button()
121 .label("[ Next -> ]")
122 .on_press(move || d3(WizardAction::Next))
123 .build(),
124 )
125 .build(),
126 )
127 .build()
128 }
129 WizardState::Color(name, color) => {
130 let d1 = dispatch.clone();
131 let d2 = dispatch.clone();
132 let d3 = dispatch.clone();
133 View::vstack()
134 .spacing(1)
135 .child(View::styled_text(format!("Hi, {}! Pick a color:", name)).color(Color::Cyan).bold().build())
136 .child(
137 View::text_input()
138 .value(color.clone())
139 .placeholder("Enter a color (e.g. Blue)...")
140 .on_change(move |s: String| d1(WizardAction::SetColor(s)))
141 .build(),
142 )
143 .child(
144 View::hstack()
145 .spacing(1)
146 .child(
147 View::button()
148 .label("[ <- Back ]")
149 .on_press(move || d2(WizardAction::Back))
150 .build(),
151 )
152 .child(
153 View::button()
154 .label("[ Finish -> ]")
155 .on_press(move || d3(WizardAction::Next))
156 .build(),
157 )
158 .build(),
159 )
160 .build()
161 }
162 WizardState::Done(name, color) => {
163 let d = dispatch.clone();
164 View::vstack()
165 .spacing(1)
166 .child(View::styled_text("All done!").color(Color::Green).bold().build())
167 .child(View::text(format!("Name: {}", name)))
168 .child(View::text(format!("Color: {}", color)))
169 .child(
170 View::button()
171 .label("[ Start Over ]")
172 .on_press(move || d(WizardAction::Reset))
173 .build(),
174 )
175 .build()
176 }
177 };
178
179 View::vstack()
180 .spacing(1)
181 .child(View::styled_text("Reducer Wizard").bold().build())
182 .child(
183 View::hstack()
184 .spacing(1)
185 .child(View::styled_text(&progress).dim().build())
186 .child(View::styled_text(&dots).color(Color::Cyan).build())
187 .build(),
188 )
189 .child(View::styled_text("────────────────────────").dim().build())
190 .child(content)
191 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
192 .child(
193 View::modal()
194 .visible(show_help.get())
195 .title("Example 36: Reducer")
196 .on_dismiss(with!(show_help => move || show_help.set(false)))
197 .child(
198 View::vstack()
199 .child(View::styled_text("What you're seeing").bold().build())
200 .child(View::text("• Multi-step wizard state machine"))
201 .child(View::text("• All transitions in one reducer fn"))
202 .child(View::text("• No scattered booleans"))
203 .child(View::gap(1))
204 .child(View::styled_text("Key concepts").bold().build())
205 .child(View::text("• reducer!(cx, init, |state, action| ...)"))
206 .child(View::text("• Returns (State<S>, Rc<dyn Fn(A)>)"))
207 .child(View::text("• dispatch(action) to transition"))
208 .child(View::text("• Pattern match (state, action) pairs"))
209 .child(View::gap(1))
210 .child(View::styled_text("Try this").bold().build())
211 .child(View::text("• Walk through all 4 steps"))
212 .child(View::text("• Go back and change answers"))
213 .child(View::text("• Watch the progress dots"))
214 .child(View::gap(1))
215 .child(View::styled_text("Next up").bold().build())
216 .child(View::text("-> 37_error_boundary: crash protection"))
217 .child(View::gap(1))
218 .child(View::styled_text("Press Escape to close").dim().build())
219 .build(),
220 )
221 .build(),
222 )
223 .build()
224 }examples/23_modal.rs (line 262)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Modal visibility states
31 let show_confirm = state!(cx, || false);
32 let show_alert = state!(cx, || false);
33 let show_custom = state!(cx, || false);
34
35 // App state that modals can modify
36 let deleted_count = state!(cx, || 0);
37 let custom_input = state!(cx, String::new);
38 let last_action = state!(cx, || "No action yet".to_string());
39
40 // Handlers
41 let open_confirm = with!(show_confirm => move || {
42 show_confirm.set(true);
43 });
44
45 let open_alert = with!(show_alert => move || {
46 show_alert.set(true);
47 });
48
49 let open_custom = with!(show_custom => move || {
50 show_custom.set(true);
51 });
52
53 let on_confirm_yes = with!(show_confirm, deleted_count, last_action => move || {
54 deleted_count.set(deleted_count.get() + 1);
55 last_action.set(format!("Deleted item #{}", deleted_count.get()));
56 show_confirm.set(false);
57 });
58
59 let on_confirm_no = with!(show_confirm, last_action => move || {
60 last_action.set("Cancelled delete".to_string());
61 show_confirm.set(false);
62 });
63
64 let on_alert_dismiss = with!(show_alert, last_action => move || {
65 last_action.set("Dismissed alert".to_string());
66 show_alert.set(false);
67 });
68
69 let on_custom_save = with!(show_custom, custom_input, last_action => move || {
70 let value = custom_input.get();
71 if !value.is_empty() {
72 last_action.set(format!("Saved: {}", value));
73 custom_input.set(String::new());
74 }
75 show_custom.set(false);
76 });
77
78 let on_custom_dismiss = with!(show_custom => move || {
79 show_custom.set(false);
80 });
81
82 View::vstack()
83 .spacing(1)
84 .child(
85 // Header
86 View::boxed()
87 .border(true)
88 .padding(1)
89 .child(
90 View::vstack()
91 .child(View::styled_text("Modal Dialogs Demo").bold().build())
92 .child(
93 View::styled_text("Click buttons to open different modal types")
94 .dim()
95 .build(),
96 )
97 .build(),
98 )
99 .build(),
100 )
101 .child(
102 // Main content
103 View::boxed()
104 .flex(1)
105 .border(true)
106 .padding(1)
107 .child(
108 View::vstack()
109 .spacing(1)
110 .child(View::text("Modal Types:"))
111 .child(
112 View::hstack()
113 .spacing(2)
114 .child(
115 View::button()
116 .label("Confirm Dialog")
117 .on_press(open_confirm)
118 .build(),
119 )
120 .child(
121 View::button()
122 .label("Alert Dialog")
123 .on_press(open_alert)
124 .build(),
125 )
126 .child(
127 View::button()
128 .label("Custom Modal")
129 .on_press(open_custom)
130 .build(),
131 )
132 .build(),
133 )
134 .child(View::spacer())
135 .child(
136 View::styled_text(format!(
137 "Deleted items: {}",
138 deleted_count.get()
139 ))
140 .color(Color::Yellow)
141 .build(),
142 )
143 .child(
144 View::styled_text(format!("Last action: {}", last_action.get()))
145 .dim()
146 .build(),
147 )
148 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
149 .build(),
150 )
151 .build(),
152 )
153 // Help modal
154 .child(
155 View::modal()
156 .visible(show_help.get())
157 .title("Example 23: Modal")
158 .on_dismiss(with!(show_help => move || show_help.set(false)))
159 .child(
160 View::vstack()
161 .child(View::styled_text("What you're seeing").bold().build())
162 .child(View::text("• Confirm, alert, and custom modals"))
163 .child(View::text("• Modal focus containment"))
164 .child(View::text("• Escape to dismiss"))
165 .child(View::gap(1))
166 .child(View::styled_text("Key concepts").bold().build())
167 .child(View::text("• View::modal() creates overlay"))
168 .child(View::text("• .visible() controls show/hide"))
169 .child(View::text("• .on_dismiss() handles Escape"))
170 .child(View::text("• Focus trapped in open modal"))
171 .child(View::gap(1))
172 .child(View::styled_text("Try this").bold().build())
173 .child(View::text("• Open confirm, click Yes/No"))
174 .child(View::text("• Custom modal has text input"))
175 .child(View::text("• Press Escape to close modals"))
176 .child(View::gap(1))
177 .child(View::styled_text("Next up").bold().build())
178 .child(View::text("→ 24_async_data: async loading"))
179 .child(View::gap(1))
180 .child(View::styled_text("Press Escape to close").dim().build())
181 .build(),
182 )
183 .build(),
184 )
185 // Confirm dialog modal
186 .child(
187 View::modal()
188 .visible(show_confirm.get())
189 .title("Confirm Delete")
190 .on_dismiss(on_confirm_no.clone())
191 .width(40)
192 .height(30)
193 .child(
194 View::vstack()
195 .spacing(1)
196 .child(View::text("Are you sure you want to delete this item?"))
197 .child(View::text("This action cannot be undone."))
198 .child(View::spacer())
199 .child(
200 View::hstack()
201 .spacing(2)
202 .child(
203 View::button()
204 .label("Yes, Delete")
205 .on_press(on_confirm_yes)
206 .build(),
207 )
208 .child(
209 View::button()
210 .label("Cancel")
211 .on_press(on_confirm_no)
212 .build(),
213 )
214 .build(),
215 )
216 .build(),
217 )
218 .build(),
219 )
220 // Alert dialog modal
221 .child(
222 View::modal()
223 .visible(show_alert.get())
224 .title("Alert")
225 .on_dismiss(on_alert_dismiss.clone())
226 .width(50)
227 .height(25)
228 .child(
229 View::vstack()
230 .spacing(1)
231 .child(
232 View::styled_text("Operation completed successfully!")
233 .color(Color::Green)
234 .build(),
235 )
236 .child(View::text("Your changes have been saved."))
237 .child(View::spacer())
238 .child(
239 View::button()
240 .label("OK")
241 .on_press(on_alert_dismiss)
242 .build(),
243 )
244 .build(),
245 )
246 .build(),
247 )
248 // Custom modal with input
249 .child(
250 View::modal()
251 .visible(show_custom.get())
252 .title("Enter Details")
253 .on_dismiss(on_custom_dismiss.clone())
254 .width(50)
255 .height(40)
256 .child(
257 View::vstack()
258 .spacing(1)
259 .child(View::text("Enter a value:"))
260 .child(
261 View::text_input()
262 .value(custom_input.get())
263 .placeholder("Type something...")
264 .on_change(with!(custom_input => move |v: String| {
265 custom_input.set(v);
266 }))
267 .build(),
268 )
269 .child(View::spacer())
270 .child(
271 View::hstack()
272 .spacing(2)
273 .child(
274 View::button()
275 .label("Save")
276 .on_press(on_custom_save)
277 .build(),
278 )
279 .child(
280 View::button()
281 .label("Cancel")
282 .on_press(on_custom_dismiss)
283 .build(),
284 )
285 .build(),
286 )
287 .build(),
288 )
289 .build(),
290 )
291 .build()
292 }Sourcepub fn placeholder(self, placeholder: impl Into<String>) -> Self
pub fn placeholder(self, placeholder: impl Into<String>) -> Self
Examples found in repository?
examples/05_todo_list.rs (line 83)
20 fn render(&self, cx: Scope) -> View {
21 let items = state!(cx, || {
22 vec![
23 "Learn Telex".to_string(),
24 "Build something cool".to_string(),
25 ]
26 });
27 let input_value = state!(cx, String::new);
28 let selected = state!(cx, || 0usize);
29 let show_help = state!(cx, || false);
30
31 // F1 toggles help
32 cx.use_command(
33 KeyBinding::key(KeyCode::F(1)),
34 with!(show_help => move || show_help.update(|v| *v = !*v)),
35 );
36
37 // Add new item on submit
38 let on_submit = with!(items, input_value => move || {
39 let text = input_value.get();
40 if !text.is_empty() {
41 items.update(|v| v.push(text));
42 input_value.set(String::new());
43 }
44 });
45
46 // Handle input changes
47 let on_change = with!(input_value => move |text: String| {
48 input_value.set(text);
49 });
50
51 // Delete selected item
52 let on_delete = with!(items, selected => move || {
53 let idx = selected.get();
54 items.update(|v| {
55 if idx < v.len() {
56 v.remove(idx);
57 // Adjust selection if needed
58 if idx > 0 && idx >= v.len() {
59 selected.set(idx - 1);
60 }
61 }
62 });
63 });
64
65 // Track selection
66 let on_select = with!(selected => move |idx: usize| {
67 selected.set(idx);
68 });
69
70 let item_count = items.get().len();
71
72 View::vstack()
73 .child(
74 View::styled_text("Todo List")
75 .color(Color::Cyan)
76 .bold()
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::text_input()
82 .value(input_value.get())
83 .placeholder("Type something to add...")
84 .on_change(on_change)
85 .on_submit(on_submit)
86 .build(),
87 )
88 .child(View::gap(1))
89 .child(if item_count > 0 {
90 View::list()
91 .items(items.get())
92 .selected(selected.get())
93 .on_select(on_select)
94 .build()
95 } else {
96 View::styled_text("No items yet").dim().build()
97 })
98 .child(View::gap(1))
99 .child(
100 View::hstack()
101 .child(View::button().label("Delete").on_press(on_delete).build())
102 .build(),
103 )
104 .child(View::gap(1))
105 .child(
106 View::styled_text("Tab navigate • Enter add/select • F1 help • Ctrl+Q quit")
107 .dim()
108 .build(),
109 )
110 .child(
111 View::modal()
112 .visible(show_help.get())
113 .title("Example 05: Todo List")
114 .on_dismiss(with!(show_help => move || show_help.set(false)))
115 .child(
116 View::vstack()
117 .child(View::styled_text("What you're seeing").bold().build())
118 .child(View::text("• View::text_input() for text entry"))
119 .child(View::text("• View::list() for displaying items"))
120 .child(View::text("• on_submit callback for Enter key"))
121 .child(View::gap(1))
122 .child(View::styled_text("Key concepts").bold().build())
123 .child(View::text("• Controlled input: value + on_change"))
124 .child(View::text("• Vec<String> state for list items"))
125 .child(View::text("• Conditional rendering (empty state)"))
126 .child(View::gap(1))
127 .child(View::styled_text("Try this").bold().build())
128 .child(View::text("• Type something and press Enter to add"))
129 .child(View::text("• Use ↑/↓ to select, then Delete button"))
130 .child(View::text("• Delete all items to see empty state"))
131 .child(View::gap(1))
132 .child(View::styled_text("Next up").bold().build())
133 .child(View::text("→ 06_log_viewer: streaming text content"))
134 .child(View::gap(1))
135 .child(View::styled_text("Press Escape to close").dim().build())
136 .build(),
137 )
138 .build(),
139 )
140 .build()
141 }More examples
examples/32_effects.rs (line 93)
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/36_reducer.rs (line 106)
38 fn render(&self, cx: Scope) -> View {
39 let show_help = state!(cx, || false);
40
41 cx.use_command(
42 KeyBinding::key(KeyCode::F(1)),
43 with!(show_help => move || show_help.update(|v| *v = !*v)),
44 );
45
46 let (wizard, dispatch) = reducer!(cx, WizardState::Welcome, |state: WizardState, action: WizardAction| {
47 match (state, action) {
48 (_, WizardAction::Reset) => WizardState::Welcome,
49 (WizardState::Welcome, WizardAction::Next) => WizardState::Name(String::new()),
50 (WizardState::Name(_), WizardAction::SetName(n)) => WizardState::Name(n),
51 (WizardState::Name(name), WizardAction::Next) => {
52 let name = if name.is_empty() { "Anonymous".to_string() } else { name };
53 WizardState::Color(name, String::new())
54 }
55 (WizardState::Name(_), WizardAction::Back) => WizardState::Welcome,
56 (WizardState::Color(name, _), WizardAction::SetColor(c)) => WizardState::Color(name, c),
57 (WizardState::Color(name, color), WizardAction::Next) => {
58 let color = if color.is_empty() { "Blue".to_string() } else { color };
59 WizardState::Done(name, color)
60 }
61 (WizardState::Color(_, _), WizardAction::Back) => WizardState::Name(String::new()),
62 (WizardState::Done(_, _), WizardAction::Back) => WizardState::Welcome,
63 (s, _) => s,
64 }
65 });
66
67 let step = match &wizard.get() {
68 WizardState::Welcome => 1,
69 WizardState::Name(_) => 2,
70 WizardState::Color(_, _) => 3,
71 WizardState::Done(_, _) => 4,
72 };
73
74 let progress = format!("Step {} of 4", step);
75 let dots: String = (1..=4)
76 .map(|i| if i <= step { "●" } else { "○" })
77 .collect::<Vec<_>>()
78 .join(" ");
79
80 let content = match &wizard.get() {
81 WizardState::Welcome => {
82 let d = dispatch.clone();
83 View::vstack()
84 .spacing(1)
85 .child(View::styled_text("Welcome to the Wizard!").color(Color::Cyan).bold().build())
86 .child(View::text("This example shows centralized state"))
87 .child(View::text("management with reducer!"))
88 .child(
89 View::button()
90 .label("[ Start -> ]")
91 .on_press(move || d(WizardAction::Next))
92 .build(),
93 )
94 .build()
95 }
96 WizardState::Name(name) => {
97 let d1 = dispatch.clone();
98 let d2 = dispatch.clone();
99 let d3 = dispatch.clone();
100 View::vstack()
101 .spacing(1)
102 .child(View::styled_text("What's your name?").color(Color::Cyan).bold().build())
103 .child(
104 View::text_input()
105 .value(name.clone())
106 .placeholder("Enter your name...")
107 .on_change(move |s: String| d1(WizardAction::SetName(s)))
108 .build(),
109 )
110 .child(
111 View::hstack()
112 .spacing(1)
113 .child(
114 View::button()
115 .label("[ <- Back ]")
116 .on_press(move || d2(WizardAction::Back))
117 .build(),
118 )
119 .child(
120 View::button()
121 .label("[ Next -> ]")
122 .on_press(move || d3(WizardAction::Next))
123 .build(),
124 )
125 .build(),
126 )
127 .build()
128 }
129 WizardState::Color(name, color) => {
130 let d1 = dispatch.clone();
131 let d2 = dispatch.clone();
132 let d3 = dispatch.clone();
133 View::vstack()
134 .spacing(1)
135 .child(View::styled_text(format!("Hi, {}! Pick a color:", name)).color(Color::Cyan).bold().build())
136 .child(
137 View::text_input()
138 .value(color.clone())
139 .placeholder("Enter a color (e.g. Blue)...")
140 .on_change(move |s: String| d1(WizardAction::SetColor(s)))
141 .build(),
142 )
143 .child(
144 View::hstack()
145 .spacing(1)
146 .child(
147 View::button()
148 .label("[ <- Back ]")
149 .on_press(move || d2(WizardAction::Back))
150 .build(),
151 )
152 .child(
153 View::button()
154 .label("[ Finish -> ]")
155 .on_press(move || d3(WizardAction::Next))
156 .build(),
157 )
158 .build(),
159 )
160 .build()
161 }
162 WizardState::Done(name, color) => {
163 let d = dispatch.clone();
164 View::vstack()
165 .spacing(1)
166 .child(View::styled_text("All done!").color(Color::Green).bold().build())
167 .child(View::text(format!("Name: {}", name)))
168 .child(View::text(format!("Color: {}", color)))
169 .child(
170 View::button()
171 .label("[ Start Over ]")
172 .on_press(move || d(WizardAction::Reset))
173 .build(),
174 )
175 .build()
176 }
177 };
178
179 View::vstack()
180 .spacing(1)
181 .child(View::styled_text("Reducer Wizard").bold().build())
182 .child(
183 View::hstack()
184 .spacing(1)
185 .child(View::styled_text(&progress).dim().build())
186 .child(View::styled_text(&dots).color(Color::Cyan).build())
187 .build(),
188 )
189 .child(View::styled_text("────────────────────────").dim().build())
190 .child(content)
191 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
192 .child(
193 View::modal()
194 .visible(show_help.get())
195 .title("Example 36: Reducer")
196 .on_dismiss(with!(show_help => move || show_help.set(false)))
197 .child(
198 View::vstack()
199 .child(View::styled_text("What you're seeing").bold().build())
200 .child(View::text("• Multi-step wizard state machine"))
201 .child(View::text("• All transitions in one reducer fn"))
202 .child(View::text("• No scattered booleans"))
203 .child(View::gap(1))
204 .child(View::styled_text("Key concepts").bold().build())
205 .child(View::text("• reducer!(cx, init, |state, action| ...)"))
206 .child(View::text("• Returns (State<S>, Rc<dyn Fn(A)>)"))
207 .child(View::text("• dispatch(action) to transition"))
208 .child(View::text("• Pattern match (state, action) pairs"))
209 .child(View::gap(1))
210 .child(View::styled_text("Try this").bold().build())
211 .child(View::text("• Walk through all 4 steps"))
212 .child(View::text("• Go back and change answers"))
213 .child(View::text("• Watch the progress dots"))
214 .child(View::gap(1))
215 .child(View::styled_text("Next up").bold().build())
216 .child(View::text("-> 37_error_boundary: crash protection"))
217 .child(View::gap(1))
218 .child(View::styled_text("Press Escape to close").dim().build())
219 .build(),
220 )
221 .build(),
222 )
223 .build()
224 }examples/23_modal.rs (line 263)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Modal visibility states
31 let show_confirm = state!(cx, || false);
32 let show_alert = state!(cx, || false);
33 let show_custom = state!(cx, || false);
34
35 // App state that modals can modify
36 let deleted_count = state!(cx, || 0);
37 let custom_input = state!(cx, String::new);
38 let last_action = state!(cx, || "No action yet".to_string());
39
40 // Handlers
41 let open_confirm = with!(show_confirm => move || {
42 show_confirm.set(true);
43 });
44
45 let open_alert = with!(show_alert => move || {
46 show_alert.set(true);
47 });
48
49 let open_custom = with!(show_custom => move || {
50 show_custom.set(true);
51 });
52
53 let on_confirm_yes = with!(show_confirm, deleted_count, last_action => move || {
54 deleted_count.set(deleted_count.get() + 1);
55 last_action.set(format!("Deleted item #{}", deleted_count.get()));
56 show_confirm.set(false);
57 });
58
59 let on_confirm_no = with!(show_confirm, last_action => move || {
60 last_action.set("Cancelled delete".to_string());
61 show_confirm.set(false);
62 });
63
64 let on_alert_dismiss = with!(show_alert, last_action => move || {
65 last_action.set("Dismissed alert".to_string());
66 show_alert.set(false);
67 });
68
69 let on_custom_save = with!(show_custom, custom_input, last_action => move || {
70 let value = custom_input.get();
71 if !value.is_empty() {
72 last_action.set(format!("Saved: {}", value));
73 custom_input.set(String::new());
74 }
75 show_custom.set(false);
76 });
77
78 let on_custom_dismiss = with!(show_custom => move || {
79 show_custom.set(false);
80 });
81
82 View::vstack()
83 .spacing(1)
84 .child(
85 // Header
86 View::boxed()
87 .border(true)
88 .padding(1)
89 .child(
90 View::vstack()
91 .child(View::styled_text("Modal Dialogs Demo").bold().build())
92 .child(
93 View::styled_text("Click buttons to open different modal types")
94 .dim()
95 .build(),
96 )
97 .build(),
98 )
99 .build(),
100 )
101 .child(
102 // Main content
103 View::boxed()
104 .flex(1)
105 .border(true)
106 .padding(1)
107 .child(
108 View::vstack()
109 .spacing(1)
110 .child(View::text("Modal Types:"))
111 .child(
112 View::hstack()
113 .spacing(2)
114 .child(
115 View::button()
116 .label("Confirm Dialog")
117 .on_press(open_confirm)
118 .build(),
119 )
120 .child(
121 View::button()
122 .label("Alert Dialog")
123 .on_press(open_alert)
124 .build(),
125 )
126 .child(
127 View::button()
128 .label("Custom Modal")
129 .on_press(open_custom)
130 .build(),
131 )
132 .build(),
133 )
134 .child(View::spacer())
135 .child(
136 View::styled_text(format!(
137 "Deleted items: {}",
138 deleted_count.get()
139 ))
140 .color(Color::Yellow)
141 .build(),
142 )
143 .child(
144 View::styled_text(format!("Last action: {}", last_action.get()))
145 .dim()
146 .build(),
147 )
148 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
149 .build(),
150 )
151 .build(),
152 )
153 // Help modal
154 .child(
155 View::modal()
156 .visible(show_help.get())
157 .title("Example 23: Modal")
158 .on_dismiss(with!(show_help => move || show_help.set(false)))
159 .child(
160 View::vstack()
161 .child(View::styled_text("What you're seeing").bold().build())
162 .child(View::text("• Confirm, alert, and custom modals"))
163 .child(View::text("• Modal focus containment"))
164 .child(View::text("• Escape to dismiss"))
165 .child(View::gap(1))
166 .child(View::styled_text("Key concepts").bold().build())
167 .child(View::text("• View::modal() creates overlay"))
168 .child(View::text("• .visible() controls show/hide"))
169 .child(View::text("• .on_dismiss() handles Escape"))
170 .child(View::text("• Focus trapped in open modal"))
171 .child(View::gap(1))
172 .child(View::styled_text("Try this").bold().build())
173 .child(View::text("• Open confirm, click Yes/No"))
174 .child(View::text("• Custom modal has text input"))
175 .child(View::text("• Press Escape to close modals"))
176 .child(View::gap(1))
177 .child(View::styled_text("Next up").bold().build())
178 .child(View::text("→ 24_async_data: async loading"))
179 .child(View::gap(1))
180 .child(View::styled_text("Press Escape to close").dim().build())
181 .build(),
182 )
183 .build(),
184 )
185 // Confirm dialog modal
186 .child(
187 View::modal()
188 .visible(show_confirm.get())
189 .title("Confirm Delete")
190 .on_dismiss(on_confirm_no.clone())
191 .width(40)
192 .height(30)
193 .child(
194 View::vstack()
195 .spacing(1)
196 .child(View::text("Are you sure you want to delete this item?"))
197 .child(View::text("This action cannot be undone."))
198 .child(View::spacer())
199 .child(
200 View::hstack()
201 .spacing(2)
202 .child(
203 View::button()
204 .label("Yes, Delete")
205 .on_press(on_confirm_yes)
206 .build(),
207 )
208 .child(
209 View::button()
210 .label("Cancel")
211 .on_press(on_confirm_no)
212 .build(),
213 )
214 .build(),
215 )
216 .build(),
217 )
218 .build(),
219 )
220 // Alert dialog modal
221 .child(
222 View::modal()
223 .visible(show_alert.get())
224 .title("Alert")
225 .on_dismiss(on_alert_dismiss.clone())
226 .width(50)
227 .height(25)
228 .child(
229 View::vstack()
230 .spacing(1)
231 .child(
232 View::styled_text("Operation completed successfully!")
233 .color(Color::Green)
234 .build(),
235 )
236 .child(View::text("Your changes have been saved."))
237 .child(View::spacer())
238 .child(
239 View::button()
240 .label("OK")
241 .on_press(on_alert_dismiss)
242 .build(),
243 )
244 .build(),
245 )
246 .build(),
247 )
248 // Custom modal with input
249 .child(
250 View::modal()
251 .visible(show_custom.get())
252 .title("Enter Details")
253 .on_dismiss(on_custom_dismiss.clone())
254 .width(50)
255 .height(40)
256 .child(
257 View::vstack()
258 .spacing(1)
259 .child(View::text("Enter a value:"))
260 .child(
261 View::text_input()
262 .value(custom_input.get())
263 .placeholder("Type something...")
264 .on_change(with!(custom_input => move |v: String| {
265 custom_input.set(v);
266 }))
267 .build(),
268 )
269 .child(View::spacer())
270 .child(
271 View::hstack()
272 .spacing(2)
273 .child(
274 View::button()
275 .label("Save")
276 .on_press(on_custom_save)
277 .build(),
278 )
279 .child(
280 View::button()
281 .label("Cancel")
282 .on_press(on_custom_dismiss)
283 .build(),
284 )
285 .build(),
286 )
287 .build(),
288 )
289 .build(),
290 )
291 .build()
292 }Sourcepub fn on_change(self, callback: impl Fn(String) + 'static) -> Self
pub fn on_change(self, callback: impl Fn(String) + 'static) -> Self
Examples found in repository?
examples/05_todo_list.rs (line 84)
20 fn render(&self, cx: Scope) -> View {
21 let items = state!(cx, || {
22 vec![
23 "Learn Telex".to_string(),
24 "Build something cool".to_string(),
25 ]
26 });
27 let input_value = state!(cx, String::new);
28 let selected = state!(cx, || 0usize);
29 let show_help = state!(cx, || false);
30
31 // F1 toggles help
32 cx.use_command(
33 KeyBinding::key(KeyCode::F(1)),
34 with!(show_help => move || show_help.update(|v| *v = !*v)),
35 );
36
37 // Add new item on submit
38 let on_submit = with!(items, input_value => move || {
39 let text = input_value.get();
40 if !text.is_empty() {
41 items.update(|v| v.push(text));
42 input_value.set(String::new());
43 }
44 });
45
46 // Handle input changes
47 let on_change = with!(input_value => move |text: String| {
48 input_value.set(text);
49 });
50
51 // Delete selected item
52 let on_delete = with!(items, selected => move || {
53 let idx = selected.get();
54 items.update(|v| {
55 if idx < v.len() {
56 v.remove(idx);
57 // Adjust selection if needed
58 if idx > 0 && idx >= v.len() {
59 selected.set(idx - 1);
60 }
61 }
62 });
63 });
64
65 // Track selection
66 let on_select = with!(selected => move |idx: usize| {
67 selected.set(idx);
68 });
69
70 let item_count = items.get().len();
71
72 View::vstack()
73 .child(
74 View::styled_text("Todo List")
75 .color(Color::Cyan)
76 .bold()
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::text_input()
82 .value(input_value.get())
83 .placeholder("Type something to add...")
84 .on_change(on_change)
85 .on_submit(on_submit)
86 .build(),
87 )
88 .child(View::gap(1))
89 .child(if item_count > 0 {
90 View::list()
91 .items(items.get())
92 .selected(selected.get())
93 .on_select(on_select)
94 .build()
95 } else {
96 View::styled_text("No items yet").dim().build()
97 })
98 .child(View::gap(1))
99 .child(
100 View::hstack()
101 .child(View::button().label("Delete").on_press(on_delete).build())
102 .build(),
103 )
104 .child(View::gap(1))
105 .child(
106 View::styled_text("Tab navigate • Enter add/select • F1 help • Ctrl+Q quit")
107 .dim()
108 .build(),
109 )
110 .child(
111 View::modal()
112 .visible(show_help.get())
113 .title("Example 05: Todo List")
114 .on_dismiss(with!(show_help => move || show_help.set(false)))
115 .child(
116 View::vstack()
117 .child(View::styled_text("What you're seeing").bold().build())
118 .child(View::text("• View::text_input() for text entry"))
119 .child(View::text("• View::list() for displaying items"))
120 .child(View::text("• on_submit callback for Enter key"))
121 .child(View::gap(1))
122 .child(View::styled_text("Key concepts").bold().build())
123 .child(View::text("• Controlled input: value + on_change"))
124 .child(View::text("• Vec<String> state for list items"))
125 .child(View::text("• Conditional rendering (empty state)"))
126 .child(View::gap(1))
127 .child(View::styled_text("Try this").bold().build())
128 .child(View::text("• Type something and press Enter to add"))
129 .child(View::text("• Use ↑/↓ to select, then Delete button"))
130 .child(View::text("• Delete all items to see empty state"))
131 .child(View::gap(1))
132 .child(View::styled_text("Next up").bold().build())
133 .child(View::text("→ 06_log_viewer: streaming text content"))
134 .child(View::gap(1))
135 .child(View::styled_text("Press Escape to close").dim().build())
136 .build(),
137 )
138 .build(),
139 )
140 .build()
141 }More examples
examples/32_effects.rs (line 94)
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/36_reducer.rs (line 107)
38 fn render(&self, cx: Scope) -> View {
39 let show_help = state!(cx, || false);
40
41 cx.use_command(
42 KeyBinding::key(KeyCode::F(1)),
43 with!(show_help => move || show_help.update(|v| *v = !*v)),
44 );
45
46 let (wizard, dispatch) = reducer!(cx, WizardState::Welcome, |state: WizardState, action: WizardAction| {
47 match (state, action) {
48 (_, WizardAction::Reset) => WizardState::Welcome,
49 (WizardState::Welcome, WizardAction::Next) => WizardState::Name(String::new()),
50 (WizardState::Name(_), WizardAction::SetName(n)) => WizardState::Name(n),
51 (WizardState::Name(name), WizardAction::Next) => {
52 let name = if name.is_empty() { "Anonymous".to_string() } else { name };
53 WizardState::Color(name, String::new())
54 }
55 (WizardState::Name(_), WizardAction::Back) => WizardState::Welcome,
56 (WizardState::Color(name, _), WizardAction::SetColor(c)) => WizardState::Color(name, c),
57 (WizardState::Color(name, color), WizardAction::Next) => {
58 let color = if color.is_empty() { "Blue".to_string() } else { color };
59 WizardState::Done(name, color)
60 }
61 (WizardState::Color(_, _), WizardAction::Back) => WizardState::Name(String::new()),
62 (WizardState::Done(_, _), WizardAction::Back) => WizardState::Welcome,
63 (s, _) => s,
64 }
65 });
66
67 let step = match &wizard.get() {
68 WizardState::Welcome => 1,
69 WizardState::Name(_) => 2,
70 WizardState::Color(_, _) => 3,
71 WizardState::Done(_, _) => 4,
72 };
73
74 let progress = format!("Step {} of 4", step);
75 let dots: String = (1..=4)
76 .map(|i| if i <= step { "●" } else { "○" })
77 .collect::<Vec<_>>()
78 .join(" ");
79
80 let content = match &wizard.get() {
81 WizardState::Welcome => {
82 let d = dispatch.clone();
83 View::vstack()
84 .spacing(1)
85 .child(View::styled_text("Welcome to the Wizard!").color(Color::Cyan).bold().build())
86 .child(View::text("This example shows centralized state"))
87 .child(View::text("management with reducer!"))
88 .child(
89 View::button()
90 .label("[ Start -> ]")
91 .on_press(move || d(WizardAction::Next))
92 .build(),
93 )
94 .build()
95 }
96 WizardState::Name(name) => {
97 let d1 = dispatch.clone();
98 let d2 = dispatch.clone();
99 let d3 = dispatch.clone();
100 View::vstack()
101 .spacing(1)
102 .child(View::styled_text("What's your name?").color(Color::Cyan).bold().build())
103 .child(
104 View::text_input()
105 .value(name.clone())
106 .placeholder("Enter your name...")
107 .on_change(move |s: String| d1(WizardAction::SetName(s)))
108 .build(),
109 )
110 .child(
111 View::hstack()
112 .spacing(1)
113 .child(
114 View::button()
115 .label("[ <- Back ]")
116 .on_press(move || d2(WizardAction::Back))
117 .build(),
118 )
119 .child(
120 View::button()
121 .label("[ Next -> ]")
122 .on_press(move || d3(WizardAction::Next))
123 .build(),
124 )
125 .build(),
126 )
127 .build()
128 }
129 WizardState::Color(name, color) => {
130 let d1 = dispatch.clone();
131 let d2 = dispatch.clone();
132 let d3 = dispatch.clone();
133 View::vstack()
134 .spacing(1)
135 .child(View::styled_text(format!("Hi, {}! Pick a color:", name)).color(Color::Cyan).bold().build())
136 .child(
137 View::text_input()
138 .value(color.clone())
139 .placeholder("Enter a color (e.g. Blue)...")
140 .on_change(move |s: String| d1(WizardAction::SetColor(s)))
141 .build(),
142 )
143 .child(
144 View::hstack()
145 .spacing(1)
146 .child(
147 View::button()
148 .label("[ <- Back ]")
149 .on_press(move || d2(WizardAction::Back))
150 .build(),
151 )
152 .child(
153 View::button()
154 .label("[ Finish -> ]")
155 .on_press(move || d3(WizardAction::Next))
156 .build(),
157 )
158 .build(),
159 )
160 .build()
161 }
162 WizardState::Done(name, color) => {
163 let d = dispatch.clone();
164 View::vstack()
165 .spacing(1)
166 .child(View::styled_text("All done!").color(Color::Green).bold().build())
167 .child(View::text(format!("Name: {}", name)))
168 .child(View::text(format!("Color: {}", color)))
169 .child(
170 View::button()
171 .label("[ Start Over ]")
172 .on_press(move || d(WizardAction::Reset))
173 .build(),
174 )
175 .build()
176 }
177 };
178
179 View::vstack()
180 .spacing(1)
181 .child(View::styled_text("Reducer Wizard").bold().build())
182 .child(
183 View::hstack()
184 .spacing(1)
185 .child(View::styled_text(&progress).dim().build())
186 .child(View::styled_text(&dots).color(Color::Cyan).build())
187 .build(),
188 )
189 .child(View::styled_text("────────────────────────").dim().build())
190 .child(content)
191 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
192 .child(
193 View::modal()
194 .visible(show_help.get())
195 .title("Example 36: Reducer")
196 .on_dismiss(with!(show_help => move || show_help.set(false)))
197 .child(
198 View::vstack()
199 .child(View::styled_text("What you're seeing").bold().build())
200 .child(View::text("• Multi-step wizard state machine"))
201 .child(View::text("• All transitions in one reducer fn"))
202 .child(View::text("• No scattered booleans"))
203 .child(View::gap(1))
204 .child(View::styled_text("Key concepts").bold().build())
205 .child(View::text("• reducer!(cx, init, |state, action| ...)"))
206 .child(View::text("• Returns (State<S>, Rc<dyn Fn(A)>)"))
207 .child(View::text("• dispatch(action) to transition"))
208 .child(View::text("• Pattern match (state, action) pairs"))
209 .child(View::gap(1))
210 .child(View::styled_text("Try this").bold().build())
211 .child(View::text("• Walk through all 4 steps"))
212 .child(View::text("• Go back and change answers"))
213 .child(View::text("• Watch the progress dots"))
214 .child(View::gap(1))
215 .child(View::styled_text("Next up").bold().build())
216 .child(View::text("-> 37_error_boundary: crash protection"))
217 .child(View::gap(1))
218 .child(View::styled_text("Press Escape to close").dim().build())
219 .build(),
220 )
221 .build(),
222 )
223 .build()
224 }examples/23_modal.rs (lines 264-266)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Modal visibility states
31 let show_confirm = state!(cx, || false);
32 let show_alert = state!(cx, || false);
33 let show_custom = state!(cx, || false);
34
35 // App state that modals can modify
36 let deleted_count = state!(cx, || 0);
37 let custom_input = state!(cx, String::new);
38 let last_action = state!(cx, || "No action yet".to_string());
39
40 // Handlers
41 let open_confirm = with!(show_confirm => move || {
42 show_confirm.set(true);
43 });
44
45 let open_alert = with!(show_alert => move || {
46 show_alert.set(true);
47 });
48
49 let open_custom = with!(show_custom => move || {
50 show_custom.set(true);
51 });
52
53 let on_confirm_yes = with!(show_confirm, deleted_count, last_action => move || {
54 deleted_count.set(deleted_count.get() + 1);
55 last_action.set(format!("Deleted item #{}", deleted_count.get()));
56 show_confirm.set(false);
57 });
58
59 let on_confirm_no = with!(show_confirm, last_action => move || {
60 last_action.set("Cancelled delete".to_string());
61 show_confirm.set(false);
62 });
63
64 let on_alert_dismiss = with!(show_alert, last_action => move || {
65 last_action.set("Dismissed alert".to_string());
66 show_alert.set(false);
67 });
68
69 let on_custom_save = with!(show_custom, custom_input, last_action => move || {
70 let value = custom_input.get();
71 if !value.is_empty() {
72 last_action.set(format!("Saved: {}", value));
73 custom_input.set(String::new());
74 }
75 show_custom.set(false);
76 });
77
78 let on_custom_dismiss = with!(show_custom => move || {
79 show_custom.set(false);
80 });
81
82 View::vstack()
83 .spacing(1)
84 .child(
85 // Header
86 View::boxed()
87 .border(true)
88 .padding(1)
89 .child(
90 View::vstack()
91 .child(View::styled_text("Modal Dialogs Demo").bold().build())
92 .child(
93 View::styled_text("Click buttons to open different modal types")
94 .dim()
95 .build(),
96 )
97 .build(),
98 )
99 .build(),
100 )
101 .child(
102 // Main content
103 View::boxed()
104 .flex(1)
105 .border(true)
106 .padding(1)
107 .child(
108 View::vstack()
109 .spacing(1)
110 .child(View::text("Modal Types:"))
111 .child(
112 View::hstack()
113 .spacing(2)
114 .child(
115 View::button()
116 .label("Confirm Dialog")
117 .on_press(open_confirm)
118 .build(),
119 )
120 .child(
121 View::button()
122 .label("Alert Dialog")
123 .on_press(open_alert)
124 .build(),
125 )
126 .child(
127 View::button()
128 .label("Custom Modal")
129 .on_press(open_custom)
130 .build(),
131 )
132 .build(),
133 )
134 .child(View::spacer())
135 .child(
136 View::styled_text(format!(
137 "Deleted items: {}",
138 deleted_count.get()
139 ))
140 .color(Color::Yellow)
141 .build(),
142 )
143 .child(
144 View::styled_text(format!("Last action: {}", last_action.get()))
145 .dim()
146 .build(),
147 )
148 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
149 .build(),
150 )
151 .build(),
152 )
153 // Help modal
154 .child(
155 View::modal()
156 .visible(show_help.get())
157 .title("Example 23: Modal")
158 .on_dismiss(with!(show_help => move || show_help.set(false)))
159 .child(
160 View::vstack()
161 .child(View::styled_text("What you're seeing").bold().build())
162 .child(View::text("• Confirm, alert, and custom modals"))
163 .child(View::text("• Modal focus containment"))
164 .child(View::text("• Escape to dismiss"))
165 .child(View::gap(1))
166 .child(View::styled_text("Key concepts").bold().build())
167 .child(View::text("• View::modal() creates overlay"))
168 .child(View::text("• .visible() controls show/hide"))
169 .child(View::text("• .on_dismiss() handles Escape"))
170 .child(View::text("• Focus trapped in open modal"))
171 .child(View::gap(1))
172 .child(View::styled_text("Try this").bold().build())
173 .child(View::text("• Open confirm, click Yes/No"))
174 .child(View::text("• Custom modal has text input"))
175 .child(View::text("• Press Escape to close modals"))
176 .child(View::gap(1))
177 .child(View::styled_text("Next up").bold().build())
178 .child(View::text("→ 24_async_data: async loading"))
179 .child(View::gap(1))
180 .child(View::styled_text("Press Escape to close").dim().build())
181 .build(),
182 )
183 .build(),
184 )
185 // Confirm dialog modal
186 .child(
187 View::modal()
188 .visible(show_confirm.get())
189 .title("Confirm Delete")
190 .on_dismiss(on_confirm_no.clone())
191 .width(40)
192 .height(30)
193 .child(
194 View::vstack()
195 .spacing(1)
196 .child(View::text("Are you sure you want to delete this item?"))
197 .child(View::text("This action cannot be undone."))
198 .child(View::spacer())
199 .child(
200 View::hstack()
201 .spacing(2)
202 .child(
203 View::button()
204 .label("Yes, Delete")
205 .on_press(on_confirm_yes)
206 .build(),
207 )
208 .child(
209 View::button()
210 .label("Cancel")
211 .on_press(on_confirm_no)
212 .build(),
213 )
214 .build(),
215 )
216 .build(),
217 )
218 .build(),
219 )
220 // Alert dialog modal
221 .child(
222 View::modal()
223 .visible(show_alert.get())
224 .title("Alert")
225 .on_dismiss(on_alert_dismiss.clone())
226 .width(50)
227 .height(25)
228 .child(
229 View::vstack()
230 .spacing(1)
231 .child(
232 View::styled_text("Operation completed successfully!")
233 .color(Color::Green)
234 .build(),
235 )
236 .child(View::text("Your changes have been saved."))
237 .child(View::spacer())
238 .child(
239 View::button()
240 .label("OK")
241 .on_press(on_alert_dismiss)
242 .build(),
243 )
244 .build(),
245 )
246 .build(),
247 )
248 // Custom modal with input
249 .child(
250 View::modal()
251 .visible(show_custom.get())
252 .title("Enter Details")
253 .on_dismiss(on_custom_dismiss.clone())
254 .width(50)
255 .height(40)
256 .child(
257 View::vstack()
258 .spacing(1)
259 .child(View::text("Enter a value:"))
260 .child(
261 View::text_input()
262 .value(custom_input.get())
263 .placeholder("Type something...")
264 .on_change(with!(custom_input => move |v: String| {
265 custom_input.set(v);
266 }))
267 .build(),
268 )
269 .child(View::spacer())
270 .child(
271 View::hstack()
272 .spacing(2)
273 .child(
274 View::button()
275 .label("Save")
276 .on_press(on_custom_save)
277 .build(),
278 )
279 .child(
280 View::button()
281 .label("Cancel")
282 .on_press(on_custom_dismiss)
283 .build(),
284 )
285 .build(),
286 )
287 .build(),
288 )
289 .build(),
290 )
291 .build()
292 }pub fn on_cursor_change(self, callback: impl Fn(usize) + 'static) -> Self
Sourcepub fn on_submit(self, callback: impl Fn() + 'static) -> Self
pub fn on_submit(self, callback: impl Fn() + 'static) -> Self
Examples found in repository?
examples/05_todo_list.rs (line 85)
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 }Sourcepub fn on_key_up(self, callback: impl Fn() + 'static) -> Self
pub fn on_key_up(self, callback: impl Fn() + 'static) -> Self
Set callback for when Up arrow is pressed (e.g., for command history).
Sourcepub fn on_key_down(self, callback: impl Fn() + 'static) -> Self
pub fn on_key_down(self, callback: impl Fn() + 'static) -> Self
Set callback for when Down arrow is pressed (e.g., for command history).
pub fn cursor(self, pos: usize) -> Self
Sourcepub fn focused(self, focused: bool) -> Self
pub fn focused(self, focused: bool) -> Self
Set this input to have initial focus when the app starts.
Sourcepub fn build(self) -> View
pub fn build(self) -> View
Examples found in repository?
examples/05_todo_list.rs (line 86)
20 fn render(&self, cx: Scope) -> View {
21 let items = state!(cx, || {
22 vec![
23 "Learn Telex".to_string(),
24 "Build something cool".to_string(),
25 ]
26 });
27 let input_value = state!(cx, String::new);
28 let selected = state!(cx, || 0usize);
29 let show_help = state!(cx, || false);
30
31 // F1 toggles help
32 cx.use_command(
33 KeyBinding::key(KeyCode::F(1)),
34 with!(show_help => move || show_help.update(|v| *v = !*v)),
35 );
36
37 // Add new item on submit
38 let on_submit = with!(items, input_value => move || {
39 let text = input_value.get();
40 if !text.is_empty() {
41 items.update(|v| v.push(text));
42 input_value.set(String::new());
43 }
44 });
45
46 // Handle input changes
47 let on_change = with!(input_value => move |text: String| {
48 input_value.set(text);
49 });
50
51 // Delete selected item
52 let on_delete = with!(items, selected => move || {
53 let idx = selected.get();
54 items.update(|v| {
55 if idx < v.len() {
56 v.remove(idx);
57 // Adjust selection if needed
58 if idx > 0 && idx >= v.len() {
59 selected.set(idx - 1);
60 }
61 }
62 });
63 });
64
65 // Track selection
66 let on_select = with!(selected => move |idx: usize| {
67 selected.set(idx);
68 });
69
70 let item_count = items.get().len();
71
72 View::vstack()
73 .child(
74 View::styled_text("Todo List")
75 .color(Color::Cyan)
76 .bold()
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::text_input()
82 .value(input_value.get())
83 .placeholder("Type something to add...")
84 .on_change(on_change)
85 .on_submit(on_submit)
86 .build(),
87 )
88 .child(View::gap(1))
89 .child(if item_count > 0 {
90 View::list()
91 .items(items.get())
92 .selected(selected.get())
93 .on_select(on_select)
94 .build()
95 } else {
96 View::styled_text("No items yet").dim().build()
97 })
98 .child(View::gap(1))
99 .child(
100 View::hstack()
101 .child(View::button().label("Delete").on_press(on_delete).build())
102 .build(),
103 )
104 .child(View::gap(1))
105 .child(
106 View::styled_text("Tab navigate • Enter add/select • F1 help • Ctrl+Q quit")
107 .dim()
108 .build(),
109 )
110 .child(
111 View::modal()
112 .visible(show_help.get())
113 .title("Example 05: Todo List")
114 .on_dismiss(with!(show_help => move || show_help.set(false)))
115 .child(
116 View::vstack()
117 .child(View::styled_text("What you're seeing").bold().build())
118 .child(View::text("• View::text_input() for text entry"))
119 .child(View::text("• View::list() for displaying items"))
120 .child(View::text("• on_submit callback for Enter key"))
121 .child(View::gap(1))
122 .child(View::styled_text("Key concepts").bold().build())
123 .child(View::text("• Controlled input: value + on_change"))
124 .child(View::text("• Vec<String> state for list items"))
125 .child(View::text("• Conditional rendering (empty state)"))
126 .child(View::gap(1))
127 .child(View::styled_text("Try this").bold().build())
128 .child(View::text("• Type something and press Enter to add"))
129 .child(View::text("• Use ↑/↓ to select, then Delete button"))
130 .child(View::text("• Delete all items to see empty state"))
131 .child(View::gap(1))
132 .child(View::styled_text("Next up").bold().build())
133 .child(View::text("→ 06_log_viewer: streaming text content"))
134 .child(View::gap(1))
135 .child(View::styled_text("Press Escape to close").dim().build())
136 .build(),
137 )
138 .build(),
139 )
140 .build()
141 }More examples
examples/32_effects.rs (line 95)
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/36_reducer.rs (line 108)
38 fn render(&self, cx: Scope) -> View {
39 let show_help = state!(cx, || false);
40
41 cx.use_command(
42 KeyBinding::key(KeyCode::F(1)),
43 with!(show_help => move || show_help.update(|v| *v = !*v)),
44 );
45
46 let (wizard, dispatch) = reducer!(cx, WizardState::Welcome, |state: WizardState, action: WizardAction| {
47 match (state, action) {
48 (_, WizardAction::Reset) => WizardState::Welcome,
49 (WizardState::Welcome, WizardAction::Next) => WizardState::Name(String::new()),
50 (WizardState::Name(_), WizardAction::SetName(n)) => WizardState::Name(n),
51 (WizardState::Name(name), WizardAction::Next) => {
52 let name = if name.is_empty() { "Anonymous".to_string() } else { name };
53 WizardState::Color(name, String::new())
54 }
55 (WizardState::Name(_), WizardAction::Back) => WizardState::Welcome,
56 (WizardState::Color(name, _), WizardAction::SetColor(c)) => WizardState::Color(name, c),
57 (WizardState::Color(name, color), WizardAction::Next) => {
58 let color = if color.is_empty() { "Blue".to_string() } else { color };
59 WizardState::Done(name, color)
60 }
61 (WizardState::Color(_, _), WizardAction::Back) => WizardState::Name(String::new()),
62 (WizardState::Done(_, _), WizardAction::Back) => WizardState::Welcome,
63 (s, _) => s,
64 }
65 });
66
67 let step = match &wizard.get() {
68 WizardState::Welcome => 1,
69 WizardState::Name(_) => 2,
70 WizardState::Color(_, _) => 3,
71 WizardState::Done(_, _) => 4,
72 };
73
74 let progress = format!("Step {} of 4", step);
75 let dots: String = (1..=4)
76 .map(|i| if i <= step { "●" } else { "○" })
77 .collect::<Vec<_>>()
78 .join(" ");
79
80 let content = match &wizard.get() {
81 WizardState::Welcome => {
82 let d = dispatch.clone();
83 View::vstack()
84 .spacing(1)
85 .child(View::styled_text("Welcome to the Wizard!").color(Color::Cyan).bold().build())
86 .child(View::text("This example shows centralized state"))
87 .child(View::text("management with reducer!"))
88 .child(
89 View::button()
90 .label("[ Start -> ]")
91 .on_press(move || d(WizardAction::Next))
92 .build(),
93 )
94 .build()
95 }
96 WizardState::Name(name) => {
97 let d1 = dispatch.clone();
98 let d2 = dispatch.clone();
99 let d3 = dispatch.clone();
100 View::vstack()
101 .spacing(1)
102 .child(View::styled_text("What's your name?").color(Color::Cyan).bold().build())
103 .child(
104 View::text_input()
105 .value(name.clone())
106 .placeholder("Enter your name...")
107 .on_change(move |s: String| d1(WizardAction::SetName(s)))
108 .build(),
109 )
110 .child(
111 View::hstack()
112 .spacing(1)
113 .child(
114 View::button()
115 .label("[ <- Back ]")
116 .on_press(move || d2(WizardAction::Back))
117 .build(),
118 )
119 .child(
120 View::button()
121 .label("[ Next -> ]")
122 .on_press(move || d3(WizardAction::Next))
123 .build(),
124 )
125 .build(),
126 )
127 .build()
128 }
129 WizardState::Color(name, color) => {
130 let d1 = dispatch.clone();
131 let d2 = dispatch.clone();
132 let d3 = dispatch.clone();
133 View::vstack()
134 .spacing(1)
135 .child(View::styled_text(format!("Hi, {}! Pick a color:", name)).color(Color::Cyan).bold().build())
136 .child(
137 View::text_input()
138 .value(color.clone())
139 .placeholder("Enter a color (e.g. Blue)...")
140 .on_change(move |s: String| d1(WizardAction::SetColor(s)))
141 .build(),
142 )
143 .child(
144 View::hstack()
145 .spacing(1)
146 .child(
147 View::button()
148 .label("[ <- Back ]")
149 .on_press(move || d2(WizardAction::Back))
150 .build(),
151 )
152 .child(
153 View::button()
154 .label("[ Finish -> ]")
155 .on_press(move || d3(WizardAction::Next))
156 .build(),
157 )
158 .build(),
159 )
160 .build()
161 }
162 WizardState::Done(name, color) => {
163 let d = dispatch.clone();
164 View::vstack()
165 .spacing(1)
166 .child(View::styled_text("All done!").color(Color::Green).bold().build())
167 .child(View::text(format!("Name: {}", name)))
168 .child(View::text(format!("Color: {}", color)))
169 .child(
170 View::button()
171 .label("[ Start Over ]")
172 .on_press(move || d(WizardAction::Reset))
173 .build(),
174 )
175 .build()
176 }
177 };
178
179 View::vstack()
180 .spacing(1)
181 .child(View::styled_text("Reducer Wizard").bold().build())
182 .child(
183 View::hstack()
184 .spacing(1)
185 .child(View::styled_text(&progress).dim().build())
186 .child(View::styled_text(&dots).color(Color::Cyan).build())
187 .build(),
188 )
189 .child(View::styled_text("────────────────────────").dim().build())
190 .child(content)
191 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
192 .child(
193 View::modal()
194 .visible(show_help.get())
195 .title("Example 36: Reducer")
196 .on_dismiss(with!(show_help => move || show_help.set(false)))
197 .child(
198 View::vstack()
199 .child(View::styled_text("What you're seeing").bold().build())
200 .child(View::text("• Multi-step wizard state machine"))
201 .child(View::text("• All transitions in one reducer fn"))
202 .child(View::text("• No scattered booleans"))
203 .child(View::gap(1))
204 .child(View::styled_text("Key concepts").bold().build())
205 .child(View::text("• reducer!(cx, init, |state, action| ...)"))
206 .child(View::text("• Returns (State<S>, Rc<dyn Fn(A)>)"))
207 .child(View::text("• dispatch(action) to transition"))
208 .child(View::text("• Pattern match (state, action) pairs"))
209 .child(View::gap(1))
210 .child(View::styled_text("Try this").bold().build())
211 .child(View::text("• Walk through all 4 steps"))
212 .child(View::text("• Go back and change answers"))
213 .child(View::text("• Watch the progress dots"))
214 .child(View::gap(1))
215 .child(View::styled_text("Next up").bold().build())
216 .child(View::text("-> 37_error_boundary: crash protection"))
217 .child(View::gap(1))
218 .child(View::styled_text("Press Escape to close").dim().build())
219 .build(),
220 )
221 .build(),
222 )
223 .build()
224 }examples/23_modal.rs (line 267)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Modal visibility states
31 let show_confirm = state!(cx, || false);
32 let show_alert = state!(cx, || false);
33 let show_custom = state!(cx, || false);
34
35 // App state that modals can modify
36 let deleted_count = state!(cx, || 0);
37 let custom_input = state!(cx, String::new);
38 let last_action = state!(cx, || "No action yet".to_string());
39
40 // Handlers
41 let open_confirm = with!(show_confirm => move || {
42 show_confirm.set(true);
43 });
44
45 let open_alert = with!(show_alert => move || {
46 show_alert.set(true);
47 });
48
49 let open_custom = with!(show_custom => move || {
50 show_custom.set(true);
51 });
52
53 let on_confirm_yes = with!(show_confirm, deleted_count, last_action => move || {
54 deleted_count.set(deleted_count.get() + 1);
55 last_action.set(format!("Deleted item #{}", deleted_count.get()));
56 show_confirm.set(false);
57 });
58
59 let on_confirm_no = with!(show_confirm, last_action => move || {
60 last_action.set("Cancelled delete".to_string());
61 show_confirm.set(false);
62 });
63
64 let on_alert_dismiss = with!(show_alert, last_action => move || {
65 last_action.set("Dismissed alert".to_string());
66 show_alert.set(false);
67 });
68
69 let on_custom_save = with!(show_custom, custom_input, last_action => move || {
70 let value = custom_input.get();
71 if !value.is_empty() {
72 last_action.set(format!("Saved: {}", value));
73 custom_input.set(String::new());
74 }
75 show_custom.set(false);
76 });
77
78 let on_custom_dismiss = with!(show_custom => move || {
79 show_custom.set(false);
80 });
81
82 View::vstack()
83 .spacing(1)
84 .child(
85 // Header
86 View::boxed()
87 .border(true)
88 .padding(1)
89 .child(
90 View::vstack()
91 .child(View::styled_text("Modal Dialogs Demo").bold().build())
92 .child(
93 View::styled_text("Click buttons to open different modal types")
94 .dim()
95 .build(),
96 )
97 .build(),
98 )
99 .build(),
100 )
101 .child(
102 // Main content
103 View::boxed()
104 .flex(1)
105 .border(true)
106 .padding(1)
107 .child(
108 View::vstack()
109 .spacing(1)
110 .child(View::text("Modal Types:"))
111 .child(
112 View::hstack()
113 .spacing(2)
114 .child(
115 View::button()
116 .label("Confirm Dialog")
117 .on_press(open_confirm)
118 .build(),
119 )
120 .child(
121 View::button()
122 .label("Alert Dialog")
123 .on_press(open_alert)
124 .build(),
125 )
126 .child(
127 View::button()
128 .label("Custom Modal")
129 .on_press(open_custom)
130 .build(),
131 )
132 .build(),
133 )
134 .child(View::spacer())
135 .child(
136 View::styled_text(format!(
137 "Deleted items: {}",
138 deleted_count.get()
139 ))
140 .color(Color::Yellow)
141 .build(),
142 )
143 .child(
144 View::styled_text(format!("Last action: {}", last_action.get()))
145 .dim()
146 .build(),
147 )
148 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
149 .build(),
150 )
151 .build(),
152 )
153 // Help modal
154 .child(
155 View::modal()
156 .visible(show_help.get())
157 .title("Example 23: Modal")
158 .on_dismiss(with!(show_help => move || show_help.set(false)))
159 .child(
160 View::vstack()
161 .child(View::styled_text("What you're seeing").bold().build())
162 .child(View::text("• Confirm, alert, and custom modals"))
163 .child(View::text("• Modal focus containment"))
164 .child(View::text("• Escape to dismiss"))
165 .child(View::gap(1))
166 .child(View::styled_text("Key concepts").bold().build())
167 .child(View::text("• View::modal() creates overlay"))
168 .child(View::text("• .visible() controls show/hide"))
169 .child(View::text("• .on_dismiss() handles Escape"))
170 .child(View::text("• Focus trapped in open modal"))
171 .child(View::gap(1))
172 .child(View::styled_text("Try this").bold().build())
173 .child(View::text("• Open confirm, click Yes/No"))
174 .child(View::text("• Custom modal has text input"))
175 .child(View::text("• Press Escape to close modals"))
176 .child(View::gap(1))
177 .child(View::styled_text("Next up").bold().build())
178 .child(View::text("→ 24_async_data: async loading"))
179 .child(View::gap(1))
180 .child(View::styled_text("Press Escape to close").dim().build())
181 .build(),
182 )
183 .build(),
184 )
185 // Confirm dialog modal
186 .child(
187 View::modal()
188 .visible(show_confirm.get())
189 .title("Confirm Delete")
190 .on_dismiss(on_confirm_no.clone())
191 .width(40)
192 .height(30)
193 .child(
194 View::vstack()
195 .spacing(1)
196 .child(View::text("Are you sure you want to delete this item?"))
197 .child(View::text("This action cannot be undone."))
198 .child(View::spacer())
199 .child(
200 View::hstack()
201 .spacing(2)
202 .child(
203 View::button()
204 .label("Yes, Delete")
205 .on_press(on_confirm_yes)
206 .build(),
207 )
208 .child(
209 View::button()
210 .label("Cancel")
211 .on_press(on_confirm_no)
212 .build(),
213 )
214 .build(),
215 )
216 .build(),
217 )
218 .build(),
219 )
220 // Alert dialog modal
221 .child(
222 View::modal()
223 .visible(show_alert.get())
224 .title("Alert")
225 .on_dismiss(on_alert_dismiss.clone())
226 .width(50)
227 .height(25)
228 .child(
229 View::vstack()
230 .spacing(1)
231 .child(
232 View::styled_text("Operation completed successfully!")
233 .color(Color::Green)
234 .build(),
235 )
236 .child(View::text("Your changes have been saved."))
237 .child(View::spacer())
238 .child(
239 View::button()
240 .label("OK")
241 .on_press(on_alert_dismiss)
242 .build(),
243 )
244 .build(),
245 )
246 .build(),
247 )
248 // Custom modal with input
249 .child(
250 View::modal()
251 .visible(show_custom.get())
252 .title("Enter Details")
253 .on_dismiss(on_custom_dismiss.clone())
254 .width(50)
255 .height(40)
256 .child(
257 View::vstack()
258 .spacing(1)
259 .child(View::text("Enter a value:"))
260 .child(
261 View::text_input()
262 .value(custom_input.get())
263 .placeholder("Type something...")
264 .on_change(with!(custom_input => move |v: String| {
265 custom_input.set(v);
266 }))
267 .build(),
268 )
269 .child(View::spacer())
270 .child(
271 View::hstack()
272 .spacing(2)
273 .child(
274 View::button()
275 .label("Save")
276 .on_press(on_custom_save)
277 .build(),
278 )
279 .child(
280 View::button()
281 .label("Cancel")
282 .on_press(on_custom_dismiss)
283 .build(),
284 )
285 .build(),
286 )
287 .build(),
288 )
289 .build(),
290 )
291 .build()
292 }Trait Implementations§
Source§impl Default for TextInputBuilder
impl Default for TextInputBuilder
Source§fn default() -> TextInputBuilder
fn default() -> TextInputBuilder
Returns the “default value” for a type. Read more
Auto Trait Implementations§
impl Freeze for TextInputBuilder
impl !RefUnwindSafe for TextInputBuilder
impl !Send for TextInputBuilder
impl !Sync for TextInputBuilder
impl Unpin for TextInputBuilder
impl !UnwindSafe for TextInputBuilder
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.