pub struct Scope { /* private fields */ }Expand description
Context passed to components during rendering.
Provides access to hooks like use_state.
Implementations§
Source§impl Scope
impl Scope
Sourcepub fn with_storage(storage: Rc<StateStorage>) -> Self
pub fn with_storage(storage: Rc<StateStorage>) -> Self
Create a scope with existing storage (for re-renders).
Sourcepub fn with_storage_and_commands(
storage: Rc<StateStorage>,
commands: Rc<CommandRegistry>,
) -> Self
pub fn with_storage_and_commands( storage: Rc<StateStorage>, commands: Rc<CommandRegistry>, ) -> Self
Create a scope with existing storage and command registry.
Sourcepub fn with_all(
storage: Rc<StateStorage>,
commands: Rc<CommandRegistry>,
context: Rc<ContextStorage>,
) -> Self
pub fn with_all( storage: Rc<StateStorage>, commands: Rc<CommandRegistry>, context: Rc<ContextStorage>, ) -> Self
Create a scope with all dependencies.
Sourcepub fn use_state_keyed<K: 'static, T: 'static>(
&self,
init: impl FnOnce() -> T,
) -> State<T>
pub fn use_state_keyed<K: 'static, T: 'static>( &self, init: impl FnOnce() -> T, ) -> State<T>
Create keyed state that persists across re-renders (order-independent).
Unlike use_state, this can be called conditionally or in any order.
The type K acts as the key - same K always returns the same state.
§Example
// Define a key type for shared state
struct CountKey;
fn Counter(cx: Scope) -> View {
// Safe to use in conditionals!
let count = cx.use_state_keyed::<CountKey, _>(|| 0);
// ...
}For independent state, prefer the state! macro which auto-generates the key:
let count = state!(cx, || 0);Examples found in repository?
29 fn render(&self, cx: Scope) -> View {
30 let show_help = state!(cx, || false);
31
32 // F1 toggles help
33 cx.use_command(
34 KeyBinding::key(KeyCode::F(1)),
35 with!(show_help => move || show_help.update(|v| *v = !*v)),
36 );
37
38 // Both panes will use the SAME key type = SAME state
39 // Note: using use_state_keyed directly with an explicit key type
40
41 // PANE A - gets the shared counter
42 let count_a = cx.use_state_keyed::<SharedCounterKey, _>(|| 0);
43 let inc_a = with!(count_a => move || count_a.update(|n| *n += 1));
44
45 // PANE B - uses the SAME key, so gets the SAME state!
46 let count_b = cx.use_state_keyed::<SharedCounterKey, _>(|| 0);
47 let inc_b = with!(count_b => move || count_b.update(|n| *n += 1));
48
49 View::vstack()
50 .spacing(1)
51 .child(
52 View::styled_text("Shared State Demo")
53 .color(Color::Cyan)
54 .bold()
55 .build(),
56 )
57 .child(View::gap(1))
58 .child(
59 View::hstack()
60 .spacing(2)
61 // Pane A
62 .child(
63 View::boxed()
64 .border(true)
65 .padding(1)
66 .max_width(25)
67 .child(
68 View::vstack()
69 .child(View::styled_text("Pane A").bold().build())
70 .child(View::gap(1))
71 .child(
72 View::hstack()
73 .spacing(1)
74 .child(View::text("Value:"))
75 .child(
76 View::styled_text(format!("{}", count_a.get()))
77 .color(Color::Yellow)
78 .bold()
79 .build(),
80 )
81 .child(View::button().label("+").on_press(inc_a).build())
82 .build(),
83 )
84 .build(),
85 )
86 .build(),
87 )
88 // Pane B
89 .child(
90 View::boxed()
91 .border(true)
92 .padding(1)
93 .max_width(25)
94 .child(
95 View::vstack()
96 .child(View::styled_text("Pane B").bold().build())
97 .child(View::gap(1))
98 .child(
99 View::hstack()
100 .spacing(1)
101 .child(View::text("Value:"))
102 .child(
103 View::styled_text(format!("{}", count_b.get()))
104 .color(Color::Yellow)
105 .bold()
106 .build(),
107 )
108 .child(View::button().label("+").on_press(inc_b).build())
109 .build(),
110 )
111 .build(),
112 )
113 .build(),
114 )
115 .build(),
116 )
117 .child(View::gap(1))
118 .child(View::styled_text("Try this:").bold().build())
119 .child(View::text(" 1. Click + on Pane A"))
120 .child(View::text(" 2. Watch Pane B update too!"))
121 .child(View::text(" 3. Click + on Pane B - same thing"))
122 .child(View::gap(1))
123 .child(
124 View::styled_text("Both panes share the SAME state.")
125 .color(Color::Green)
126 .build(),
127 )
128 .child(View::gap(1))
129 .child(
130 View::boxed()
131 .border(true)
132 .padding(1)
133 .child(
134 View::vstack()
135 .child(View::styled_text("The code:").bold().build())
136 .child(View::gap(1))
137 .child(
138 View::styled_text("struct SharedCounterKey; // Named key type")
139 .color(Color::Green)
140 .build(),
141 )
142 .child(View::gap(1))
143 .child(View::styled_text("// Pane A").color(Color::DarkGrey).build())
144 .child(
145 View::styled_text("let count_a = cx.use_state_keyed::<SharedCounterKey, _>(|| 0);")
146 .color(Color::Yellow)
147 .build(),
148 )
149 .child(View::gap(1))
150 .child(View::styled_text("// Pane B - SAME key type!").color(Color::DarkGrey).build())
151 .child(
152 View::styled_text("let count_b = cx.use_state_keyed::<SharedCounterKey, _>(|| 0);")
153 .color(Color::Yellow)
154 .build(),
155 )
156 .child(View::gap(1))
157 .child(View::text("Same key = same state. Both variables"))
158 .child(View::text("point to the same underlying value."))
159 .build(),
160 )
161 .build(),
162 )
163 .child(View::gap(1))
164 .child(
165 View::boxed()
166 .border(true)
167 .padding(1)
168 .child(
169 View::vstack()
170 .child(View::styled_text("Compare with example 27:").bold().build())
171 .child(View::gap(1))
172 .child(View::styled_text("state!(cx, || 0) // Anonymous key").color(Color::Magenta).build())
173 .child(View::text(" Each call = unique key = independent state"))
174 .child(View::gap(1))
175 .child(View::styled_text("cx.use_state_keyed::<MyKey, _>(|| 0) // Named key").color(Color::Yellow).build())
176 .child(View::text(" Same key = same state = shared everywhere"))
177 .build(),
178 )
179 .build(),
180 )
181 .child(View::gap(1))
182 .child(View::styled_text("Tab: navigate | F1 help | Ctrl+Q: quit").dim().build())
183 .child(
184 View::modal()
185 .visible(show_help.get())
186 .title("Example 28: Shared State")
187 .on_dismiss(with!(show_help => move || show_help.set(false)))
188 .child(
189 View::vstack()
190 .child(View::styled_text("What you're seeing").bold().build())
191 .child(View::text("• Two panes sharing ONE counter"))
192 .child(View::text("• Both + buttons increment same value"))
193 .child(View::text("• Named key type = shared state"))
194 .child(View::gap(1))
195 .child(View::styled_text("Key concepts").bold().build())
196 .child(View::text("• struct SharedCounterKey; defines key"))
197 .child(View::text("• use_state_keyed::<Key, _>() uses it"))
198 .child(View::text("• Same key = same underlying value"))
199 .child(View::text("• Opposite of state! (unique keys)"))
200 .child(View::gap(1))
201 .child(View::styled_text("Try this").bold().build())
202 .child(View::text("• Click + on Pane A"))
203 .child(View::text("• Watch Pane B update too!"))
204 .child(View::text("• Both buttons modify same state"))
205 .child(View::gap(1))
206 .child(View::styled_text("Next up").bold().build())
207 .child(View::text("→ 29_canvas: pixel graphics"))
208 .child(View::gap(1))
209 .child(View::styled_text("Press Escape to close").dim().build())
210 .build()
211 )
212 .build()
213 )
214 .build()
215 }Sourcepub fn use_async<T, F>(&self, f: F) -> Async<T>
pub fn use_async<T, F>(&self, f: F) -> Async<T>
Load async data that persists across re-renders.
The function is called once on first render. The result is cached and returned on subsequent renders.
§Example
fn DataList(cx: Scope) -> View {
let data = cx.use_async(|| {
// This runs in a background thread
Ok(fetch_data())
});
match data {
Async::Loading => view! { <Text>"Loading..."</Text> },
Async::Ready(items) => view! { <List items={items} /> },
Async::Error(e) => view! { <Text>{format!("Error: {}", e)}</Text> },
}
}Examples found in repository?
38 fn render(&self, cx: Scope) -> View {
39 let show_help = state!(cx, || false);
40
41 // F1 toggles help
42 cx.use_command(
43 KeyBinding::key(KeyCode::F(1)),
44 with!(show_help => move || show_help.update(|v| *v = !*v)),
45 );
46
47 // Simulate fetching user profile (slow - 2 seconds)
48 let profile = cx.use_async(|| {
49 thread::sleep(Duration::from_secs(2));
50 Ok(UserProfile {
51 name: "Alice Johnson".to_string(),
52 email: "alice@example.com".to_string(),
53 member_since: "January 2024".to_string(),
54 })
55 });
56
57 // Simulate fetching user stats (medium - 1 second)
58 let stats = cx.use_async(|| {
59 thread::sleep(Duration::from_secs(1));
60 Ok(Stats {
61 posts: 142,
62 followers: 1234,
63 following: 567,
64 })
65 });
66
67 // Simulate a failing request
68 let failing_data = cx.use_async(|| {
69 thread::sleep(Duration::from_millis(500));
70 Err::<String, _>("Network error: Connection refused".to_string())
71 });
72
73 View::vstack()
74 .spacing(1)
75 .child(
76 // Header
77 View::boxed()
78 .border(true)
79 .padding(1)
80 .child(
81 View::vstack()
82 .child(View::styled_text("Async Data Loading Demo").bold().build())
83 .child(
84 View::styled_text(
85 "Demonstrates use_async for loading data with loading/error states",
86 )
87 .dim()
88 .build(),
89 )
90 .build(),
91 )
92 .build(),
93 )
94 .child(
95 // Main content - three columns
96 View::hstack()
97 .spacing(1)
98 // Profile section
99 .child(
100 View::boxed()
101 .flex(1)
102 .border(true)
103 .padding(1)
104 .child(render_profile_section(&profile))
105 .build(),
106 )
107 // Stats section
108 .child(
109 View::boxed()
110 .flex(1)
111 .border(true)
112 .padding(1)
113 .child(render_stats_section(&stats))
114 .build(),
115 )
116 // Error section
117 .child(
118 View::boxed()
119 .flex(1)
120 .border(true)
121 .padding(1)
122 .child(render_error_section(&failing_data))
123 .build(),
124 )
125 .build(),
126 )
127 .child(
128 // Status footer
129 View::boxed()
130 .border(true)
131 .padding(1)
132 .child(
133 View::vstack()
134 .child(View::text(format!(
135 "Overall status: {}",
136 if profile.is_loading() || stats.is_loading() {
137 "Loading..."
138 } else if profile.is_error() || stats.is_error() || failing_data.is_error() {
139 "Some requests failed"
140 } else {
141 "All data loaded"
142 }
143 )))
144 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
145 .build(),
146 )
147 .build(),
148 )
149 .child(
150 View::modal()
151 .visible(show_help.get())
152 .title("Example 24: Async Data")
153 .on_dismiss(with!(show_help => move || show_help.set(false)))
154 .child(
155 View::vstack()
156 .child(View::styled_text("What you're seeing").bold().build())
157 .child(View::text("• Three async data loads in parallel"))
158 .child(View::text("• Loading, success, and error states"))
159 .child(View::text("• Different load times for each"))
160 .child(View::gap(1))
161 .child(View::styled_text("Key concepts").bold().build())
162 .child(View::text("• cx.use_async() runs in background"))
163 .child(View::text("• Returns Async<T> enum"))
164 .child(View::text("• .is_loading() / .is_error() helpers"))
165 .child(View::text("• Pattern match for state handling"))
166 .child(View::gap(1))
167 .child(View::styled_text("Try this").bold().build())
168 .child(View::text("• Watch data load progressively"))
169 .child(View::text("• Notice the failing request"))
170 .child(View::gap(1))
171 .child(View::styled_text("Next up").bold().build())
172 .child(View::text("→ 25_context: context API"))
173 .child(View::gap(1))
174 .child(View::styled_text("Press Escape to close").dim().build())
175 .build()
176 )
177 .build()
178 )
179 .build()
180 }Sourcepub fn use_stream<T, F, I>(&self, stream_fn: F) -> StreamHandle<T>
pub fn use_stream<T, F, I>(&self, stream_fn: F) -> StreamHandle<T>
Stream data incrementally with automatic accumulation.
Perfect for LLM token streaming or any iterator-based async data.
§Example
fn ChatMessage(cx: Scope) -> View {
let stream = cx.use_stream(|| {
// Returns an iterator that yields items over time
vec!["Hello", " ", "world", "!"].into_iter()
});
if stream.is_loading() {
view! { <Text>{stream.get()}</Text><Text>"▌"</Text> }
} else {
view! { <Text>{stream.get()}</Text> }
}
}Examples found in repository?
22 fn render(&self, cx: Scope) -> View {
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // Stream that yields elapsed seconds
32 let elapsed = cx.use_stream(|| {
33 (0u64..).inspect(|&s| {
34 if s > 0 {
35 std::thread::sleep(Duration::from_secs(1));
36 }
37 })
38 });
39
40 let seconds = elapsed.get();
41 let is_running = elapsed.is_loading();
42
43 // Format as MM:SS
44 let minutes = seconds / 60;
45 let secs = seconds % 60;
46 let time_display = format!("{:02}:{:02}", minutes, secs);
47
48 View::vstack()
49 .child(View::styled_text("Timer").color(Color::Cyan).bold().build())
50 .child(View::gap(1))
51 .child(
52 View::hstack()
53 .child(View::styled_text(&time_display).bold().build())
54 .child(if is_running {
55 View::styled_text(" ●").color(Color::Green).build()
56 } else {
57 View::styled_text(" ○").dim().build()
58 })
59 .build(),
60 )
61 .child(View::gap(1))
62 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
63 .child(
64 View::modal()
65 .visible(show_help.get())
66 .title("Example 04: Timer")
67 .on_dismiss(with!(show_help => move || show_help.set(false)))
68 .child(
69 View::vstack()
70 .child(View::styled_text("What you're seeing").bold().build())
71 .child(View::text("• cx.use_stream() for background data"))
72 .child(View::text("• Auto-updating UI without user input"))
73 .child(View::text("• Green dot = stream is running"))
74 .child(View::gap(1))
75 .child(View::styled_text("Key concepts").bold().build())
76 .child(View::text("• Streams run in background threads"))
77 .child(View::text("• Each yielded value triggers a re-render"))
78 .child(View::text("• is_loading() tells you if stream is active"))
79 .child(View::gap(1))
80 .child(View::styled_text("Try this").bold().build())
81 .child(View::text("• Just watch - the timer ticks automatically"))
82 .child(View::text("• No button presses needed for updates"))
83 .child(View::gap(1))
84 .child(View::styled_text("Next up").bold().build())
85 .child(View::text("→ 05_todo_list: text input and list management"))
86 .child(View::gap(1))
87 .child(View::styled_text("Press Escape to close").dim().build())
88 .build(),
89 )
90 .build(),
91 )
92 .build()
93 }More examples
25 fn render(&self, cx: Scope) -> View {
26 let show_help = state!(cx, || false);
27
28 // F1 toggles help
29 cx.use_command(
30 KeyBinding::key(KeyCode::F(1)),
31 with!(show_help => move || show_help.update(|v| *v = !*v)),
32 );
33
34 // Animated progress value using use_stream
35 let progress = cx.use_stream(|| {
36 (0u64..).map(|i| {
37 if i > 0 {
38 std::thread::sleep(Duration::from_millis(50));
39 }
40 // Progress cycles from 0.0 to 1.0
41 (i % 100) as f32 / 100.0
42 })
43 });
44
45 let current_progress = progress.get();
46
47 View::vstack()
48 .spacing(1)
49 .child(View::styled_text("Progress Bar Examples").bold().build())
50 .child(View::text(""))
51 // Basic progress bar
52 .child(View::text("Basic (75%):"))
53 .child(View::progress_bar().value(0.75).build())
54 // With label
55 .child(View::text("With label (50%):"))
56 .child(View::progress_bar().value(0.5).label("Loading").build())
57 // Without percentage
58 .child(View::text("No percentage (33%):"))
59 .child(
60 View::progress_bar()
61 .value(0.33)
62 .show_percentage(false)
63 .build(),
64 )
65 // Fixed width
66 .child(View::text("Fixed width (20 chars, 60%):"))
67 .child(View::progress_bar().value(0.6).width(20).build())
68 // Custom characters
69 .child(View::text("Custom characters (80%):"))
70 .child(
71 View::progress_bar()
72 .value(0.8)
73 .filled_char('=')
74 .empty_char('-')
75 .width(20)
76 .build(),
77 )
78 // Another style
79 .child(View::text("Block style (65%):"))
80 .child(
81 View::progress_bar()
82 .value(0.65)
83 .filled_char('#')
84 .empty_char('.')
85 .width(25)
86 .build(),
87 )
88 // Animated progress
89 .child(View::text("Animated (loops 0-100%):"))
90 .child(
91 View::progress_bar()
92 .value(current_progress)
93 .label("Progress")
94 .build(),
95 )
96 .child(View::text(""))
97 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
98 .child(
99 View::modal()
100 .visible(show_help.get())
101 .title("Example 18: Progress Bar")
102 .on_dismiss(with!(show_help => move || show_help.set(false)))
103 .child(
104 View::vstack()
105 .child(View::styled_text("What you're seeing").bold().build())
106 .child(View::text("• Progress bars with various styles"))
107 .child(View::text("• Animated progress using use_stream"))
108 .child(View::text("• Custom fill and empty characters"))
109 .child(View::gap(1))
110 .child(View::styled_text("Key concepts").bold().build())
111 .child(View::text("• View::progress_bar() creates bars"))
112 .child(View::text("• .value(0.0 to 1.0) sets progress"))
113 .child(View::text("• .label() adds text label"))
114 .child(View::text("• .filled_char() / .empty_char() customize"))
115 .child(View::gap(1))
116 .child(View::styled_text("Try this").bold().build())
117 .child(View::text("• Watch the animated bar loop"))
118 .child(View::text("• Compare different bar styles"))
119 .child(View::gap(1))
120 .child(View::styled_text("Next up").bold().build())
121 .child(View::text("→ 19_status_bar: status bar widget"))
122 .child(View::gap(1))
123 .child(View::styled_text("Press Escape to close").dim().build())
124 .build(),
125 )
126 .build(),
127 )
128 .build()
129 }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 // CPU usage stream (fluctuates between 10-90%)
31 let cpu = cx.use_stream(|| {
32 let mut rng_state = 42u64;
33 (0..).map(move |_| {
34 std::thread::sleep(Duration::from_millis(500));
35 // Simple LCG pseudo-random
36 rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
37
38 ((rng_state >> 16) % 80) as u8 + 10
39 })
40 });
41
42 // Memory usage stream (slowly increases then drops)
43 let memory = cx.use_stream(|| {
44 (0..).map(|i| {
45 std::thread::sleep(Duration::from_millis(800));
46 let cycle = i % 20;
47 if cycle < 15 {
48 40 + (cycle * 3) as u8
49 } else {
50 40 + ((20 - cycle) * 8) as u8
51 }
52 })
53 });
54
55 // Network stream (random-ish traffic)
56 let network = cx.use_stream(|| {
57 let mut rng_state = 123u64;
58 (0..).map(move |_| {
59 std::thread::sleep(Duration::from_millis(300));
60 rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
61
62 ((rng_state >> 16) % 1000) as u32 + 100
63 })
64 });
65
66 let cpu_val = cpu.get();
67 let mem_val = memory.get();
68 let net_val = network.get();
69
70 // Color based on value
71 let cpu_color = if cpu_val > 80 {
72 Color::Red
73 } else if cpu_val > 50 {
74 Color::Yellow
75 } else {
76 Color::Green
77 };
78
79 let mem_color = if mem_val > 80 {
80 Color::Red
81 } else if mem_val > 60 {
82 Color::Yellow
83 } else {
84 Color::Green
85 };
86
87 // Create progress bar (ASCII to avoid multi-byte char issues)
88 fn progress_bar(value: u8, width: usize) -> String {
89 let filled = (value as usize * width) / 100;
90 let empty = width - filled;
91 format!("[{}{}]", "#".repeat(filled), "-".repeat(empty))
92 }
93
94 View::vstack()
95 .child(
96 View::styled_text("System Monitor")
97 .color(Color::Cyan)
98 .bold()
99 .build(),
100 )
101 .child(View::gap(1))
102 .child(
103 View::hstack()
104 .child(View::text("CPU: "))
105 .child(
106 View::styled_text(progress_bar(cpu_val, 20))
107 .color(cpu_color)
108 .build(),
109 )
110 .child(
111 View::styled_text(format!(" {:>3}%", cpu_val))
112 .bold()
113 .build(),
114 )
115 .build(),
116 )
117 .child(
118 View::hstack()
119 .child(View::text("Memory: "))
120 .child(
121 View::styled_text(progress_bar(mem_val, 20))
122 .color(mem_color)
123 .build(),
124 )
125 .child(
126 View::styled_text(format!(" {:>3}%", mem_val))
127 .bold()
128 .build(),
129 )
130 .build(),
131 )
132 .child(View::gap(1))
133 .child(
134 View::hstack()
135 .child(View::text("Network: "))
136 .child(
137 View::styled_text(format!("{:>6} KB/s", net_val))
138 .color(Color::Magenta)
139 .build(),
140 )
141 .build(),
142 )
143 .child(View::gap(1))
144 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
145 .child(
146 View::modal()
147 .visible(show_help.get())
148 .title("Example 08: System Monitor")
149 .on_dismiss(with!(show_help => move || show_help.set(false)))
150 .child(
151 View::vstack()
152 .child(View::styled_text("What you're seeing").bold().build())
153 .child(View::text(
154 "• Multiple independent streams running together",
155 ))
156 .child(View::text("• Color-coded thresholds (green/yellow/red)"))
157 .child(View::text("• ASCII progress bars"))
158 .child(View::gap(1))
159 .child(View::styled_text("Key concepts").bold().build())
160 .child(View::text("• Each use_stream() runs in its own thread"))
161 .child(View::text(
162 "• Streams update independently at different rates",
163 ))
164 .child(View::text("• Conditional styling based on values"))
165 .child(View::gap(1))
166 .child(View::styled_text("Try this").bold().build())
167 .child(View::text("• Watch CPU fluctuate randomly"))
168 .child(View::text("• Memory climbs then drops cyclically"))
169 .child(View::text("• Network updates fastest (300ms)"))
170 .child(View::gap(1))
171 .child(View::styled_text("Next up").bold().build())
172 .child(View::text(
173 "→ 09_syntax_comparison: builder vs macro syntax",
174 ))
175 .child(View::gap(1))
176 .child(View::styled_text("Press Escape to close").dim().build())
177 .build(),
178 )
179 .build(),
180 )
181 .build()
182 }31 fn render(&self, cx: Scope) -> View {
32 let show_help = state!(cx, || false);
33
34 // F1 toggles help
35 cx.use_command(
36 KeyBinding::key(KeyCode::F(1)),
37 with!(show_help => move || show_help.update(|v| *v = !*v)),
38 );
39
40 // Animated value for the bar chart using use_stream directly
41 let frame_stream = cx.use_stream(|| {
42 (0u32..).inspect(|&i| {
43 if i > 0 {
44 std::thread::sleep(std::time::Duration::from_millis(100));
45 }
46 })
47 });
48
49 let current_frame = frame_stream.get();
50
51 View::vstack()
52 .spacing(1)
53 .child(
54 View::styled_text("Canvas Examples (Kitty Graphics)")
55 .bold()
56 .build(),
57 )
58 .child(View::text("Requires Kitty, Ghostty, or WezTerm terminal"))
59 .child(View::text(""))
60 // Basic shapes demo
61 .child(View::text("Basic Shapes:"))
62 .child(
63 View::canvas()
64 .width(200)
65 .height(80)
66 .on_draw(|ctx| {
67 // Clear to dark background
68 ctx.clear(Color::Rgb {
69 r: 30,
70 g: 30,
71 b: 40,
72 });
73
74 // Draw some lines
75 ctx.line(10, 10, 190, 10, Color::Red);
76 ctx.line(10, 10, 10, 70, Color::Green);
77 ctx.line(10, 70, 190, 70, Color::Blue);
78 ctx.line(190, 10, 190, 70, Color::Yellow);
79
80 // Draw diagonal lines
81 ctx.line(10, 10, 190, 70, Color::Cyan);
82 ctx.line(10, 70, 190, 10, Color::Magenta);
83
84 // Draw filled rectangles
85 ctx.fill_rect(30, 25, 30, 20, Color::Red);
86 ctx.fill_rect(80, 25, 30, 20, Color::Green);
87 ctx.fill_rect(130, 25, 30, 20, Color::Blue);
88
89 // Draw stroked rectangles
90 ctx.stroke_rect(30, 50, 30, 15, Color::Yellow);
91 ctx.stroke_rect(80, 50, 30, 15, Color::Cyan);
92 ctx.stroke_rect(130, 50, 30, 15, Color::Magenta);
93 })
94 .build(),
95 )
96 .child(View::text(""))
97 // Circles demo
98 .child(View::text("Circles:"))
99 .child(
100 View::canvas()
101 .width(200)
102 .height(60)
103 .on_draw(|ctx| {
104 ctx.clear(Color::Rgb {
105 r: 20,
106 g: 25,
107 b: 35,
108 });
109
110 // Filled circles
111 ctx.fill_circle(30, 30, 20, Color::Red);
112 ctx.fill_circle(80, 30, 15, Color::Green);
113 ctx.fill_circle(120, 30, 10, Color::Blue);
114
115 // Stroked circles
116 ctx.circle(160, 30, 20, Color::Yellow);
117 ctx.circle(160, 30, 15, Color::Cyan);
118 ctx.circle(160, 30, 10, Color::Magenta);
119 })
120 .build(),
121 )
122 .child(View::text(""))
123 // Animated bar chart
124 .child(View::text("Animated Bar Chart:"))
125 .child(
126 View::canvas()
127 .width(200)
128 .height(80)
129 .on_draw({
130 move |ctx| {
131 ctx.clear(Color::Rgb {
132 r: 25,
133 g: 25,
134 b: 30,
135 });
136
137 // Generate animated data
138 let data: Vec<f32> = (0..8)
139 .map(|i| {
140 let phase = (current_frame as f32 * 0.1) + (i as f32 * 0.5);
141 0.3 + 0.7 * ((phase.sin() + 1.0) / 2.0)
142 })
143 .collect();
144
145 let bar_width = 20u16;
146 let gap = 5u16;
147 let max_height = 60u16;
148 let start_x = 10u16;
149 let baseline = 75u16;
150
151 // Draw baseline
152 ctx.line(5, baseline as i32, 195, baseline as i32, Color::Grey);
153
154 // Draw bars
155 let colors = [
156 Color::Red,
157 Color::Green,
158 Color::Blue,
159 Color::Yellow,
160 Color::Cyan,
161 Color::Magenta,
162 Color::Rgb {
163 r: 255,
164 g: 128,
165 b: 0,
166 },
167 Color::Rgb {
168 r: 128,
169 g: 255,
170 b: 128,
171 },
172 ];
173
174 for (i, &value) in data.iter().enumerate() {
175 let x = start_x + (i as u16) * (bar_width + gap);
176 let height = (value * max_height as f32) as u16;
177 let y = baseline - height;
178 ctx.fill_rect(x, y, bar_width, height, colors[i % colors.len()]);
179 }
180 }
181 })
182 .build(),
183 )
184 .child(View::text(""))
185 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
186 .child(
187 View::modal()
188 .visible(show_help.get())
189 .title("Example 29: Canvas")
190 .on_dismiss(with!(show_help => move || show_help.set(false)))
191 .child(
192 View::vstack()
193 .child(View::styled_text("What you're seeing").bold().build())
194 .child(View::text("• Pixel graphics via Kitty protocol"))
195 .child(View::text("• Lines, rectangles, circles"))
196 .child(View::text("• Animated bar chart"))
197 .child(View::gap(1))
198 .child(View::styled_text("Key concepts").bold().build())
199 .child(View::text("• View::canvas() creates drawing area"))
200 .child(View::text("• .on_draw(|ctx| { ... }) draws pixels"))
201 .child(View::text("• ctx.line(), ctx.fill_rect(), etc."))
202 .child(View::text("• Works in Kitty/Ghostty/WezTerm"))
203 .child(View::gap(1))
204 .child(View::styled_text("Try this").bold().build())
205 .child(View::text("• Watch the animated bar chart"))
206 .child(View::text("• Run in compatible terminal"))
207 .child(View::gap(1))
208 .child(View::styled_text("Next up").bold().build())
209 .child(View::text("→ 30_image: image display"))
210 .child(View::gap(1))
211 .child(View::styled_text("Press Escape to close").dim().build())
212 .build(),
213 )
214 .build(),
215 )
216 .build()
217 }Sourcepub fn use_text_stream<F, I>(&self, stream_fn: F) -> TextStreamHandle
pub fn use_text_stream<F, I>(&self, stream_fn: F) -> TextStreamHandle
Stream text with automatic concatenation.
Convenience wrapper for use_stream that automatically concatenates
string tokens. Ideal for LLM streaming responses.
§Example
fn StreamingChat(cx: Scope) -> View {
let response = cx.use_text_stream(|| {
// Simulate LLM token stream
llm_client.stream_completion("Hello!")
});
let cursor = if response.is_loading() { "▌" } else { "" };
view! { <Text>{format!("{}{}", response.get(), cursor)}</Text> }
}Examples found in repository?
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Stream log entries
31 let logs = cx.use_text_stream(|| {
32 let log_messages = vec![
33 "[INFO] Application started",
34 "[INFO] Loading configuration...",
35 "[OK] Config loaded successfully",
36 "[INFO] Connecting to database...",
37 "[OK] Database connected",
38 "[INFO] Starting web server on :8080",
39 "[OK] Server listening",
40 "[INFO] Processing request GET /api/users",
41 "[OK] Response 200 in 45ms",
42 "[INFO] Processing request POST /api/login",
43 "[OK] Response 200 in 120ms",
44 "[WARN] High memory usage detected: 85%",
45 "[INFO] Running garbage collection...",
46 "[OK] Memory freed: 200MB",
47 "[INFO] Processing request GET /api/data",
48 "[ERROR] Database timeout after 5000ms",
49 "[INFO] Retrying database connection...",
50 "[OK] Database reconnected",
51 "[OK] Response 200 in 5045ms",
52 "[INFO] Scheduled backup starting...",
53 "[OK] Backup completed: 1.2GB",
54 "[INFO] Processing request GET /api/health",
55 "[OK] Response 200 in 12ms",
56 "[INFO] Processing request PUT /api/users/123",
57 "[OK] Response 200 in 89ms",
58 "[INFO] Cache invalidation triggered",
59 "[OK] Cache cleared: 50MB",
60 "[WARN] Slow query detected: 1200ms",
61 "[INFO] Query optimization suggested",
62 "[INFO] Processing request DELETE /api/sessions",
63 "[OK] Response 204 in 34ms",
64 "[INFO] SSL certificate check",
65 "[OK] Certificate valid for 45 days",
66 "[INFO] Processing request POST /api/upload",
67 "[OK] File uploaded: 15MB",
68 "[WARN] Disk usage at 78%",
69 "[INFO] Processing request GET /api/reports",
70 "[OK] Response 200 in 230ms",
71 "[INFO] Metrics exported to monitoring",
72 "[OK] Heartbeat sent successfully",
73 "------- Log stream completed -------",
74 ];
75
76 log_messages.into_iter().map(|msg| {
77 std::thread::sleep(Duration::from_millis(500));
78 format!("{}\n", msg)
79 })
80 });
81
82 let log_text = logs.get();
83 let is_streaming = logs.is_loading();
84
85 // Color the status indicator
86 let status = if is_streaming {
87 View::styled_text(" [LIVE]")
88 .color(Color::Green)
89 .bold()
90 .build()
91 } else {
92 View::styled_text(" [END]").dim().build()
93 };
94
95 View::vstack()
96 .child(
97 View::hstack()
98 .child(
99 View::styled_text("Log Viewer")
100 .color(Color::Cyan)
101 .bold()
102 .build(),
103 )
104 .child(status)
105 .build(),
106 )
107 .child(View::gap(1))
108 .child(
109 View::boxed()
110 .scroll(true)
111 .auto_scroll_bottom(true)
112 .min_height(15)
113 .max_height(15)
114 .child(View::text(&log_text))
115 .build(),
116 )
117 .child(View::gap(1))
118 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
119 .child(
120 View::modal()
121 .visible(show_help.get())
122 .title("Example 06: Log Viewer")
123 .on_dismiss(with!(show_help => move || show_help.set(false)))
124 .child(
125 View::vstack()
126 .child(View::styled_text("What you're seeing").bold().build())
127 .child(View::text("• cx.use_text_stream() for accumulating text"))
128 .child(View::text("• Auto-scrolling box that follows new content"))
129 .child(View::text("• Live/End indicator based on stream state"))
130 .child(View::gap(1))
131 .child(View::styled_text("Key concepts").bold().build())
132 .child(View::text("• use_text_stream concatenates yielded strings"))
133 .child(View::text(
134 "• auto_scroll_bottom(true) keeps newest visible",
135 ))
136 .child(View::text("• Simulates tailing a log file"))
137 .child(View::gap(1))
138 .child(View::styled_text("Try this").bold().build())
139 .child(View::text("• Watch the [LIVE] indicator change to [END]"))
140 .child(View::text("• Notice auto-scroll keeps up with new entries"))
141 .child(View::gap(1))
142 .child(View::styled_text("Next up").bold().build())
143 .child(View::text("→ 07_file_browser: real filesystem navigation"))
144 .child(View::gap(1))
145 .child(View::styled_text("Press Escape to close").dim().build())
146 .build(),
147 )
148 .build(),
149 )
150 .build()
151 }Sourcepub fn use_text_stream_with_restart<F, I>(
&self,
restart: bool,
stream_fn: F,
) -> TextStreamHandle
pub fn use_text_stream_with_restart<F, I>( &self, restart: bool, stream_fn: F, ) -> TextStreamHandle
Stream text with automatic concatenation and restart support.
Like use_text_stream, but allows forcing a restart when restart is true.
Use this when you need to start a fresh stream for each new request.
§Example
fn Chat(cx: Scope) -> View {
let request_id = cx.use_state(|| 0u32);
let last_id = cx.use_state(|| 0u32);
let needs_restart = request_id.get() != last_id.get();
let stream = cx.use_text_stream_with_restart(needs_restart, || {
stream_response()
});
if needs_restart {
last_id.set(request_id.get());
}
// ...
}Sourcepub fn use_command<F>(&self, binding: KeyBinding, callback: F)where
F: Fn() + 'static,
pub fn use_command<F>(&self, binding: KeyBinding, callback: F)where
F: Fn() + 'static,
Register a keyboard command/shortcut.
The callback will be invoked when the key combination is pressed. Commands registered later in the render tree take precedence.
§Example
fn App(cx: Scope) -> View {
let count = cx.use_state(|| 0);
let c = count.clone();
// Ctrl+R to reset counter
cx.use_command(KeyBinding::ctrl('r'), move || {
c.set(0);
});
view! { <Text>{format!("Count: {}", count.get())}</Text> }
}Examples found in repository?
19 fn render(&self, cx: Scope) -> View {
20 let show_help = state!(cx, || false);
21
22 // F1 toggles help
23 cx.use_command(
24 KeyBinding::key(KeyCode::F(1)),
25 with!(show_help => move || show_help.update(|v| *v = !*v)),
26 );
27
28 View::vstack()
29 .child(View::styled_text("Hello World").bold().build())
30 .child(View::gap(1))
31 .child(View::text("Welcome to Telex!"))
32 .child(View::gap(1))
33 .child(
34 View::styled_text("F1 for help • Ctrl+Q to quit")
35 .dim()
36 .build(),
37 )
38 .child(
39 View::modal()
40 .visible(show_help.get())
41 .title("Example 01: Hello World")
42 .on_dismiss(with!(show_help => move || show_help.set(false)))
43 .child(
44 View::vstack()
45 .child(View::styled_text("What you're seeing").bold().build())
46 .child(View::text(
47 "• Basic app structure with struct + Component trait",
48 ))
49 .child(View::text(
50 "• View::text() and View::styled_text() for display",
51 ))
52 .child(View::text("• View::vstack() for vertical layout"))
53 .child(View::gap(1))
54 .child(View::styled_text("Key concepts").bold().build())
55 .child(View::text("• Every Telex app implements Component"))
56 .child(View::text("• render() returns a View tree"))
57 .child(View::text("• No state yet - this is purely static"))
58 .child(View::gap(1))
59 .child(View::styled_text("Next up").bold().build())
60 .child(View::text("→ 02_counter: add state and interactivity"))
61 .child(View::gap(1))
62 .child(View::styled_text("Press Escape to close").dim().build())
63 .build(),
64 )
65 .build(),
66 )
67 .build()
68 }More examples
30 fn render(&self, cx: Scope) -> View {
31 let show_help = state!(cx, || false);
32
33 // F1 toggles help
34 cx.use_command(
35 KeyBinding::key(KeyCode::F(1)),
36 with!(show_help => move || show_help.update(|v| *v = !*v)),
37 );
38 View::vstack()
39 .spacing(1)
40 .child(
41 View::styled_text("Image Widget Demo (Kitty Graphics)")
42 .bold()
43 .build(),
44 )
45 .child(View::text("Requires Kitty, Ghostty, or WezTerm terminal"))
46 .child(View::text(""))
47 // Load image from file path
48 .child(View::text("Logo (from file path):"))
49 .child(View::image().file("assets/telex-tui.png").build())
50 .child(View::text(""))
51 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
52 .child(
53 View::modal()
54 .visible(show_help.get())
55 .title("Example 30: Image")
56 .on_dismiss(with!(show_help => move || show_help.set(false)))
57 .child(
58 View::vstack()
59 .child(View::styled_text("What you're seeing").bold().build())
60 .child(View::text("• Image display via Kitty protocol"))
61 .child(View::text("• PNG/JPEG/GIF support"))
62 .child(View::text("• Loaded from file path"))
63 .child(View::gap(1))
64 .child(View::styled_text("Key concepts").bold().build())
65 .child(View::text("• View::image() displays images"))
66 .child(View::text("• .file(\"path\") loads from disk"))
67 .child(View::text("• .bytes(data) for embedded images"))
68 .child(View::text("• Works in Kitty/Ghostty/WezTerm"))
69 .child(View::gap(1))
70 .child(View::styled_text("Try this").bold().build())
71 .child(View::text("• Run in compatible terminal"))
72 .child(View::text("• See the Telex logo rendered"))
73 .child(View::gap(1))
74 .child(View::styled_text("Next up").bold().build())
75 .child(View::text("→ 31_animated_canvas: animations"))
76 .child(View::gap(1))
77 .child(View::styled_text("Press Escape to close").dim().build())
78 .build(),
79 )
80 .build(),
81 )
82 .build()
83 }22 fn render(&self, cx: Scope) -> View {
23 let show_help = state!(cx, || false);
24
25 // F1 toggles help
26 cx.use_command(
27 KeyBinding::key(KeyCode::F(1)),
28 with!(show_help => move || show_help.update(|v| *v = !*v)),
29 );
30
31 // Stream that yields elapsed seconds
32 let elapsed = cx.use_stream(|| {
33 (0u64..).inspect(|&s| {
34 if s > 0 {
35 std::thread::sleep(Duration::from_secs(1));
36 }
37 })
38 });
39
40 let seconds = elapsed.get();
41 let is_running = elapsed.is_loading();
42
43 // Format as MM:SS
44 let minutes = seconds / 60;
45 let secs = seconds % 60;
46 let time_display = format!("{:02}:{:02}", minutes, secs);
47
48 View::vstack()
49 .child(View::styled_text("Timer").color(Color::Cyan).bold().build())
50 .child(View::gap(1))
51 .child(
52 View::hstack()
53 .child(View::styled_text(&time_display).bold().build())
54 .child(if is_running {
55 View::styled_text(" ●").color(Color::Green).build()
56 } else {
57 View::styled_text(" ○").dim().build()
58 })
59 .build(),
60 )
61 .child(View::gap(1))
62 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
63 .child(
64 View::modal()
65 .visible(show_help.get())
66 .title("Example 04: Timer")
67 .on_dismiss(with!(show_help => move || show_help.set(false)))
68 .child(
69 View::vstack()
70 .child(View::styled_text("What you're seeing").bold().build())
71 .child(View::text("• cx.use_stream() for background data"))
72 .child(View::text("• Auto-updating UI without user input"))
73 .child(View::text("• Green dot = stream is running"))
74 .child(View::gap(1))
75 .child(View::styled_text("Key concepts").bold().build())
76 .child(View::text("• Streams run in background threads"))
77 .child(View::text("• Each yielded value triggers a re-render"))
78 .child(View::text("• is_loading() tells you if stream is active"))
79 .child(View::gap(1))
80 .child(View::styled_text("Try this").bold().build())
81 .child(View::text("• Just watch - the timer ticks automatically"))
82 .child(View::text("• No button presses needed for updates"))
83 .child(View::gap(1))
84 .child(View::styled_text("Next up").bold().build())
85 .child(View::text("→ 05_todo_list: text input and list management"))
86 .child(View::gap(1))
87 .child(View::styled_text("Press Escape to close").dim().build())
88 .build(),
89 )
90 .build(),
91 )
92 .build()
93 }19 fn render(&self, cx: Scope) -> View {
20 let count = state!(cx, || 0i32);
21 let show_help = state!(cx, || false);
22
23 let increment = with!(count => move || count.update(|n| *n += 1));
24 let decrement = with!(count => move || count.update(|n| *n -= 1));
25
26 // F1 toggles help
27 cx.use_command(
28 KeyBinding::key(KeyCode::F(1)),
29 with!(show_help => move || show_help.update(|v| *v = !*v)),
30 );
31
32 View::vstack()
33 .child(View::styled_text("Counter").bold().build())
34 .child(View::gap(1))
35 .child(View::text(format!("Count: {}", count.get())))
36 .child(View::gap(1))
37 .child(
38 View::hstack()
39 .child(
40 View::button()
41 .label("Decrement")
42 .on_press(decrement)
43 .build(),
44 )
45 .child(View::text(" "))
46 .child(
47 View::button()
48 .label("Increment")
49 .on_press(increment)
50 .build(),
51 )
52 .build(),
53 )
54 .child(View::gap(1))
55 .child(
56 View::styled_text("Tab to switch • Enter to press • F1 for help • Ctrl+Q to quit")
57 .dim()
58 .build(),
59 )
60 .child(
61 View::modal()
62 .visible(show_help.get())
63 .title("Example 02: Counter")
64 .on_dismiss(with!(show_help => move || show_help.set(false)))
65 .child(
66 View::vstack()
67 .child(View::styled_text("What you're seeing").bold().build())
68 .child(View::text("• state!() macro for reactive state"))
69 .child(View::text("• View::button() with on_press callbacks"))
70 .child(View::text(
71 "• The with!() macro for capturing state in closures",
72 ))
73 .child(View::gap(1))
74 .child(View::styled_text("Key concepts").bold().build())
75 .child(View::text("• State persists across renders"))
76 .child(View::text("• Updating state triggers a re-render"))
77 .child(View::text("• Tab navigates between focusable elements"))
78 .child(View::gap(1))
79 .child(View::styled_text("Try this").bold().build())
80 .child(View::text("• Press +/- rapidly - notice instant updates"))
81 .child(View::text(
82 "• The UI stays in sync with state automatically",
83 ))
84 .child(View::gap(1))
85 .child(View::styled_text("Next up").bold().build())
86 .child(View::text("→ 03_theme_switcher: styling and colors"))
87 .child(View::gap(1))
88 .child(View::styled_text("Press Escape to close").dim().build())
89 .build(),
90 )
91 .build(),
92 )
93 .build()
94 }24 fn render(&self, cx: Scope) -> View {
25 let show_help = state!(cx, || false);
26
27 // F1 toggles help
28 cx.use_command(
29 KeyBinding::key(KeyCode::F(1)),
30 with!(show_help => move || show_help.update(|v| *v = !*v)),
31 );
32 View::vstack()
33 .child(View::styled_text("Status Bar Examples").bold().build())
34 .child(View::text(""))
35 .child(View::text("Basic status bar (left only):"))
36 .child(View::status_bar().left("NORMAL").build())
37 .child(View::text(""))
38 .child(View::text("Left and right sections:"))
39 .child(
40 View::status_bar()
41 .left("INSERT")
42 .right("Ln 42, Col 8")
43 .build(),
44 )
45 .child(View::text(""))
46 .child(View::text("All three sections:"))
47 .child(
48 View::status_bar()
49 .left("VISUAL")
50 .center("main.rs")
51 .right("UTF-8 | LF | Rust")
52 .build(),
53 )
54 .child(View::text(""))
55 .child(View::text("Custom colors (green on dark):"))
56 .child(
57 View::status_bar()
58 .left("SUCCESS")
59 .center("All tests passed")
60 .right("100%")
61 .fg(Color::Green)
62 .bg(Color::DarkGreen)
63 .build(),
64 )
65 .child(View::text(""))
66 .child(View::text("Editor-style status bar:"))
67 .child(
68 View::status_bar()
69 .left("-- INSERT --")
70 .center("~/projects/myapp/src/main.rs [+]")
71 .right("1/100 | 50%")
72 .build(),
73 )
74 .child(View::spacer())
75 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
76 .child(
77 View::modal()
78 .visible(show_help.get())
79 .title("Example 19: Status Bar")
80 .on_dismiss(with!(show_help => move || show_help.set(false)))
81 .child(
82 View::vstack()
83 .child(View::styled_text("What you're seeing").bold().build())
84 .child(View::text("• Status bars with left/center/right sections"))
85 .child(View::text("• Custom foreground and background colors"))
86 .child(View::text("• Editor-style status line example"))
87 .child(View::gap(1))
88 .child(View::styled_text("Key concepts").bold().build())
89 .child(View::text("• View::status_bar() creates status lines"))
90 .child(View::text("• .left(), .center(), .right() for sections"))
91 .child(View::text("• .fg() and .bg() for custom colors"))
92 .child(View::text("• Great for showing mode, file info, etc."))
93 .child(View::gap(1))
94 .child(View::styled_text("Try this").bold().build())
95 .child(View::text("• Compare different status bar styles"))
96 .child(View::text("• Notice how sections align"))
97 .child(View::gap(1))
98 .child(View::styled_text("Next up").bold().build())
99 .child(View::text("→ 20_menu_bar: dropdown menus"))
100 .child(View::gap(1))
101 .child(View::styled_text("Press Escape to close").dim().build())
102 .build(),
103 )
104 .build(),
105 )
106 .build()
107 }24 fn render(&self, cx: Scope) -> View {
25 let use_jsx = state!(cx, || false);
26 let show_help = state!(cx, || false);
27
28 // F1 toggles help
29 cx.use_command(
30 KeyBinding::key(KeyCode::F(1)),
31 with!(show_help => move || show_help.update(|v| *v = !*v)),
32 );
33
34 let toggle = with!(use_jsx => move || use_jsx.set(!use_jsx.get()));
35
36 // Show which syntax is currently displayed
37 let syntax_name = if use_jsx.get() {
38 "view! macro (JSX-like)"
39 } else {
40 "Builder pattern"
41 };
42
43 View::vstack()
44 .child(
45 View::styled_text("Syntax Comparison")
46 .color(Color::Cyan)
47 .bold()
48 .build(),
49 )
50 .child(
51 View::styled_text("Same UI, two ways to write it")
52 .dim()
53 .build(),
54 )
55 .child(View::gap(1))
56 .child(
57 View::hstack()
58 .child(View::text("Current syntax: "))
59 .child(
60 View::styled_text(syntax_name)
61 .color(Color::Yellow)
62 .bold()
63 .build(),
64 )
65 .build(),
66 )
67 .child(View::gap(1))
68 .child(
69 View::boxed()
70 .border(true)
71 .padding(1)
72 .child(if use_jsx.get() {
73 counter_jsx(cx.clone())
74 } else {
75 counter_builder(cx.clone())
76 })
77 .build(),
78 )
79 .child(View::gap(1))
80 .child(
81 View::button()
82 .label("Toggle Syntax")
83 .on_press(toggle)
84 .build(),
85 )
86 .child(View::gap(1))
87 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
88 .child(
89 View::modal()
90 .visible(show_help.get())
91 .title("Example 09: Syntax Comparison")
92 .on_dismiss(with!(show_help => move || show_help.set(false)))
93 .child(
94 View::vstack()
95 .child(View::styled_text("What you're seeing").bold().build())
96 .child(View::text("• Two syntaxes that produce identical output"))
97 .child(View::text("• Builder: View::vstack().child(...).build()"))
98 .child(View::text("• Macro: view! { <VStack>...</VStack> }"))
99 .child(View::gap(1))
100 .child(View::styled_text("Key concepts").bold().build())
101 .child(View::text("• Builder is Rust-native, IDE-friendly"))
102 .child(View::text("• view! macro is JSX-like, less boilerplate"))
103 .child(View::text("• Choose based on your preference"))
104 .child(View::gap(1))
105 .child(View::styled_text("Try this").bold().build())
106 .child(View::text("• Toggle between syntaxes"))
107 .child(View::text("• Notice the output is identical"))
108 .child(View::text("• Check the source code to see both styles"))
109 .child(View::gap(1))
110 .child(View::styled_text("Next up").bold().build())
111 .child(View::text("→ 10_state_explained: deep dive into state"))
112 .child(View::gap(1))
113 .child(View::styled_text("Press Escape to close").dim().build())
114 .build(),
115 )
116 .build(),
117 )
118 .build()
119 }- examples/15_markdown.rs
- examples/18_progress_bar.rs
- examples/16_tree.rs
- examples/05_todo_list.rs
- examples/10_state_explained.rs
- examples/32_effects.rs
- examples/13_split_panes.rs
- examples/07_file_browser.rs
- examples/12_text_area.rs
- examples/06_log_viewer.rs
- examples/24_async_data.rs
- examples/08_system_monitor.rs
- examples/25_context.rs
- examples/14_tabs.rs
- examples/11_checkbox.rs
- examples/17_table.rs
- examples/29_canvas.rs
- examples/20_menu_bar.rs
- examples/26_radio_buttons.rs
- examples/21_toasts.rs
- examples/03_theme_switcher.rs
- examples/28_shared_state.rs
- examples/27_keyed_state.rs
- examples/31_animated_canvas.rs
- examples/22_forms.rs
- examples/23_modal.rs
Sourcepub fn provide_context<T: Clone + 'static>(&self, value: T)
pub fn provide_context<T: Clone + 'static>(&self, value: T)
Provide a value in the context for child components to access.
Values are stored by type, so each type can only have one value. Providing a value of a type that already exists will replace it.
§Example
#[derive(Clone)]
struct UserState {
name: String,
logged_in: bool,
}
fn App(cx: Scope) -> View {
// Provide user state for all children
cx.provide_context(UserState {
name: "Alice".to_string(),
logged_in: true,
});
view! { <Header /> }
}Examples found in repository?
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 }Sourcepub fn use_context<T: Clone + 'static>(&self) -> Option<T>
pub fn use_context<T: Clone + 'static>(&self) -> Option<T>
Get a value from the context.
Returns None if no value of this type has been provided by a parent.
§Example
fn Header(cx: Scope) -> View {
let user = cx.use_context::<UserState>();
match user {
Some(u) => view! { <Text>{format!("Hello, {}", u.name)}</Text> },
None => view! { <Text>"Not logged in"</Text> },
}
}Examples found in repository?
224fn render_header(cx: &Scope) -> View {
225 // Read config and theme from context
226 let config = cx.use_context::<AppConfig>();
227 let theme = cx
228 .use_context::<ColorTheme>()
229 .unwrap_or(ColorTheme::Default);
230
231 let title = config
232 .map(|c| format!("{} v{}", c.app_name, c.version))
233 .unwrap_or_else(|| "No config".to_string());
234
235 View::boxed()
236 .border(true)
237 .padding(1)
238 .child(
239 View::vstack()
240 .child(
241 View::styled_text(title)
242 .bold()
243 .color(theme.primary())
244 .build(),
245 )
246 .child(
247 View::styled_text("Demonstrates provide_context and use_context")
248 .dim()
249 .build(),
250 )
251 .build(),
252 )
253 .build()
254}
255
256// Helper function that reads user from context
257fn render_user_panel(cx: &Scope) -> View {
258 let user = cx.use_context::<User>();
259 let theme = cx
260 .use_context::<ColorTheme>()
261 .unwrap_or(ColorTheme::Default);
262
263 let (status_text, status_color) = match &user {
264 Some(u) if u.logged_in => (format!("Logged in as: {}", u.name), theme.accent()),
265 Some(_) => ("Not logged in".to_string(), Color::DarkGrey),
266 None => ("User context not available".to_string(), Color::Red),
267 };
268
269 View::boxed()
270 .border(true)
271 .padding(1)
272 .child(
273 View::vstack()
274 .child(
275 View::styled_text("User Panel (reads from context)")
276 .bold()
277 .color(theme.primary())
278 .build(),
279 )
280 .child(View::styled_text(status_text).color(status_color).build())
281 .build(),
282 )
283 .build()
284}
285
286// Helper function for status bar
287fn render_status_bar(cx: &Scope) -> View {
288 let theme = cx
289 .use_context::<ColorTheme>()
290 .unwrap_or(ColorTheme::Default);
291
292 View::boxed()
293 .border(true)
294 .child(
295 View::hstack()
296 .child(
297 View::styled_text(format!(" Theme: {} ", theme.name()))
298 .color(theme.accent())
299 .build(),
300 )
301 .child(View::spacer())
302 .child(
303 View::styled_text(" Context values propagate automatically ")
304 .dim()
305 .build(),
306 )
307 .build(),
308 )
309 .build()
310}Sourcepub fn context(&self) -> Rc<ContextStorage>
pub fn context(&self) -> Rc<ContextStorage>
Get the context storage (for passing to child scopes).
Sourcepub fn use_effect<F, C>(&self, effect_fn: F)
pub fn use_effect<F, C>(&self, effect_fn: F)
Run a side effect after every render.
The effect function is called after each render completes. Return a cleanup function that will be called before the next effect runs.
§Example
fn Logger(cx: Scope) -> View {
let count = cx.use_state(|| 0);
cx.use_effect(|| {
println!("Rendered with count: {}", count.get());
|| {} // cleanup (runs before next effect)
});
// ...
}Warning: Be careful not to create infinite loops by updating state in an effect that runs every render.
Sourcepub fn use_effect_once<F, C>(&self, effect_fn: F)
pub fn use_effect_once<F, C>(&self, effect_fn: F)
Sourcepub fn use_effect_with<D, F, C>(&self, deps: D, effect_fn: F)
pub fn use_effect_with<D, F, C>(&self, deps: D, effect_fn: F)
Run a side effect when dependencies change.
The effect function is called on first render and whenever the
dependencies change (compared via PartialEq).
§Example
fn Counter(cx: Scope) -> View {
let count = cx.use_state(|| 0);
cx.use_effect_with(count.get(), |count| {
println!("Count changed to: {}", count);
|| {} // cleanup
});
// ...
}Multiple dependencies can be passed as a tuple:
cx.use_effect_with((a.get(), b.get()), |(a, b)| {
println!("a={}, b={}", a, b);
|| {}
});Sourcepub fn use_effect_once_keyed<K: 'static, F, C>(&self, effect_fn: F)
pub fn use_effect_once_keyed<K: 'static, F, C>(&self, effect_fn: F)
Run a keyed side effect only once (on first render). Order-independent - safe to use in conditionals.
Prefer the effect_once! macro which auto-generates the key:
effect_once!(cx, || {
println!("initialized");
|| { println!("cleanup"); }
});Sourcepub fn use_effect_keyed<K: 'static, D, F, C>(&self, deps: D, effect_fn: F)
pub fn use_effect_keyed<K: 'static, D, F, C>(&self, deps: D, effect_fn: F)
Run a keyed side effect when dependencies change. Order-independent - safe to use in conditionals.
Prefer the effect! macro which auto-generates the key:
effect!(cx, count.get(), |&c| {
println!("count changed to {}", c);
|| {} // cleanup
});Sourcepub fn use_terminal(&self) -> TerminalHandle
pub fn use_terminal(&self) -> TerminalHandle
Create or get a terminal handle.
This uses keyed state internally so it’s safe to use in conditionals. Each call site gets its own terminal handle based on the call location.
§Example
let terminal = cx.use_terminal();
if !terminal.is_started() {
terminal.spawn("bash", &[], 80, 24);
}
terminal.poll();
View::terminal().handle(terminal).build()Examples found in repository?
5fn app(cx: Scope) -> View {
6 let terminal = cx.use_terminal();
7
8 // Spawn bash on first render
9 if !terminal.is_started() {
10 if let Err(e) = terminal.spawn("bash", &[], 80, 24) {
11 eprintln!("Failed to spawn terminal: {}", e);
12 }
13 }
14
15 View::vstack()
16 .child(View::text("Telex Terminal Demo"))
17 .child(View::text(
18 "Press Ctrl+Shift+[ to escape terminal focus, Tab to navigate",
19 ))
20 .child(View::terminal().handle(terminal).build())
21 .build()
22}Trait Implementations§
Auto Trait Implementations§
impl Freeze for Scope
impl !RefUnwindSafe for Scope
impl !Send for Scope
impl !Sync for Scope
impl Unpin for Scope
impl !UnwindSafe for Scope
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.