34_channels_and_intervals/
34_channels_and_intervals.rs1use 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 interval!(cx, Duration::from_secs(1), with!(ticks => move || {
38 ticks.update(|n| *n += 1);
39 }));
40
41 let ch = channel!(cx, String);
43
44 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}