egui_charts/ui_kit/alert/
mod.rs1use crate::localization as l10n;
27use egui::{Color32, Response, RichText, Ui, Vec2};
28
29use crate::icons::{Icon, icons};
30use crate::theme::{ThemeExt, UiColors};
31use crate::tokens::DESIGN_TOKENS;
32use crate::ui_kit::buttons::{ButtonSize, IconButton};
33
34#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
36pub enum AlertKind {
37 #[default]
39 Info,
40 Success,
42 Warning,
44 Error,
46}
47
48impl AlertKind {
49 pub fn color(&self, ui: &UiColors) -> Color32 {
51 match self {
52 AlertKind::Info => ui.info,
53 AlertKind::Success => ui.success,
54 AlertKind::Warning => ui.warning,
55 AlertKind::Error => ui.error,
56 }
57 }
58
59 pub fn icon(&self) -> &'static Icon {
61 match self {
62 AlertKind::Info => &icons::INFO,
63 AlertKind::Success => &icons::SUCCESS,
64 AlertKind::Warning => &icons::WARNING,
65 AlertKind::Error => &icons::STATUS_ERROR,
66 }
67 }
68}
69
70pub struct AlertResponse {
72 pub response: Response,
74 pub dismissed: bool,
76}
77
78pub struct Alert {
80 kind: AlertKind,
81 title: Option<String>,
82 message: String,
83 dismissible: bool,
84 show_icon: bool,
85}
86
87impl Alert {
88 pub fn new(kind: AlertKind, message: impl Into<String>) -> Self {
90 Self {
91 kind,
92 title: None,
93 message: message.into(),
94 dismissible: false,
95 show_icon: true,
96 }
97 }
98
99 pub fn info(message: impl Into<String>) -> Self {
101 Self::new(AlertKind::Info, message)
102 }
103
104 pub fn success(message: impl Into<String>) -> Self {
106 Self::new(AlertKind::Success, message)
107 }
108
109 pub fn warning(message: impl Into<String>) -> Self {
111 Self::new(AlertKind::Warning, message)
112 }
113
114 pub fn error(message: impl Into<String>) -> Self {
116 Self::new(AlertKind::Error, message)
117 }
118
119 pub fn with_title(mut self, title: impl Into<String>) -> Self {
121 self.title = Some(title.into());
122 self
123 }
124
125 pub fn dismissible(mut self, dismissible: bool) -> Self {
127 self.dismissible = dismissible;
128 self
129 }
130
131 pub fn show_icon(mut self, show: bool) -> Self {
133 self.show_icon = show;
134 self
135 }
136
137 pub fn show(self, ui: &mut Ui) -> AlertResponse {
139 let theme = ui.active_theme();
140 let color = self.kind.color(&theme.ui);
141
142 let frame = egui::Frame::new()
143 .fill(color.gamma_multiply(0.1))
144 .stroke(egui::Stroke::new(
145 DESIGN_TOKENS.stroke.hairline,
146 color.gamma_multiply(0.4),
147 ))
148 .corner_radius(theme.radius.md)
149 .inner_margin(egui::Margin::same(theme.spacing.lg as i8));
150
151 let mut dismissed = false;
152
153 let inner_response = frame.show(ui, |ui| {
154 ui.set_width(ui.available_width());
156 ui.horizontal(|ui| {
157 if self.show_icon {
159 let icon = self.kind.icon();
160 let icon_size = DESIGN_TOKENS.sizing.icon_md;
161 ui.add(icon.as_image_tinted(Vec2::splat(icon_size), color));
162 }
163
164 ui.vertical(|ui| {
166 let mut message_color = theme.ui.text;
168 if let Some(title) = &self.title {
169 ui.label(RichText::new(l10n::text(ui.ctx(), title)).strong());
170 ui.add_space(ui.active_theme().spacing.xs);
171 message_color = theme.ui.text_secondary;
172 }
173 ui.label(
174 RichText::new(l10n::text(ui.ctx(), &self.message)).color(message_color),
175 );
176 });
177
178 if self.dismissible {
180 ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
181 dismissed = IconButton::new(&icons::CLOSE)
182 .size(ButtonSize::SM)
183 .tooltip("Dismiss")
184 .show(ui)
185 .clicked();
186 });
187 }
188 });
189 });
190
191 AlertResponse {
192 response: inner_response.response,
193 dismissed,
194 }
195 }
196}