use egui::{Align2, Id, Order, ScrollArea, Ui, Vec2, Widget};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use crate::Alert;
#[derive(Debug)]
pub struct AlertManager<'a> {
pub unique_key: String,
pub alerts: &'a mut Vec<Alert>,
pub inner_margin: i8,
pub outer_margin: i8,
pub corner_radius: u8,
pub width: Option<f32>,
pub can_close: bool,
pub anchor: Align2,
pub anchor_offset: Option<Vec2>,
pub max_height: Option<f32>,
}
impl Hash for AlertManager<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.unique_key.hash(state);
for a in self.alerts.iter() {
a.hash(state);
}
self.inner_margin.hash(state);
self.outer_margin.hash(state);
self.corner_radius.hash(state);
self.width.unwrap_or(-1.0).to_bits().hash(state);
self.can_close.hash(state);
self.anchor.hash(state);
self.anchor_offset
.unwrap_or(Vec2::ZERO)
.to_string()
.hash(state);
self.max_height.unwrap_or(-1.0).to_bits().hash(state); }
}
impl<'a> AlertManager<'a> {
pub fn new(alerts: &'a mut Vec<Alert>, unique_key: &str) -> Self {
Self {
unique_key: format!("alert_manager_{}", unique_key),
alerts,
inner_margin: 10,
outer_margin: 1,
corner_radius: 4,
width: None,
can_close: true,
anchor: Align2::CENTER_TOP, anchor_offset: None,
max_height: None,
}
}
pub fn inner_margin(mut self, margin: i8) -> Self {
self.inner_margin = margin;
self
}
pub fn outer_margin(mut self, margin: i8) -> Self {
self.outer_margin = margin;
self
}
pub fn corner_radius(mut self, radius: u8) -> Self {
self.corner_radius = radius;
self
}
pub fn width(mut self, width: f32) -> Self {
self.width = Some(width);
self
}
pub fn can_close(mut self, can_close: bool) -> Self {
self.can_close = can_close;
self
}
pub fn anchor(mut self, anchor: Align2) -> Self {
let is_valid = matches!(
anchor,
Align2::LEFT_TOP
| Align2::CENTER_TOP
| Align2::RIGHT_TOP
| Align2::LEFT_BOTTOM
| Align2::CENTER_BOTTOM
| Align2::RIGHT_BOTTOM
);
assert!(
is_valid,
"Invalid anchor position: {:?}. We only support top or bottom anchors.",
anchor
);
self.anchor = anchor;
self
}
pub fn anchor_offset(mut self, offset: Vec2) -> Self {
self.anchor_offset = Some(offset);
self
}
pub fn max_height(mut self, max_height: f32) -> Self {
self.max_height = Some(max_height);
self
}
}
impl<'a> Widget for AlertManager<'a> {
fn ui(self, ui: &mut Ui) -> egui::Response {
let parent_area = ui.max_rect();
let mut to_remove = Vec::new();
let hasher_id = Id::new(format!("alert_manager_hash_{}", self.unique_key));
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
let hash = hasher.finish();
let old_hash = ui.ctx().memory(|mem| mem.data.get_temp::<u64>(hasher_id));
ui.ctx().memory_mut(|mem| {
mem.data.insert_temp(hasher_id, hash);
});
let max_height = self
.max_height
.unwrap_or(parent_area.height())
.clamp(1.0, parent_area.height());
let max_width = self
.width
.unwrap_or(parent_area.width())
.clamp(1.0, parent_area.width());
let id: Id = Id::new(self.unique_key.clone());
egui::Area::new(id)
.order(Order::Foreground)
.anchor(self.anchor, self.anchor_offset.unwrap_or(Vec2::ZERO))
.constrain_to(parent_area)
.default_size(Vec2::new(max_width, max_height))
.sizing_pass(old_hash.is_some() && old_hash.unwrap() != hash)
.show(ui.ctx(), |ui| {
if !ui.is_enabled() && !ui.is_visible() {
for alert in self.alerts.iter() {
let mut new_alert = alert
.clone()
.inner_margin(self.inner_margin)
.outer_margin(self.outer_margin)
.corner_radius(self.corner_radius)
.can_close(self.can_close);
if self.width.is_some() {
new_alert = new_alert.width(self.width.unwrap());
}
ui.add(new_alert);
}
} else {
let is_bottom = self.anchor == Align2::LEFT_BOTTOM
|| self.anchor == Align2::CENTER_BOTTOM
|| self.anchor == Align2::RIGHT_BOTTOM;
let scroll_resp = ScrollArea::both()
.stick_to_bottom(is_bottom)
.max_height(max_height)
.max_width(max_width)
.show(ui, |ui| {
let alert_iter: Box<dyn Iterator<Item = (usize, &Alert)>> = if is_bottom
{
Box::new(self.alerts.iter().enumerate())
} else {
Box::new(self.alerts.iter().enumerate().rev())
};
for (idx, alert) in alert_iter {
let mut new_alert = alert
.clone()
.inner_margin(self.inner_margin)
.outer_margin(self.outer_margin)
.corner_radius(self.corner_radius)
.can_close(self.can_close);
if self.width.is_some() {
new_alert = new_alert.width(self.width.unwrap());
}
let resp = ui.add(new_alert);
if self.can_close && resp.clicked() {
to_remove.push(idx);
}
}
for idx in to_remove.into_iter().rev() {
self.alerts.remove(idx);
}
});
scroll_resp.inner
}
})
.response
}
}
pub fn alert_manager<'a>(alerts: &'a mut Vec<Alert>, unique_key: &str) -> AlertManager<'a> {
AlertManager::new(alerts, unique_key)
}