Skip to main content

egui_charts/ui_kit/notifications/
toast.rs

1//! Toast Struct
2//!
3//! A non-blocking notification that appears and optionally auto-dismisses.
4
5use std::sync::atomic::{AtomicU64, Ordering};
6
7use egui::Color32;
8
9use crate::icons::{Icon, icons};
10use crate::theme::UiColors;
11
12/// Counter for generating unique toast IDs
13static TOAST_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
14
15/// Generate a unique toast ID
16fn next_toast_id() -> u64 {
17    TOAST_ID_COUNTER.fetch_add(1, Ordering::Relaxed)
18}
19
20/// Toast severity/type
21#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
22pub enum ToastKind {
23    /// Informational message (blue)
24    #[default]
25    Info,
26    /// Success message (green)
27    Success,
28    /// Warning message (amber)
29    Warning,
30    /// Error message (red)
31    Error,
32}
33
34impl ToastKind {
35    /// The theme's status colour for this kind, which tints the icon and the progress bar
36    pub fn color(&self, ui: &UiColors) -> Color32 {
37        match self {
38            ToastKind::Info => ui.info,
39            ToastKind::Success => ui.success,
40            ToastKind::Warning => ui.warning,
41            ToastKind::Error => ui.error,
42        }
43    }
44
45    /// Get the icon for this toast kind
46    pub fn icon(&self) -> &'static Icon {
47        match self {
48            ToastKind::Info => &icons::INFO,
49            ToastKind::Success => &icons::SUCCESS,
50            ToastKind::Warning => &icons::WARNING,
51            ToastKind::Error => &icons::STATUS_ERROR,
52        }
53    }
54}
55
56/// Structured details of an order notification, rendered as symbol and side chips.
57#[derive(Clone, Debug)]
58pub struct TradingToast {
59    /// Instrument identifier.
60    pub symbol: String,
61    /// URI of the symbol logo, for egui's installed image loaders. Without one, or while it is
62    /// loading or unreadable, the chip shows the symbol's initial.
63    pub logo: Option<String>,
64    /// Buy or sell.
65    pub side: crate::model::OrderSide,
66    /// Filled or placed quantity.
67    pub quantity: f64,
68    /// Price formatted at the instrument's precision.
69    pub price: String,
70    /// Executed orders have a check; placements have a side chevron.
71    pub executed: bool,
72}
73
74/// A toast notification
75#[derive(Clone, Debug)]
76pub struct Toast {
77    /// Unique identifier
78    pub id: u64,
79    /// Toast kind/severity
80    pub kind: ToastKind,
81    /// Message to display
82    pub message: String,
83    /// Optional title
84    pub title: Option<String>,
85    /// Optional structured order details.
86    pub trading: Option<TradingToast>,
87    /// Duration in seconds (0 = permanent until dismissed)
88    pub duration: f32,
89    /// Timestamp when the toast was created (in seconds since app start)
90    pub created_at: f64,
91    /// Whether the toast can be dismissed by clicking
92    pub dismissible: bool,
93}
94
95impl Toast {
96    /// Create a new toast
97    pub fn new(kind: ToastKind, message: impl Into<String>) -> Self {
98        Self {
99            id: next_toast_id(),
100            kind,
101            message: message.into(),
102            title: None,
103            trading: None,
104            duration: 5.0, // Default 5 seconds
105            created_at: 0.0,
106            dismissible: true,
107        }
108    }
109
110    /// Create an info toast
111    pub fn info(message: impl Into<String>) -> Self {
112        Self::new(ToastKind::Info, message)
113    }
114
115    /// Create a success toast
116    pub fn success(message: impl Into<String>) -> Self {
117        Self::new(ToastKind::Success, message)
118    }
119
120    /// Create a warning toast
121    pub fn warning(message: impl Into<String>) -> Self {
122        Self::new(ToastKind::Warning, message)
123    }
124
125    /// Create an error toast
126    pub fn error(message: impl Into<String>) -> Self {
127        Self::new(ToastKind::Error, message)
128    }
129
130    /// Set the title
131    pub fn with_title(mut self, title: impl Into<String>) -> Self {
132        self.title = Some(title.into());
133        self
134    }
135
136    /// Set the duration in seconds (0 = permanent)
137    pub fn duration(mut self, seconds: f32) -> Self {
138        self.duration = seconds;
139        self
140    }
141
142    /// Make the toast permanent (won't auto-dismiss)
143    pub fn permanent(mut self) -> Self {
144        self.duration = 0.0;
145        self
146    }
147
148    /// Set whether the toast can be dismissed by clicking
149    pub fn dismissible(mut self, dismissible: bool) -> Self {
150        self.dismissible = dismissible;
151        self
152    }
153
154    /// Check if the toast has expired
155    pub fn is_expired(&self, current_time: f64) -> bool {
156        if self.duration <= 0.0 {
157            return false; // Permanent toast
158        }
159        current_time - self.created_at >= self.duration as f64
160    }
161
162    /// Get the remaining time as a fraction (1.0 = full, 0.0 = expired)
163    pub fn remaining_fraction(&self, current_time: f64) -> f32 {
164        if self.duration <= 0.0 {
165            return 1.0; // Permanent toast
166        }
167        let elapsed = current_time - self.created_at;
168        (1.0 - (elapsed / self.duration as f64) as f32).clamp(0.0, 1.0)
169    }
170}