Skip to main content

bevy_egui_notify/
toast.rs

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