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}