Skip to main content

Scope

Struct Scope 

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

Context passed to components during rendering.

Provides access to hooks like use_state.

Implementations§

Source§

impl Scope

Source

pub fn new() -> Self

Create a new scope with fresh state storage.

Source

pub fn with_storage(storage: Rc<StateStorage>) -> Self

Create a scope with existing storage (for re-renders).

Source

pub fn with_storage_and_commands( storage: Rc<StateStorage>, commands: Rc<CommandRegistry>, ) -> Self

Create a scope with existing storage and command registry.

Source

pub fn with_all( storage: Rc<StateStorage>, commands: Rc<CommandRegistry>, context: Rc<ContextStorage>, ) -> Self

Create a scope with all dependencies.

Source

pub fn storage(&self) -> Rc<StateStorage>

Get the underlying storage for persistence.

Source

pub fn use_state<T: 'static>(&self, init: impl FnOnce() -> T) -> State<T>

Create local state that persists across re-renders.

§Example
fn Counter(cx: Scope) -> View {
    let count = cx.use_state(|| 0);
    // ...
}

Note: This API requires hooks to be called in the same order every render. For order-independent state, use state! macro instead.

Source

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?
examples/28_shared_state.rs (line 42)
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    }
Source

pub fn use_async<T, F>(&self, f: F) -> Async<T>
where T: Clone + Send + 'static, F: FnOnce() -> Result<T, String> + Send + 'static,

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?
examples/24_async_data.rs (lines 48-55)
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    }
Source

pub fn use_stream<T, F, I>(&self, stream_fn: F) -> StreamHandle<T>
where T: Clone + Default + Send + 'static, F: FnOnce() -> I + Send + 'static, I: Iterator<Item = T> + Send + 'static,

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?
examples/04_timer.rs (lines 32-38)
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
Hide additional examples
examples/18_progress_bar.rs (lines 35-43)
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    }
examples/08_system_monitor.rs (lines 31-40)
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    }
examples/29_canvas.rs (lines 41-47)
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    }
Source

pub fn use_text_stream<F, I>(&self, stream_fn: F) -> TextStreamHandle
where F: FnOnce() -> I + Send + 'static, I: Iterator<Item = String> + Send + 'static,

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?
examples/06_log_viewer.rs (lines 31-80)
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    }
Source

pub fn use_text_stream_with_restart<F, I>( &self, restart: bool, stream_fn: F, ) -> TextStreamHandle
where F: FnOnce() -> I + Send + 'static, I: Iterator<Item = String> + Send + 'static,

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());
    }
    // ...
}
Source

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?
examples/01_hello_world.rs (lines 23-26)
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
Hide additional examples
examples/30_image.rs (lines 34-37)
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    }
examples/04_timer.rs (lines 26-29)
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    }
examples/02_counter.rs (lines 27-30)
19    fn render(&self, cx: Scope) -> View {
20        let count = state!(cx, || 0i32);
21        let show_help = state!(cx, || false);
22
23        let increment = with!(count => move || count.update(|n| *n += 1));
24        let decrement = with!(count => move || count.update(|n| *n -= 1));
25
26        // F1 toggles help
27        cx.use_command(
28            KeyBinding::key(KeyCode::F(1)),
29            with!(show_help => move || show_help.update(|v| *v = !*v)),
30        );
31
32        View::vstack()
33            .child(View::styled_text("Counter").bold().build())
34            .child(View::gap(1))
35            .child(View::text(format!("Count: {}", count.get())))
36            .child(View::gap(1))
37            .child(
38                View::hstack()
39                    .child(
40                        View::button()
41                            .label("Decrement")
42                            .on_press(decrement)
43                            .build(),
44                    )
45                    .child(View::text(" "))
46                    .child(
47                        View::button()
48                            .label("Increment")
49                            .on_press(increment)
50                            .build(),
51                    )
52                    .build(),
53            )
54            .child(View::gap(1))
55            .child(
56                View::styled_text("Tab to switch • Enter to press • F1 for help • Ctrl+Q to quit")
57                    .dim()
58                    .build(),
59            )
60            .child(
61                View::modal()
62                    .visible(show_help.get())
63                    .title("Example 02: Counter")
64                    .on_dismiss(with!(show_help => move || show_help.set(false)))
65                    .child(
66                        View::vstack()
67                            .child(View::styled_text("What you're seeing").bold().build())
68                            .child(View::text("• state!() macro for reactive state"))
69                            .child(View::text("• View::button() with on_press callbacks"))
70                            .child(View::text(
71                                "• The with!() macro for capturing state in closures",
72                            ))
73                            .child(View::gap(1))
74                            .child(View::styled_text("Key concepts").bold().build())
75                            .child(View::text("• State persists across renders"))
76                            .child(View::text("• Updating state triggers a re-render"))
77                            .child(View::text("• Tab navigates between focusable elements"))
78                            .child(View::gap(1))
79                            .child(View::styled_text("Try this").bold().build())
80                            .child(View::text("• Press +/- rapidly - notice instant updates"))
81                            .child(View::text(
82                                "• The UI stays in sync with state automatically",
83                            ))
84                            .child(View::gap(1))
85                            .child(View::styled_text("Next up").bold().build())
86                            .child(View::text("→ 03_theme_switcher: styling and colors"))
87                            .child(View::gap(1))
88                            .child(View::styled_text("Press Escape to close").dim().build())
89                            .build(),
90                    )
91                    .build(),
92            )
93            .build()
94    }
examples/19_status_bar.rs (lines 28-31)
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    }
examples/09_syntax_comparison.rs (lines 29-32)
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    }
Source

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?
examples/25_context.rs (lines 88-91)
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    }
Source

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?
examples/25_context.rs (line 226)
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}
Source

pub fn context(&self) -> Rc<ContextStorage>

Get the context storage (for passing to child scopes).

Source

pub fn use_effect<F, C>(&self, effect_fn: F)
where F: FnOnce() -> C + 'static, C: FnOnce() + 'static,

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.

Source

pub fn use_effect_once<F, C>(&self, effect_fn: F)
where F: FnOnce() -> C + 'static, C: FnOnce() + 'static,

Run a side effect only once (on first render).

The effect function is called only on the first render. The cleanup function is called on app exit.

§Example
fn App(cx: Scope) -> View {
    cx.use_effect_once(|| {
        println!("App initialized");
        || {
            println!("App cleanup");
        }
    });

    // ...
}
Source

pub fn use_effect_with<D, F, C>(&self, deps: D, effect_fn: F)
where D: PartialEq + Clone + 'static, F: FnOnce(&D) -> C + 'static, C: FnOnce() + 'static,

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);
    || {}
});
Source

pub fn use_effect_once_keyed<K: 'static, F, C>(&self, effect_fn: F)
where F: FnOnce() -> C + 'static, C: FnOnce() + 'static,

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"); }
});
Source

pub fn use_effect_keyed<K: 'static, D, F, C>(&self, deps: D, effect_fn: F)
where D: PartialEq + Clone + 'static, F: FnOnce(&D) -> C + 'static, C: FnOnce() + 'static,

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
});
Source

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?
examples/33_terminal.rs (line 6)
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§

Source§

impl Clone for Scope

Source§

fn clone(&self) -> Scope

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Default for Scope

Source§

fn default() -> Self

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

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

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

Source§

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

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

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

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

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

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

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

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

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

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

Source§

fn into(self) -> U

Calls U::from(self).

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

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

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

Source§

type Error = Infallible

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

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

Performs the conversion.
Source§

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

Source§

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

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

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

Performs the conversion.