visualpanic_rs/lib.rs
1//! # VisualPanic
2//!
3//! Visualize panics with native GUI dialogs on supported systems (see list at https://crates.io/crates/native-dialog).
4//!
5//! Provides a solution to panic visually, useful for GUI applications where a console view might not be available at all times. Customizable in some ways, e.g., which icon, title and dialog level should be used.
6//!
7//! ## Example 1: Use the default settings and register for the whole application
8//! ```rust
9//! # use visualpanic_rs::VisualPanic;
10//! fn main() {
11//! VisualPanic::default().register_global();
12//! }
13//! ```
14//!
15//! ## Example 2: Use custom settings and register for the whole application
16//! ```rust
17//! # use visualpanic_rs::VisualPanic;
18//! # use visualpanic_rs::VisualPanicLevel;
19//! fn main() {
20//! VisualPanic::new(
21//! Some("path/to/custom_icon.png"),
22//! Some("Custom Title"),
23//! Some(VisualPanicLevel::Info))
24//! .register_global();
25//! }
26//! ```
27
28#![allow(warnings, unused)]
29#![feature(panic_info_message)]
30
31use serde::{Deserialize, Serialize};
32
33/// An enum stating the possible dialog levels
34#[derive(Clone, Debug, Copy, Eq, PartialOrd, PartialEq, Ord, Hash, Default, Serialize, Deserialize)]
35pub enum VisualPanicLevel {
36 /// Error level
37 #[default]
38 Error,
39 /// Warning level (
40 Warning,
41 /// Info level
42 Info
43}
44
45/// The struct containing information on the current VisualPanic settings.
46#[derive(Clone, Debug, Eq, PartialOrd, PartialEq, Ord, Hash, Default, Serialize, Deserialize)]
47pub struct VisualPanic {
48 /// <div class="warning">Currently not implemented!</div>
49 /// Option to set a custom icon to be used.
50 /// Value must be set to a valid path, e.g.,
51 /// ```rust
52 /// Some(String::from("path/to/icon.png"));
53 /// ```
54 custom_icon: Option<String>,
55 /// Option to set a custom title to be used.
56 /// Value can be set to any UTF-8 compliant [`String`], e.g.,
57 /// ```rust
58 /// Some(String::from("Custom String"));
59 /// ```
60 custom_title: Option<String>,
61 /// Option to set a custom dialog level.
62 /// Value can be one option of [`VisualPanicLevel`], e.g.,
63 /// ```rust
64 /// # use visualpanic_rs::VisualPanicLevel;
65 /// Some(VisualPanicLevel::Error);
66 /// ```
67 custom_level: Option<VisualPanicLevel>,
68}
69
70/// Provide public methods for [`VisualPanic`].
71impl VisualPanic {
72 /// Implements a default struct with all fields set to [`None`].
73 pub fn default() -> Self {
74 return VisualPanic {
75 custom_icon: None,
76 custom_title: None,
77 custom_level: None,
78 }
79 }
80
81 /// Implements a new struct with custom options.
82 /// The icon, title and level of the dialog can be set using [`Option<T>`], e.g.,
83 /// ```rust
84 /// # use visualpanic_rs::{VisualPanic, VisualPanicLevel};
85 /// let visual_panic_options: VisualPanic = VisualPanic::new(
86 /// Some("path/to/custom_icon.png"),
87 /// Some("Custom Title"),
88 /// Some(VisualPanicLevel::Info)
89 /// );
90 /// ```
91 pub fn new(custom_icon: Option<&str>, custom_title: Option<&str>, custom_level: Option<VisualPanicLevel>) -> Self {
92 let mut return_val = VisualPanic{
93 custom_icon: None,
94 custom_title: None,
95 custom_level: None,
96 };
97 if let Some(icon_str) = custom_icon {
98 return_val.custom_icon = Some(String::from(icon_str));
99 }
100 if let Some(title_str) = custom_title {
101 return_val.custom_title = Some(String::from(title_str));
102 }
103 if let Some(custom_level) = custom_level {
104 return_val.custom_level = Some(custom_level);
105 }
106 return return_val;
107 }
108
109 /// Registers a [`VisualPanic`] globally, i.e., for the whole application.
110 /// Returns currently nothing.
111 /// Will panic, if handling the &[`std::panic::PanicInfo`] fails in any way or the native message dialog can not be spawned.
112 pub fn register_global(self) {
113
114 let clone = self.clone();
115
116 std::panic::set_hook(Box::new(move |panic_info| {
117 let mut level: native_dialog::MessageType = native_dialog::MessageType::Error;
118 let mut icon: Option<String> = None;
119 let mut title = String::from(env!("CARGO_PKG_NAME"));
120 let payload_as_str = panic_info.payload().downcast_ref::<&str>().expect("Failed to extract Panic Payload to &str.");
121 let location = panic_info.location().expect("Failed to get location where the panic occured.");
122 let message = panic_info.message().expect("Failed to extract message.").to_string();
123 let location_str = format!("File: {} at Line: {}, Column: {}", location.file(), location.line(), location.column());
124 let display_message = format!("An error occured.\n({})\n\nPayload: {}\n\nMessage: {}\n\nThe program will terminate.\n", location_str, payload_as_str, message);
125
126 match &clone.custom_title {
127 None => {},
128 Some(custom) => { title = custom.clone() }
129 }
130 match &clone.custom_level {
131 None => {},
132 Some(custom) => {
133 match custom {
134 VisualPanicLevel::Error => { level = native_dialog::MessageType::Error }
135 VisualPanicLevel::Warning => { level = native_dialog::MessageType::Warning }
136 VisualPanicLevel::Info => { level = native_dialog::MessageType::Info }
137 }
138 }
139 }
140 match &clone.custom_icon {
141 None => {}
142 Some(custom) => { icon = Some(custom.clone()) }
143 }
144
145 let _ = native_dialog::MessageDialog::new()
146 .set_title(&title)
147 .set_type(level)
148 .set_text(&display_message)
149 .show_alert()
150 .expect("Failed to launch VisualPanic Dialog.");
151 }));
152 }
153}