#![allow(unused_imports)]
use serde::{Deserialize, Serialize};
use std::panic::AssertUnwindSafe;
use crate::Rect;
use crate::Renderer;
use crate::Size;
use crate::SizeProposal;
use crate::View;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ComponentErrorState {
pub has_error: bool,
pub error_message: Option<String>,
pub error_location: Option<String>,
}
impl ComponentErrorState {
pub fn clear() -> Self {
Self::default()
}
pub fn error(message: impl Into<String>, location: impl Into<String>) -> Self {
Self {
has_error: true,
error_message: Some(message.into()),
error_location: Some(location.into()),
}
}
}
pub struct ErrorBoundary<V: View> {
child: V,
has_error: std::sync::atomic::AtomicBool,
last_error: std::sync::Mutex<Option<String>>,
pub(crate) fallback_color: [f32; 4],
pub(crate) fallback_label: Option<String>,
}
impl<V: View> ErrorBoundary<V> {
pub fn new(child: V) -> Self {
Self {
child,
has_error: std::sync::atomic::AtomicBool::new(false),
last_error: std::sync::Mutex::new(None),
fallback_color: [1.0, 0.2, 0.2, 0.9],
fallback_label: None,
}
}
pub fn fallback_color(mut self, color: [f32; 4]) -> Self {
self.fallback_color = color;
self
}
pub fn fallback_label(mut self, label: impl Into<String>) -> Self {
self.fallback_label = Some(label.into());
self
}
pub fn has_error(&self) -> bool {
self.has_error
.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn last_error(&self) -> Option<String> {
self.last_error
.lock()
.ok()
.and_then(|guard| guard.clone())
}
pub fn clear_error(&self) {
self.has_error
.store(false, std::sync::atomic::Ordering::Relaxed);
if let Ok(mut guard) = self.last_error.lock() {
*guard = None;
}
}
fn render_fallback(&self, renderer: &mut dyn Renderer, rect: Rect) {
renderer.fill_rounded_rect(rect, 4.0, self.fallback_color);
if let Some(ref label) = self.fallback_label {
renderer.draw_text(
label,
rect.x + 8.0,
rect.y + rect.height * 0.5,
12.0,
[1.0, 1.0, 1.0, 1.0],
);
}
}
}
impl<V: View> View for ErrorBoundary<V> {
type Body = V::Body;
fn body(self) -> Self::Body {
self.child.body()
}
fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
let snap = renderer.snapshot_render_state();
let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
self.child.render(renderer, rect);
}));
match result {
Ok(()) => {
self.has_error
.store(false, std::sync::atomic::Ordering::Relaxed);
}
Err(panic) => {
renderer.restore_render_state(snap);
self.has_error
.store(true, std::sync::atomic::Ordering::Relaxed);
let msg = if let Some(s) = panic.downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = panic.downcast_ref::<String>() {
s.clone()
} else {
"unknown panic".to_string()
};
if let Ok(mut guard) = self.last_error.lock() {
*guard = Some(msg.clone());
}
log::error!("ErrorBoundary caught panic: {msg}");
self.render_fallback(renderer, rect);
}
}
}
fn intrinsic_size(&self, renderer: &mut dyn Renderer, proposal: SizeProposal) -> Size {
let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
self.child.intrinsic_size(renderer, proposal)
}));
match result {
Ok(size) => size,
Err(panic) => {
self.has_error
.store(true, std::sync::atomic::Ordering::Relaxed);
let msg = if let Some(s) = panic.downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = panic.downcast_ref::<String>() {
s.clone()
} else {
"unknown panic in intrinsic_size".to_string()
};
if let Ok(mut guard) = self.last_error.lock() {
*guard = Some(msg.clone());
}
log::error!("ErrorBoundary caught panic in intrinsic_size: {msg}");
Size::ZERO
}
}
}
fn flex_weight(&self) -> f32 {
self.child.flex_weight()
}
}