use crate::{
Area, Color32, Context, Frame, Id, InnerResponse, Order, Response, Sense, Ui, UiBuilder, UiKind,
};
use emath::{Align2, Vec2};
pub struct Modal {
pub area: Area,
pub backdrop_color: Color32,
pub frame: Option<Frame>,
}
impl Modal {
pub fn new(id: Id) -> Self {
Self {
area: Self::default_area(id),
backdrop_color: Color32::from_black_alpha(100),
frame: None,
}
}
pub fn default_area(id: Id) -> Area {
Area::new(id)
.kind(UiKind::Modal)
.sense(Sense::hover())
.anchor(Align2::CENTER_CENTER, Vec2::ZERO)
.order(Order::Foreground)
.interactable(true)
}
#[inline]
pub fn frame(mut self, frame: Frame) -> Self {
self.frame = Some(frame);
self
}
#[inline]
pub fn backdrop_color(mut self, color: Color32) -> Self {
self.backdrop_color = color;
self
}
#[inline]
pub fn area(mut self, area: Area) -> Self {
self.area = area;
self
}
pub fn show<T>(self, ctx: &Context, content: impl FnOnce(&mut Ui) -> T) -> ModalResponse<T> {
let Self {
area,
backdrop_color,
frame,
} = self;
let (is_top_modal, any_popup_open) = ctx.memory_mut(|mem| {
mem.set_modal_layer(area.layer());
(
mem.top_modal_layer() == Some(area.layer()),
mem.any_popup_open(),
)
});
let InnerResponse {
inner: (inner, backdrop_response),
response,
} = area.show(ctx, |ui| {
let bg_rect = ui.ctx().screen_rect();
let bg_sense = Sense {
click: true,
drag: true,
focusable: false,
};
let mut backdrop = ui.new_child(UiBuilder::new().sense(bg_sense).max_rect(bg_rect));
backdrop.set_min_size(bg_rect.size());
ui.painter().rect_filled(bg_rect, 0.0, backdrop_color);
let backdrop_response = backdrop.response();
let frame = frame.unwrap_or_else(|| Frame::popup(ui.style()));
let inner = ui
.scope_builder(
UiBuilder::new().sense(Sense {
click: true,
drag: true,
focusable: false,
}),
|ui| frame.show(ui, content).inner,
)
.inner;
(inner, backdrop_response)
});
ModalResponse {
response,
backdrop_response,
inner,
is_top_modal,
any_popup_open,
}
}
}
pub struct ModalResponse<T> {
pub response: Response,
pub backdrop_response: Response,
pub inner: T,
pub is_top_modal: bool,
pub any_popup_open: bool,
}
impl<T> ModalResponse<T> {
pub fn should_close(&self) -> bool {
let ctx = &self.response.ctx;
let escape_clicked =
|| ctx.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape));
self.backdrop_response.clicked()
|| (self.is_top_modal && !self.any_popup_open && escape_clicked())
}
}