pub struct StreamHandle<T> { /* private fields */ }Expand description
Handle for stream state that can be stored and cloned.
Implementations§
Source§impl<T: Clone + Default + 'static> StreamHandle<T>
impl<T: Clone + Default + 'static> StreamHandle<T>
Sourcepub fn with_initial(initial: T) -> Self
pub fn with_initial(initial: T) -> Self
Create a new stream handle with a specific initial value.
Sourcepub fn get(&self) -> T
pub fn get(&self) -> T
Get the current accumulated value.
Examples found in repository?
examples/04_timer.rs (line 40)
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
examples/18_progress_bar.rs (line 45)
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/06_log_viewer.rs (line 82)
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 }examples/08_system_monitor.rs (line 66)
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 (line 49)
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 is_loading(&self) -> bool
pub fn is_loading(&self) -> bool
Check if the stream is currently loading/streaming.
Examples found in repository?
examples/04_timer.rs (line 41)
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
examples/06_log_viewer.rs (line 83)
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 is_streaming(&self) -> bool
pub fn is_streaming(&self) -> bool
Check if the stream is actively receiving data.
Sourcepub fn state(&self) -> StreamState
pub fn state(&self) -> StreamState
Get the current stream state.
Source§impl<T: Clone + Send + 'static> StreamHandle<T>
impl<T: Clone + Send + 'static> StreamHandle<T>
Sourcepub fn start<F, I>(&self, stream_fn: F)
pub fn start<F, I>(&self, stream_fn: F)
Start the stream if not already started.
The stream_fn should be a function that returns an iterator.
Each item from the iterator will be sent through the channel.
Sourcepub fn start_with_result<F, I>(&self, stream_fn: F)
pub fn start_with_result<F, I>(&self, stream_fn: F)
Start the stream with error handling.
Sourcepub fn poll(&self, accumulate: impl Fn(&mut T, T)) -> bool
pub fn poll(&self, accumulate: impl Fn(&mut T, T)) -> bool
Poll for new items and update accumulated value. Returns true if there were updates.
Sourcepub fn reset_with(&self, initial: T)
pub fn reset_with(&self, initial: T)
Reset the stream with a specific initial value.
Trait Implementations§
Source§impl<T> Clone for StreamHandle<T>where
T: Clone,
impl<T> Clone for StreamHandle<T>where
T: Clone,
Auto Trait Implementations§
impl<T> Freeze for StreamHandle<T>
impl<T> !RefUnwindSafe for StreamHandle<T>
impl<T> !Send for StreamHandle<T>
impl<T> !Sync for StreamHandle<T>
impl<T> Unpin for StreamHandle<T>
impl<T> !UnwindSafe for StreamHandle<T>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> 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>
Convert
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Convert
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Convert
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.