use egui::{Align2, Area, Color32, Frame, Id, Order, RichText, Ui, Vec2};
use crate::{icons, Theme};
pub struct Modal<'a> {
title: Option<&'a str>,
width: f32,
closable: bool,
}
impl<'a> Modal<'a> {
pub fn new() -> Self {
Self {
title: None,
width: 400.0,
closable: true,
}
}
pub fn titled(title: &'a str) -> Self {
Self {
title: Some(title),
width: 400.0,
closable: true,
}
}
pub fn width(mut self, width: f32) -> Self {
self.width = width;
self
}
pub fn closable(mut self, closable: bool) -> Self {
self.closable = closable;
self
}
pub fn show(self, ui: &mut Ui, open: bool, content: impl FnOnce(&mut Ui)) -> bool {
if !open {
return false;
}
let theme = Theme::current(ui.ctx());
let is_dark = ui.ctx().style().visuals.dark_mode;
let mut close_requested = false;
let screen_rect = ui.ctx().content_rect();
let backdrop_alpha = (theme.overlay_dim * 255.0) as u8;
let backdrop_color = Color32::from_rgba_unmultiplied(0, 0, 0, backdrop_alpha);
Area::new(Id::new("modal_backdrop"))
.fixed_pos(screen_rect.min)
.order(Order::Foreground)
.show(ui.ctx(), |ui| {
let response = ui.allocate_response(screen_rect.size(), egui::Sense::click());
ui.painter().rect_filled(screen_rect, 0.0, backdrop_color);
if self.closable && response.clicked() {
close_requested = true;
}
});
let bg_color = if is_dark {
Color32::from_rgb(31, 41, 55)
} else {
Color32::WHITE
};
let border_color = if is_dark {
Color32::from_rgb(55, 65, 81)
} else {
Color32::from_rgb(229, 231, 235)
};
Area::new(Id::new("modal_content"))
.anchor(Align2::CENTER_CENTER, Vec2::ZERO)
.order(Order::Tooltip)
.show(ui.ctx(), |ui| {
Frame::new()
.fill(bg_color)
.stroke(egui::Stroke::new(1.0, border_color))
.corner_radius(theme.radius_md)
.inner_margin(egui::Margin::same(theme.spacing_md as i8))
.show(ui, |ui| {
ui.set_width(self.width);
if self.title.is_some() || self.closable {
ui.horizontal(|ui| {
if let Some(title) = self.title {
ui.label(RichText::new(title).strong().size(18.0));
}
ui.with_layout(
egui::Layout::right_to_left(egui::Align::Center),
|ui| {
if self.closable {
let close_btn = RichText::new(icons::X)
.family(egui::FontFamily::Name("icons".into()))
.size(16.0);
if ui.button(close_btn).clicked() {
close_requested = true;
}
}
},
);
});
ui.add_space(theme.spacing_sm);
}
content(ui);
});
});
close_requested
}
}
impl<'a> Default for Modal<'a> {
fn default() -> Self {
Self::new()
}
}
pub struct ConfirmDialog<'a> {
title: &'a str,
message: &'a str,
confirm_text: &'a str,
cancel_text: &'a str,
danger: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfirmResult {
None,
Confirmed,
Cancelled,
}
impl<'a> ConfirmDialog<'a> {
pub fn new(title: &'a str, message: &'a str) -> Self {
Self {
title,
message,
confirm_text: "Confirm",
cancel_text: "Cancel",
danger: false,
}
}
pub fn confirm_text(mut self, text: &'a str) -> Self {
self.confirm_text = text;
self
}
pub fn cancel_text(mut self, text: &'a str) -> Self {
self.cancel_text = text;
self
}
pub fn danger(mut self) -> Self {
self.danger = true;
self
}
pub fn show(self, ui: &mut Ui, open: bool) -> ConfirmResult {
if !open {
return ConfirmResult::None;
}
use crate::atoms::Button;
let theme = Theme::current(ui.ctx());
let mut result = ConfirmResult::None;
let close_requested = Modal::titled(self.title).show(ui, open, |ui| {
ui.label(self.message);
ui.add_space(theme.spacing_md);
ui.horizontal(|ui| {
if self.danger {
if Button::danger(self.confirm_text).show(ui) {
result = ConfirmResult::Confirmed;
}
} else if Button::primary(self.confirm_text).show(ui) {
result = ConfirmResult::Confirmed;
}
if Button::outline(self.cancel_text).show(ui) {
result = ConfirmResult::Cancelled;
}
});
});
if close_requested && result == ConfirmResult::None {
result = ConfirmResult::Cancelled;
}
result
}
}