use egui::{Context, Id, Ui};
pub struct ModalHandler<T = ()> {
id: Id,
open: bool,
payload: Option<T>,
}
impl<T> Default for ModalHandler<T> {
fn default() -> Self {
Self {
id: Id::NULL,
open: false,
payload: None,
}
}
}
impl<T> ModalHandler<T> {
pub fn new(id: impl std::hash::Hash) -> Self {
Self {
id: Id::new(id),
open: false,
payload: None,
}
}
pub fn id(&self) -> Id {
self.id
}
pub fn open(&mut self) {
self.open = true;
}
pub fn open_with(&mut self, payload: T) {
self.open = true;
self.payload = Some(payload);
}
pub fn close(&mut self) {
self.open = false;
self.payload = None;
}
pub fn is_open(&self) -> bool {
self.open
}
pub fn payload(&self) -> Option<&T> {
self.payload.as_ref()
}
pub fn payload_mut(&mut self) -> Option<&mut T> {
self.payload.as_mut()
}
pub fn take_payload(&mut self) -> Option<T> {
self.payload.take()
}
pub fn show<R>(
&mut self,
ctx: &Context,
add_contents: impl FnOnce(&mut Ui, Option<&T>) -> R,
) -> Option<R> {
if !self.open {
return None;
}
let mut result = None;
egui::Area::new(self.id)
.fixed_pos(egui::pos2(0.0, 0.0))
.show(ctx, |ui| {
let screen_rect = ctx.content_rect();
let dim_color = egui::Color32::from_black_alpha(180);
ui.painter().rect_filled(screen_rect, 0.0, dim_color);
let backdrop_response = ui.allocate_rect(screen_rect, egui::Sense::click());
if backdrop_response.clicked() {
self.close();
return;
}
let modal_rect = egui::Rect::from_center_size(
screen_rect.center(),
egui::vec2(
crate::tokens::DESIGN_TOKENS.sizing.dialog.default_width,
crate::tokens::DESIGN_TOKENS.sizing.dialog.default_height,
),
);
let mut child_ui = ui.new_child(egui::UiBuilder::new().max_rect(modal_rect));
result = Some(add_contents(&mut child_ui, self.payload.as_ref()));
});
result
}
pub fn show_mut<R>(
&mut self,
ctx: &Context,
add_contents: impl FnOnce(&mut Ui, Option<&mut T>) -> R,
) -> Option<R> {
if !self.open {
return None;
}
let mut result = None;
egui::Area::new(self.id)
.fixed_pos(egui::pos2(0.0, 0.0))
.show(ctx, |ui| {
let screen_rect = ctx.content_rect();
let dim_color = egui::Color32::from_black_alpha(180);
ui.painter().rect_filled(screen_rect, 0.0, dim_color);
let backdrop_response = ui.allocate_rect(screen_rect, egui::Sense::click());
if backdrop_response.clicked() {
self.close();
return;
}
let modal_rect = egui::Rect::from_center_size(
screen_rect.center(),
egui::vec2(
crate::tokens::DESIGN_TOKENS.sizing.dialog.default_width,
crate::tokens::DESIGN_TOKENS.sizing.dialog.default_height,
),
);
let mut child_ui = ui.new_child(egui::UiBuilder::new().max_rect(modal_rect));
result = Some(add_contents(&mut child_ui, self.payload.as_mut()));
});
result
}
}
impl ModalHandler<()> {
pub fn show_simple<R>(
&mut self,
ctx: &Context,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
self.show(ctx, |ui, _| add_contents(ui))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_modal_handler_lifecycle() {
let mut handler: ModalHandler<String> = ModalHandler::new("test_modal");
assert!(!handler.is_open());
assert!(handler.payload().is_none());
handler.open_with("test_payload".to_string());
assert!(handler.is_open());
assert_eq!(handler.payload(), Some(&"test_payload".to_string()));
handler.close();
assert!(!handler.is_open());
assert!(handler.payload().is_none());
}
}