37_error_boundary/
37_error_boundary.rs1use crossterm::event::KeyCode;
9use crossterm::style::Color;
10use telex::buffer::{Buffer, Rect};
11use telex::prelude::*;
12use telex::widget::Widget;
13
14telex::require_api!(0, 2);
15
16fn main() {
17 telex::run(App).unwrap();
18}
19
20struct RiskyCounter(i32);
22
23impl Widget for RiskyCounter {
24 fn render(&self, area: Rect, buf: &mut Buffer) {
25 assert!(self.0 < 5, "Counter hit 5 — boom!");
26 let text = format!("Counter: {} (panics at 5)", self.0);
27 buf.write_str(area.x, area.y, &text, Color::Green, Color::Reset);
28 }
29
30 fn height_hint(&self, _width: u16) -> Option<u16> {
31 Some(1)
32 }
33}
34
35struct App;
36
37impl Component for App {
38 fn render(&self, cx: Scope) -> View {
39 let show_help = state!(cx, || false);
40
41 cx.use_command(
42 KeyBinding::key(KeyCode::F(1)),
43 with!(show_help => move || show_help.update(|v| *v = !*v)),
44 );
45
46 let count = state!(cx, || 0i32);
47
48 let risky_view = View::custom(std::rc::Rc::new(std::cell::RefCell::new(RiskyCounter(count.get()))));
52
53 let fallback = View::vstack()
54 .child(View::styled_text("CAUGHT PANIC").color(Color::Red).bold().build())
55 .child(View::text("The child view panicked."))
56 .child(View::text("But the app is still running!"))
57 .build();
58
59 View::vstack()
60 .spacing(1)
61 .child(View::styled_text("Error Boundary Demo").bold().build())
62 .child(
63 View::hstack()
64 .spacing(2)
65 .child(
66 View::vstack()
67 .spacing(1)
68 .child(View::styled_text("Protected Panel").bold().build())
69 .child(
70 View::error_boundary()
71 .child(risky_view)
72 .fallback(fallback)
73 .build(),
74 )
75 .build(),
76 )
77 .child(
78 View::vstack()
79 .spacing(1)
80 .child(View::styled_text("How It Works").bold().build())
81 .child(View::text("The left panel asserts"))
82 .child(View::text("count < 5. When it hits"))
83 .child(View::text("5, the error boundary"))
84 .child(View::text("catches the panic and"))
85 .child(View::text("renders the fallback."))
86 .build(),
87 )
88 .build(),
89 )
90 .child(
91 View::hstack()
92 .spacing(1)
93 .child(
94 View::button()
95 .label("[ + Increment ]")
96 .on_press(with!(count => move || count.update(|n| *n += 1)))
97 .build(),
98 )
99 .child(
100 View::button()
101 .label("[ Reset to 0 ]")
102 .on_press(with!(count => move || count.set(0)))
103 .build(),
104 )
105 .child(View::styled_text(format!("count = {}", count.get())).dim().build())
106 .build(),
107 )
108 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
109 .child(
110 View::modal()
111 .visible(show_help.get())
112 .title("Example 37: Error Boundary")
113 .on_dismiss(with!(show_help => move || show_help.set(false)))
114 .child(
115 View::vstack()
116 .child(View::styled_text("What you're seeing").bold().build())
117 .child(View::text("• A counter that panics at 5"))
118 .child(View::text("• Error boundary catches the panic"))
119 .child(View::text("• Red fallback replaces the crash"))
120 .child(View::gap(1))
121 .child(View::styled_text("Key concepts").bold().build())
122 .child(View::text("• View::error_boundary()"))
123 .child(View::text("• .child(risky) .fallback(safe)"))
124 .child(View::text("• Panics are caught, not propagated"))
125 .child(View::text("• App keeps running after panic"))
126 .child(View::gap(1))
127 .child(View::styled_text("Try this").bold().build())
128 .child(View::text("• Increment to 5 to trigger panic"))
129 .child(View::text("• Reset to 0 to recover"))
130 .child(View::text("• Keep incrementing past 5"))
131 .child(View::gap(1))
132 .child(View::styled_text("Next up").bold().build())
133 .child(View::text("-> 38_custom_widget: Game of Life"))
134 .child(View::gap(1))
135 .child(View::styled_text("Press Escape to close").dim().build())
136 .build(),
137 )
138 .build(),
139 )
140 .build()
141 }
142}