Skip to main content

34_channels_and_intervals/
34_channels_and_intervals.rs

1//! Example 34: Channels and Intervals
2//!
3//! Demonstrates external event handling with channel! and interval!:
4//! - `interval!` fires a callback every second (live tick counter)
5//! - `channel!` receives messages from background threads
6//! - A button spawns a worker thread that sleeps then sends a result
7//!
8//! Run with: `cargo run -p telex-tui --example 34_channels_and_intervals`
9
10use crossterm::event::KeyCode;
11use crossterm::style::Color;
12use std::time::Duration;
13use telex::prelude::*;
14
15telex::require_api!(0, 2);
16
17fn main() {
18    telex::run(App).unwrap();
19}
20
21struct App;
22
23impl Component for App {
24    fn render(&self, cx: Scope) -> View {
25        let show_help = state!(cx, || false);
26
27        cx.use_command(
28            KeyBinding::key(KeyCode::F(1)),
29            with!(show_help => move || show_help.update(|v| *v = !*v)),
30        );
31
32        let ticks = state!(cx, || 0u64);
33        let messages: State<Vec<String>> = state!(cx, Vec::new);
34        let tasks_running = state!(cx, || 0u32);
35
36        // Tick every second
37        interval!(cx, Duration::from_secs(1), with!(ticks => move || {
38            ticks.update(|n| *n += 1);
39        }));
40
41        // Channel for worker thread results
42        let ch = channel!(cx, String);
43
44        // Process incoming messages
45        for msg in ch.get() {
46            messages.update(|v| v.push(msg));
47            tasks_running.update(|n| *n = n.saturating_sub(1));
48        }
49
50        let spawn_task = {
51            let tx = ch.tx();
52            let task_num = messages.get().len() + tasks_running.get() as usize + 1;
53            with!(tasks_running => move || {
54                tasks_running.update(|n| *n += 1);
55                let tx = tx.clone();
56                std::thread::spawn(move || {
57                    std::thread::sleep(Duration::from_secs(2));
58                    tx.send(format!("Task {} complete!", task_num)).ok();
59                });
60            })
61        };
62
63        let elapsed = ticks.get();
64        let mins = elapsed / 60;
65        let secs = elapsed % 60;
66
67        View::vstack()
68            .spacing(1)
69            .child(View::styled_text("Channels & Intervals").bold().build())
70            .child(
71                View::hstack()
72                    .spacing(1)
73                    .child(View::styled_text("Elapsed:").dim().build())
74                    .child(View::styled_text(format!("{:02}:{:02}", mins, secs)).color(Color::Cyan).bold().build())
75                    .child(View::styled_text(format!("({} ticks)", elapsed)).dim().build())
76                    .build(),
77            )
78            .child(
79                View::hstack()
80                    .spacing(1)
81                    .child(
82                        View::button()
83                            .label("[ Spawn Background Task ]")
84                            .on_press(spawn_task)
85                            .build(),
86                    )
87                    .child(if tasks_running.get() > 0 {
88                        View::styled_text(format!("{} running...", tasks_running.get()))
89                            .color(Color::Yellow)
90                            .build()
91                    } else {
92                        View::styled_text("idle").dim().build()
93                    })
94                    .build(),
95            )
96            .child(View::styled_text("─── Messages ───").dim().build())
97            .child({
98                let msgs = messages.get();
99                if msgs.is_empty() {
100                    View::styled_text("(no messages yet — spawn a task!)").dim().build()
101                } else {
102                    let max_visible = 8;
103                    let len = msgs.len();
104                    let skip = len.saturating_sub(max_visible);
105                    let mut stack = View::vstack();
106                    if skip > 0 {
107                        stack = stack.child(
108                            View::styled_text(format!("  ... {} earlier messages", skip))
109                                .dim()
110                                .build(),
111                        );
112                    }
113                    for (i, m) in msgs.iter().enumerate().skip(skip) {
114                        let is_last = i == len - 1;
115                        stack = stack.child(
116                            View::styled_text(format!("  {} {}", if is_last { "→" } else { " " }, m))
117                                .color(if is_last { Color::Green } else { Color::Reset })
118                                .build(),
119                        );
120                    }
121                    stack.build()
122                }
123            })
124            .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
125            .child(
126                View::modal()
127                    .visible(show_help.get())
128                    .title("Example 34: Channels & Intervals")
129                    .on_dismiss(with!(show_help => move || show_help.set(false)))
130                    .child(
131                        View::vstack()
132                            .child(View::styled_text("What you're seeing").bold().build())
133                            .child(View::text("• interval! ticks every second"))
134                            .child(View::text("• channel! receives from threads"))
135                            .child(View::text("• Workers sleep 2s then send a message"))
136                            .child(View::gap(1))
137                            .child(View::styled_text("Key concepts").bold().build())
138                            .child(View::text("• interval!(cx, dur, || callback)"))
139                            .child(View::text("• channel!(cx, Type) for inbound msgs"))
140                            .child(View::text("• ch.tx() gives a WakingSender"))
141                            .child(View::text("• ch.get() reads this frame's messages"))
142                            .child(View::gap(1))
143                            .child(View::styled_text("Try this").bold().build())
144                            .child(View::text("• Spawn several tasks at once"))
145                            .child(View::text("• Watch messages arrive after 2s"))
146                            .child(View::text("• Notice the tick counter keeps going"))
147                            .child(View::gap(1))
148                            .child(View::styled_text("Next up").bold().build())
149                            .child(View::text("-> 35_slider: bounded numeric input"))
150                            .child(View::gap(1))
151                            .child(View::styled_text("Press Escape to close").dim().build())
152                            .build(),
153                    )
154                    .build(),
155            )
156            .build()
157    }
158}