intelli_shell/widgets/error.rs
1use ratatui::{Frame, layout::Rect, style::Style, text::Text, widgets::Clear};
2
3use crate::config::Theme;
4
5/// The number of ticks an error message will be displayed.
6///
7/// Calculated as 3 seconds * 4 ticks per second.
8const ERROR_MESSAGE_DISPLAY_TICKS: u16 = 3 * 4;
9
10/// Represents a popup for displaying error messages temporarily.
11///
12/// The error message is rendered at the bottom of a given area and disappears after a predefined timeout.
13pub struct ErrorPopup<'a> {
14 style: Style,
15 message: Option<Text<'a>>,
16 timeout_ticks: Option<u16>,
17}
18
19impl<'a> ErrorPopup<'a> {
20 /// Creates a new, empty [`ErrorPopup`]
21 pub fn empty(theme: &Theme) -> Self {
22 Self {
23 style: theme.error.into(),
24 message: None,
25 timeout_ticks: None,
26 }
27 }
28
29 /// Sets or replaces the error message to be permanently displayed.
30 ///
31 /// The message will remain displayed until a temporary message is set ot the message is cleared.
32 pub fn set_perm_message(&mut self, message: impl Into<Text<'a>>) {
33 self.message = Some(message.into().centered().style(self.style));
34 }
35
36 /// Sets or replaces the error message to be temporarily displayed.
37 ///
38 /// The message will remain visible for a short period of time, after which it will disappear.
39 pub fn set_temp_message(&mut self, message: impl Into<Text<'a>>) {
40 self.message = Some(message.into().centered().style(self.style));
41 self.timeout_ticks.replace(ERROR_MESSAGE_DISPLAY_TICKS);
42 }
43
44 /// Clears the error message.
45 pub fn clear_message(&mut self) {
46 self.message = None;
47 self.timeout_ticks = None;
48 }
49
50 /// Advances the state of the error popup by one tick.
51 ///
52 /// If an error message is currently displayed, its timeout is decremented.
53 /// If the timeout reaches zero, the message is cleared.
54 pub fn tick(&mut self) {
55 // Check if there's an active timer for the error message
56 if let Some(mut remaining_ticks) = self.timeout_ticks {
57 if remaining_ticks > 0 {
58 remaining_ticks -= 1;
59 self.timeout_ticks.replace(remaining_ticks);
60 } else {
61 // Timer has expired, clear the error message and the timer
62 self.message = None;
63 self.timeout_ticks = None;
64 }
65 }
66 }
67
68 /// Renders the error message popup within the given area if a message is active.
69 ///
70 /// The message is displayed as an overlay on the last line of the specified `area`.
71 pub fn render_in(&mut self, frame: &mut Frame, area: Rect) {
72 // Render the error message as an overlay, if it exists
73 if let Some(ref text) = self.message {
74 // Define the rectangle for the error message overlay: the last line of the component's total area
75 let error_overlay_rect = Rect {
76 x: area.x,
77 y: area.bottom() - 1,
78 width: area.width,
79 height: 1,
80 };
81
82 // Clear the area behind it and render it
83 frame.render_widget(Clear, error_overlay_rect);
84 frame.render_widget(text, error_overlay_rect);
85 }
86 }
87}