egui_components/
dialog.rs1use egui::{Frame, Id, Margin, Sense};
23use egui_components_theme::Theme;
24
25use crate::button::Button;
26use crate::common::{Size, Variant};
27use crate::icon::{paint_icon, IconKind};
28use crate::label::Label;
29
30pub struct Dialog {
32 title: String,
33 width: f32,
34 closable: bool,
35}
36
37impl Dialog {
38 pub fn new(title: impl Into<String>) -> Self {
39 Self {
40 title: title.into(),
41 width: 420.0,
42 closable: true,
43 }
44 }
45 pub fn width(mut self, w: f32) -> Self {
46 self.width = w;
47 self
48 }
49 pub fn no_close_button(mut self) -> Self {
51 self.closable = false;
52 self
53 }
54
55 pub fn show<R>(
58 self,
59 ctx: &egui::Context,
60 open: &mut bool,
61 body: impl FnOnce(&mut egui::Ui) -> R,
62 ) -> Option<R> {
63 if !*open {
64 return None;
65 }
66 let theme = Theme::get(ctx);
67 let c = theme.colors;
68 let width = self.width;
69
70 let frame = Frame::new()
71 .fill(c.popover_background)
72 .stroke(theme.border_stroke())
73 .corner_radius(theme.corner_lg())
74 .inner_margin(Margin::same(20))
75 .shadow(egui::epaint::Shadow {
76 offset: [0, 8],
77 blur: 32,
78 spread: 0,
79 color: c.overlay,
80 });
81
82 let modal = egui::Modal::new(Id::new(("sc-dialog", self.title.as_str())))
83 .frame(frame)
84 .backdrop_color(c.overlay);
85
86 let title = self.title;
87 let closable = self.closable;
88 let res = modal.show(ctx, |ui| {
89 ui.set_width(width);
90 let mut close_requested = false;
91 ui.horizontal(|ui| {
92 ui.add(Label::new(title.clone()).strong().size(Size::Large));
93 if closable {
94 ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
95 let (rect, resp) =
96 ui.allocate_exact_size(egui::vec2(20.0, 20.0), Sense::click());
97 let col = if resp.hovered() {
98 c.foreground
99 } else {
100 c.muted_foreground
101 };
102 paint_icon(ui.painter(), IconKind::Close, rect, col, 1.6);
103 if resp.hovered() {
104 ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
105 }
106 if resp.clicked() {
107 close_requested = true;
108 }
109 });
110 }
111 });
112 ui.add_space(12.0);
113 let inner = body(ui);
114 (inner, close_requested)
115 });
116
117 let should_close = res.should_close();
118 let (inner, close_requested) = res.inner;
119 if should_close || close_requested {
120 *open = false;
121 }
122 Some(inner)
123 }
124}
125
126#[derive(Clone, Copy, Debug, PartialEq, Eq)]
128pub enum AlertChoice {
129 Confirm,
130 Cancel,
131}
132
133pub struct AlertDialog {
135 title: String,
136 description: Option<String>,
137 confirm_label: String,
138 cancel_label: String,
139 confirm_variant: Variant,
140 width: f32,
141}
142
143impl AlertDialog {
144 pub fn new(title: impl Into<String>) -> Self {
145 Self {
146 title: title.into(),
147 description: None,
148 confirm_label: "Continue".to_string(),
149 cancel_label: "Cancel".to_string(),
150 confirm_variant: Variant::Primary,
151 width: 380.0,
152 }
153 }
154 pub fn description(mut self, d: impl Into<String>) -> Self {
155 self.description = Some(d.into());
156 self
157 }
158 pub fn confirm_label(mut self, s: impl Into<String>) -> Self {
159 self.confirm_label = s.into();
160 self
161 }
162 pub fn cancel_label(mut self, s: impl Into<String>) -> Self {
163 self.cancel_label = s.into();
164 self
165 }
166 pub fn danger(mut self) -> Self {
168 self.confirm_variant = Variant::Danger;
169 self
170 }
171 pub fn width(mut self, w: f32) -> Self {
172 self.width = w;
173 self
174 }
175
176 pub fn show(self, ctx: &egui::Context, open: &mut bool) -> Option<AlertChoice> {
179 if !*open {
180 return None;
181 }
182 let theme = Theme::get(ctx);
183 let c = theme.colors;
184 let width = self.width;
185
186 let frame = Frame::new()
187 .fill(c.popover_background)
188 .stroke(theme.border_stroke())
189 .corner_radius(theme.corner_lg())
190 .inner_margin(Margin::same(20))
191 .shadow(egui::epaint::Shadow {
192 offset: [0, 8],
193 blur: 32,
194 spread: 0,
195 color: c.overlay,
196 });
197
198 let modal = egui::Modal::new(Id::new(("sc-alert", self.title.as_str())))
199 .frame(frame)
200 .backdrop_color(c.overlay);
201
202 let AlertDialog {
203 title,
204 description,
205 confirm_label,
206 cancel_label,
207 confirm_variant,
208 ..
209 } = self;
210
211 let res = modal.show(ctx, |ui| {
212 ui.set_width(width);
213 ui.add(Label::new(title.clone()).strong().size(Size::Large));
214 if let Some(desc) = &description {
215 ui.add_space(6.0);
216 ui.add(Label::new(desc.clone()).muted());
217 }
218 ui.add_space(18.0);
219 let mut choice = None;
220 ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
221 if ui
222 .add(Button::new(confirm_label.clone()).variant(confirm_variant))
223 .clicked()
224 {
225 choice = Some(AlertChoice::Confirm);
226 }
227 ui.add_space(8.0);
228 if ui.add(Button::secondary(cancel_label.clone())).clicked() {
229 choice = Some(AlertChoice::Cancel);
230 }
231 });
232 choice
233 });
234
235 let choice = res.inner;
236 if let Some(ch) = choice {
237 *open = false;
238 return Some(ch);
239 }
240 if res.should_close() {
241 *open = false;
242 return Some(AlertChoice::Cancel);
243 }
244 None
245 }
246}