egui_notify/
toast.rs

1use crate::{Anchor, TOAST_HEIGHT, TOAST_WIDTH};
2use egui::{pos2, vec2, Color32, Pos2, Rect, WidgetText};
3use std::{fmt::Debug, time::Duration};
4
5/// Level of importance
6#[derive(Debug, Default, Clone, PartialEq, Eq)]
7#[allow(missing_docs)]
8pub enum ToastLevel {
9    #[default]
10    Info,
11    Warning,
12    Error,
13    Success,
14    None,
15    Custom(String, Color32),
16}
17
18#[derive(Debug)]
19/// State of the toast
20pub enum ToastState {
21    /// Toast is appearing
22    Appear,
23    /// Toast is disappearing
24    Disappear,
25    /// Toast has disappeared
26    Disappeared,
27    /// Toast is idling
28    Idle,
29}
30
31impl ToastState {
32    /// Returns `true` if the toast is appearing
33    pub const fn appearing(&self) -> bool {
34        matches!(self, Self::Appear)
35    }
36
37    /// Returns `true` if the toast is disappearing
38    pub const fn disappearing(&self) -> bool {
39        matches!(self, Self::Disappear)
40    }
41
42    /// Returns `true` if the toast has disappeared
43    pub const fn disappeared(&self) -> bool {
44        matches!(self, Self::Disappeared)
45    }
46
47    /// Returns `true` if the toast is idling
48    pub const fn idling(&self) -> bool {
49        matches!(self, Self::Idle)
50    }
51}
52
53/// Container for options for initlizing toasts
54pub struct ToastOptions {
55    duration: Option<Duration>,
56    level: ToastLevel,
57    closable: bool,
58    show_progress_bar: bool,
59}
60
61/// Single notification or *toast*
62pub struct Toast {
63    pub(crate) level: ToastLevel,
64    pub(crate) caption: WidgetText,
65    // (initial, current)
66    pub(crate) duration: Option<(f32, f32)>,
67    pub(crate) height: f32,
68    pub(crate) width: f32,
69    pub(crate) closable: bool,
70    pub(crate) show_progress_bar: bool,
71
72    pub(crate) state: ToastState,
73    pub(crate) value: f32,
74}
75
76impl Default for ToastOptions {
77    fn default() -> Self {
78        Self {
79            duration: Some(Duration::from_millis(3500)),
80            level: ToastLevel::None,
81            closable: true,
82            show_progress_bar: true,
83        }
84    }
85}
86
87fn duration_to_seconds_f32(duration: Duration) -> f32 {
88    duration.as_nanos() as f32 * 1e-9
89}
90
91impl Toast {
92    fn new(caption: impl Into<WidgetText>, options: ToastOptions) -> Self {
93        Self {
94            caption: caption.into(),
95            height: TOAST_HEIGHT,
96            width: TOAST_WIDTH,
97            duration: options.duration.map(|dur| {
98                let max_dur = duration_to_seconds_f32(dur);
99                (max_dur, max_dur)
100            }),
101            closable: options.closable,
102            show_progress_bar: options.show_progress_bar,
103            level: options.level,
104            value: 0.,
105            state: ToastState::Appear,
106        }
107    }
108
109    /// Creates new basic toast, can be closed by default.
110    pub fn basic(caption: impl Into<WidgetText>) -> Self {
111        Self::new(caption, ToastOptions::default())
112    }
113
114    /// Creates new success toast, can be closed by default.
115    pub fn success(caption: impl Into<WidgetText>) -> Self {
116        Self::new(
117            caption,
118            ToastOptions {
119                level: ToastLevel::Success,
120                ..ToastOptions::default()
121            },
122        )
123    }
124
125    /// Creates new info toast, can be closed by default.
126    pub fn info(caption: impl Into<WidgetText>) -> Self {
127        Self::new(
128            caption,
129            ToastOptions {
130                level: ToastLevel::Info,
131                ..ToastOptions::default()
132            },
133        )
134    }
135
136    /// Creates new warning toast, can be closed by default.
137    pub fn warning(caption: impl Into<WidgetText>) -> Self {
138        Self::new(
139            caption,
140            ToastOptions {
141                level: ToastLevel::Warning,
142                ..ToastOptions::default()
143            },
144        )
145    }
146
147    /// Creates new error toast, can not be closed by default.
148    pub fn error(caption: impl Into<WidgetText>) -> Self {
149        Self::new(
150            caption,
151            ToastOptions {
152                closable: false,
153                level: ToastLevel::Error,
154                ..ToastOptions::default()
155            },
156        )
157    }
158
159    /// Creates new custom toast, can be closed by default.
160    pub fn custom(caption: impl Into<WidgetText>, level: ToastLevel) -> Self {
161        Self::new(
162            caption,
163            ToastOptions {
164                level,
165                ..ToastOptions::default()
166            },
167        )
168    }
169
170    /// Set the options with a [`ToastOptions`]
171    pub fn options(&mut self, options: ToastOptions) -> &mut Self {
172        self.closable(options.closable);
173        self.duration(options.duration);
174        self.level(options.level);
175        self
176    }
177
178    /// Change the level of the toast
179    pub fn level(&mut self, level: ToastLevel) -> &mut Self {
180        self.level = level;
181        self
182    }
183
184    /// Can the user close the toast?
185    pub fn closable(&mut self, closable: bool) -> &mut Self {
186        self.closable = closable;
187        self
188    }
189
190    /// Should a progress bar be shown?
191    pub fn show_progress_bar(&mut self, show_progress_bar: bool) -> &mut Self {
192        self.show_progress_bar = show_progress_bar;
193        self
194    }
195
196    /// In what time should the toast expire? Set to `None` for no expiry.
197    pub fn duration(&mut self, duration: impl Into<Option<Duration>>) -> &mut Self {
198        if let Some(duration) = duration.into() {
199            let max_dur = duration_to_seconds_f32(duration);
200            self.duration = Some((max_dur, max_dur));
201        } else {
202            self.duration = None;
203        }
204        self
205    }
206
207    /// Toast's box height
208    pub fn height(&mut self, height: f32) -> &mut Self {
209        self.height = height;
210        self
211    }
212
213    /// Toast's box width
214    pub fn width(&mut self, width: f32) -> &mut Self {
215        self.width = width;
216        self
217    }
218
219    /// Dismiss this toast
220    pub fn dismiss(&mut self) {
221        self.state = ToastState::Disappear;
222    }
223
224    pub(crate) fn calc_anchored_rect(&self, pos: Pos2, anchor: Anchor) -> Rect {
225        match anchor {
226            Anchor::TopRight => Rect {
227                min: pos2(pos.x - self.width, pos.y),
228                max: pos2(pos.x, pos.y + self.height),
229            },
230            Anchor::TopLeft => Rect {
231                min: pos,
232                max: pos + vec2(self.width, self.height),
233            },
234            Anchor::BottomRight => Rect {
235                min: pos - vec2(self.width, self.height),
236                max: pos,
237            },
238            Anchor::BottomLeft => Rect {
239                min: pos2(pos.x, pos.y - self.height),
240                max: pos2(pos.x + self.width, pos.y),
241            },
242        }
243    }
244
245    pub(crate) fn adjust_next_pos(&self, pos: &mut Pos2, anchor: Anchor, spacing: f32) {
246        match anchor {
247            Anchor::TopRight | Anchor::TopLeft => pos.y += self.height + spacing,
248            Anchor::BottomRight | Anchor::BottomLeft => pos.y -= self.height + spacing,
249        }
250    }
251}