use std::cell::RefCell;
use std::panic;
use crate::render::Cell;
use crate::style::Color;
use crate::widget::traits::{RenderContext, View, WidgetProps};
use crate::{impl_props_builders, impl_styled_view};
pub struct ErrorBoundary {
child: Option<Box<dyn View>>,
fallback: Option<Box<dyn View>>,
has_error: std::cell::Cell<bool>,
error_message: RefCell<Option<String>>,
props: WidgetProps,
}
impl ErrorBoundary {
pub fn new() -> Self {
Self {
child: None,
fallback: None,
has_error: std::cell::Cell::new(false),
error_message: RefCell::new(None),
props: WidgetProps::new(),
}
}
pub fn child<V: View + 'static>(mut self, child: V) -> Self {
self.child = Some(Box::new(child));
self
}
pub fn fallback<V: View + 'static>(mut self, fallback: V) -> Self {
self.fallback = Some(Box::new(fallback));
self
}
pub fn reset(&self) {
self.has_error.set(false);
*self.error_message.borrow_mut() = None;
}
pub fn has_error(&self) -> bool {
self.has_error.get()
}
pub fn error_message(&self) -> Option<String> {
self.error_message.borrow().clone()
}
fn render_default_fallback(&self, ctx: &mut RenderContext) {
let area = ctx.area;
if area.width < 4 || area.height < 3 {
return;
}
let border_color = Color::rgb(200, 60, 60);
let text_color = Color::rgb(200, 60, 60);
let dim_color = Color::rgb(140, 60, 60);
for x in 0..area.width {
ctx.set(x, 0, Cell::new('─').fg(border_color));
ctx.set(x, area.height - 1, Cell::new('─').fg(border_color));
}
for y in 0..area.height {
ctx.set(0, y, Cell::new('│').fg(border_color));
ctx.set(area.width - 1, y, Cell::new('│').fg(border_color));
}
ctx.set(0, 0, Cell::new('┌').fg(border_color));
ctx.set(area.width - 1, 0, Cell::new('┐').fg(border_color));
ctx.set(0, area.height - 1, Cell::new('└').fg(border_color));
ctx.set(
area.width - 1,
area.height - 1,
Cell::new('┘').fg(border_color),
);
let title = " Error ";
let title_x: u16 = 2;
let mut dx: u16 = 0;
for ch in title.chars() {
let cw = crate::utils::char_width(ch) as u16;
let x = title_x + dx;
if x < area.width - 1 {
ctx.set(x, 0, Cell::new(ch).fg(text_color));
}
dx += cw;
}
let msg = self.error_message.borrow();
let display_msg = msg.as_deref().unwrap_or("A rendering error occurred");
let inner_width = (area.width.saturating_sub(4)) as usize;
let truncated = crate::utils::truncate_to_width(display_msg, inner_width);
let msg_y: u16 = 1;
let mut dx: u16 = 0;
for ch in truncated.chars() {
let cw = crate::utils::char_width(ch) as u16;
let x = 2 + dx;
if x < area.width - 1 {
ctx.set(x, msg_y, Cell::new(ch).fg(dim_color).dim());
}
dx += cw;
}
}
}
impl Default for ErrorBoundary {
fn default() -> Self {
Self::new()
}
}
impl View for ErrorBoundary {
crate::impl_view_meta!("ErrorBoundary");
fn render(&self, ctx: &mut RenderContext) {
if self.has_error.get() {
if let Some(ref fallback) = self.fallback {
fallback.render(ctx);
} else {
self.render_default_fallback(ctx);
}
return;
}
if let Some(ref child) = self.child {
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
child.render(ctx);
}));
if let Err(panic_info) = result {
self.has_error.set(true);
let msg = if let Some(s) = panic_info.downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = panic_info.downcast_ref::<String>() {
s.clone()
} else {
"Unknown panic".to_string()
};
*self.error_message.borrow_mut() = Some(msg);
if let Some(ref fallback) = self.fallback {
fallback.render(ctx);
} else {
self.render_default_fallback(ctx);
}
}
}
}
}
impl_styled_view!(ErrorBoundary);
impl_props_builders!(ErrorBoundary);
pub fn error_boundary() -> ErrorBoundary {
ErrorBoundary::new()
}