1use crossterm::event::KeyCode;
9use telex::prelude::*;
10use telex::Color;
11
12telex::require_api!(0, 2);
13
14fn main() {
15 telex::run_with_theme(App, telex::theme::Theme::nord()).unwrap();
16}
17
18#[derive(Clone)]
20struct AppConfig {
21 app_name: String,
22 version: String,
23}
24
25#[derive(Clone, Copy, PartialEq)]
26enum ColorTheme {
27 Default,
28 Ocean,
29 Forest,
30 Sunset,
31}
32
33impl ColorTheme {
34 fn primary(&self) -> Color {
35 match self {
36 ColorTheme::Default => Color::White,
37 ColorTheme::Ocean => Color::Cyan,
38 ColorTheme::Forest => Color::Green,
39 ColorTheme::Sunset => Color::Yellow,
40 }
41 }
42
43 fn accent(&self) -> Color {
44 match self {
45 ColorTheme::Default => Color::Blue,
46 ColorTheme::Ocean => Color::Blue,
47 ColorTheme::Forest => Color::DarkGreen,
48 ColorTheme::Sunset => Color::Red,
49 }
50 }
51
52 fn name(&self) -> &'static str {
53 match self {
54 ColorTheme::Default => "Default",
55 ColorTheme::Ocean => "Ocean",
56 ColorTheme::Forest => "Forest",
57 ColorTheme::Sunset => "Sunset",
58 }
59 }
60}
61
62#[derive(Clone)]
63struct User {
64 name: String,
65 logged_in: bool,
66}
67
68struct App;
69
70impl Component for App {
71 fn render(&self, cx: Scope) -> View {
72 let show_help = state!(cx, || false);
73
74 cx.use_command(
76 KeyBinding::key(KeyCode::F(1)),
77 with!(show_help => move || show_help.update(|v| *v = !*v)),
78 );
79
80 let theme = state!(cx, || ColorTheme::Default);
82 let user = state!(cx, || User {
83 name: "Guest".to_string(),
84 logged_in: false,
85 });
86
87 cx.provide_context(AppConfig {
89 app_name: "Context Demo".to_string(),
90 version: "1.0.0".to_string(),
91 });
92
93 cx.provide_context(theme.get());
95 cx.provide_context(user.get());
96
97 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 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 let current_theme = theme.get();
121
122 View::vstack()
123 .spacing(1)
124 .child(render_header(&cx))
126 .child(
127 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 .child(render_user_panel(&cx))
181 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
182 .build(),
183 )
184 .build(),
185 )
186 .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 }
221}
222
223fn render_header(cx: &Scope) -> View {
225 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
256fn 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
286fn 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}