1use emath::{Align2, Vec2};
2
3use crate::{
4 Area, Color32, Context, Frame, Id, InnerResponse, Order, Response, Sense, Ui, UiBuilder, UiKind,
5};
6
7pub struct Modal {
17 pub area: Area,
18 pub backdrop_color: Color32,
19 pub frame: Option<Frame>,
20}
21
22impl Modal {
23 pub fn new(id: Id) -> Self {
27 Self {
28 area: Self::default_area(id),
29 backdrop_color: Color32::from_black_alpha(100),
30 frame: None,
31 }
32 }
33
34 pub fn default_area(id: Id) -> Area {
41 Area::new(id)
42 .kind(UiKind::Modal)
43 .sense(Sense::hover())
44 .anchor(Align2::CENTER_CENTER, Vec2::ZERO)
45 .order(Order::Foreground)
46 .interactable(true)
47 }
48
49 #[inline]
53 pub fn frame(mut self, frame: Frame) -> Self {
54 self.frame = Some(frame);
55 self
56 }
57
58 #[inline]
62 pub fn backdrop_color(mut self, color: Color32) -> Self {
63 self.backdrop_color = color;
64 self
65 }
66
67 #[inline]
71 pub fn area(mut self, area: Area) -> Self {
72 self.area = area;
73 self
74 }
75
76 pub fn show<T>(self, ctx: &Context, content: impl FnOnce(&mut Ui) -> T) -> ModalResponse<T> {
78 let Self {
79 area,
80 backdrop_color,
81 frame,
82 } = self;
83
84 let is_top_modal = ctx.memory_mut(|mem| {
85 mem.set_modal_layer(area.layer());
86 mem.top_modal_layer() == Some(area.layer())
87 });
88 let any_popup_open = crate::Popup::is_any_open(ctx);
89 let InnerResponse {
90 inner: (inner, backdrop_response),
91 response,
92 } = area.show(ctx, |ui| {
93 let bg_rect = ui.ctx().screen_rect();
94 let bg_sense = Sense::CLICK | Sense::DRAG;
95 let mut backdrop = ui.new_child(UiBuilder::new().sense(bg_sense).max_rect(bg_rect));
96 backdrop.set_min_size(bg_rect.size());
97 ui.painter().rect_filled(bg_rect, 0.0, backdrop_color);
98 let backdrop_response = backdrop.response();
99
100 let frame = frame.unwrap_or_else(|| Frame::popup(ui.style()));
101
102 let inner = ui
105 .scope_builder(UiBuilder::new().sense(Sense::CLICK | Sense::DRAG), |ui| {
106 frame.show(ui, content).inner
107 })
108 .inner;
109
110 (inner, backdrop_response)
111 });
112
113 ModalResponse {
114 response,
115 backdrop_response,
116 inner,
117 is_top_modal,
118 any_popup_open,
119 }
120 }
121}
122
123pub struct ModalResponse<T> {
125 pub response: Response,
127
128 pub backdrop_response: Response,
133
134 pub inner: T,
136
137 pub is_top_modal: bool,
139
140 pub any_popup_open: bool,
144}
145
146impl<T> ModalResponse<T> {
147 pub fn should_close(&self) -> bool {
152 let ctx = &self.response.ctx;
153
154 let escape_clicked =
156 || ctx.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape));
157
158 let ui_close_called = self.response.should_close();
159
160 self.backdrop_response.clicked()
161 || ui_close_called
162 || (self.is_top_modal && !self.any_popup_open && escape_clicked())
163 }
164}