egui_charts/ui_kit/modal/
modal_handler.rs1use egui::{Context, Id, Ui};
6
7use crate::theme::ThemeExt;
8use crate::ui_kit::dialog::dialog_frame;
9
10pub struct ModalHandler<T = ()> {
12 id: Id,
13 open: bool,
14 payload: Option<T>,
15}
16
17impl<T> Default for ModalHandler<T> {
18 fn default() -> Self {
19 Self {
20 id: Id::NULL,
21 open: false,
22 payload: None,
23 }
24 }
25}
26
27impl<T> ModalHandler<T> {
28 pub fn new(id: impl std::hash::Hash + std::fmt::Debug) -> Self {
30 Self {
31 id: Id::unique(id),
32 open: false,
33 payload: None,
34 }
35 }
36
37 pub fn id(&self) -> Id {
39 self.id
40 }
41
42 pub fn open(&mut self) {
44 self.open = true;
45 }
46
47 pub fn open_with(&mut self, payload: T) {
49 self.open = true;
50 self.payload = Some(payload);
51 }
52
53 pub fn close(&mut self) {
55 self.open = false;
56 self.payload = None;
57 }
58
59 pub fn is_open(&self) -> bool {
61 self.open
62 }
63
64 pub fn payload(&self) -> Option<&T> {
66 self.payload.as_ref()
67 }
68
69 pub fn payload_mut(&mut self) -> Option<&mut T> {
71 self.payload.as_mut()
72 }
73
74 pub fn take_payload(&mut self) -> Option<T> {
76 self.payload.take()
77 }
78
79 pub fn show<R>(
86 &mut self,
87 ctx: &Context,
88 add_contents: impl FnOnce(&mut Ui, Option<&T>) -> R,
89 ) -> Option<R> {
90 self.show_mut(ctx, |ui, payload| add_contents(ui, payload.map(|p| &*p)))
91 }
92
93 pub fn show_mut<R>(
95 &mut self,
96 ctx: &Context,
97 add_contents: impl FnOnce(&mut Ui, Option<&mut T>) -> R,
98 ) -> Option<R> {
99 if !self.open {
100 return None;
101 }
102
103 let response = egui::Modal::new(self.id)
104 .frame(dialog_frame(ctx))
105 .backdrop_color(ctx.active_theme().ui.modal_overlay)
106 .show(ctx, |ui| add_contents(ui, self.payload.as_mut()));
107 if response.should_close() {
108 self.close();
109 }
110 Some(response.inner)
111 }
112}
113
114impl ModalHandler<()> {
115 pub fn show_simple<R>(
117 &mut self,
118 ctx: &Context,
119 add_contents: impl FnOnce(&mut Ui) -> R,
120 ) -> Option<R> {
121 self.show(ctx, |ui, _| add_contents(ui))
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn modal_backdrops_follow_the_active_theme() {
131 use crate::theme::{Theme, apply_to_egui};
132 use crate::ui_kit::{command_palette::CommandPalette, modal::Modal};
133 use egui::{Color32, Pos2, RawInput, Rect, Shape, Vec2};
134 let screen = Rect::from_min_size(Pos2::ZERO, Vec2::new(1440.0, 900.0));
135 for (theme, alpha) in [
136 (Theme::dark(), 180),
137 (Theme::light(), 96),
138 (Theme::high_contrast(), 0),
139 ] {
140 for surface in 0..3 {
141 let ctx = Context::default();
142 apply_to_egui(&ctx, &theme);
143 ctx.all_styles_mut(|style| style.animation_time = 0.0);
144 let mut handler = ModalHandler::new(("backdrop", surface));
145 handler.open();
146 let mut palette = CommandPalette::new(Vec::new());
147 for frame in 0..3 {
148 let output = ctx.run_ui(
149 RawInput {
150 screen_rect: Some(screen),
151 ..Default::default()
152 },
153 |ui| match surface {
154 0 => {
155 Modal::new("backdrop", "Modal")
156 .show(ui.ctx(), &mut true, |ui| ui.label("Content"));
157 }
158 1 => {
159 handler.show_simple(ui.ctx(), |ui| ui.label("Content"));
160 }
161 _ => {
162 palette.show(ui.ctx(), &mut true);
163 }
164 },
165 );
166 let fills: Vec<_> = output
167 .shapes
168 .iter()
169 .filter_map(|shape| match &shape.shape {
170 Shape::Rect(rect)
171 if rect.rect == screen
172 && rect.fill.a() > 0
173 && (alpha == 0 || rect.fill.a() < 255) =>
174 {
175 Some(rect.fill)
176 }
177 _ => None,
178 })
179 .collect();
180 output.drop_without_applying_deltas();
181 let expected = if alpha == 0 || surface == 2 {
183 vec![]
184 } else {
185 vec![Color32::from_black_alpha(alpha)]
186 };
187 if frame == 2 {
188 assert_eq!(fills, expected, "dark: {}, surface: {surface}", theme.dark);
189 }
190 }
191 }
192 }
193 }
194
195 #[test]
196 fn test_modal_handler_lifecycle() {
197 let mut handler: ModalHandler<String> = ModalHandler::new("test_modal");
198
199 assert!(!handler.is_open());
200 assert!(handler.payload().is_none());
201
202 handler.open_with("test_payload".to_string());
203 assert!(handler.is_open());
204 assert_eq!(handler.payload(), Some(&"test_payload".to_string()));
205
206 handler.close();
207 assert!(!handler.is_open());
208 assert!(handler.payload().is_none());
209 }
210}