use egui::{ComboBox, Id, Modal, ProgressBar, Ui, Widget as _, Window};
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct Modals {
user_modal_open: bool,
save_modal_open: bool,
save_progress: Option<f32>,
role: &'static str,
name: String,
}
impl Default for Modals {
fn default() -> Self {
Self {
user_modal_open: false,
save_modal_open: false,
save_progress: None,
role: Self::ROLES[0],
name: "John Doe".to_owned(),
}
}
}
impl Modals {
const ROLES: [&'static str; 2] = ["user", "admin"];
}
impl crate::Demo for Modals {
fn name(&self) -> &'static str {
"đź—– Modals"
}
fn show(&mut self, ui: &mut egui::Ui, open: &mut bool) {
use crate::View as _;
Window::new(self.name())
.open(open)
.vscroll(false)
.resizable(false)
.constrain_to(ui.available_rect_before_wrap())
.show(ui, |ui| self.ui(ui));
}
}
impl crate::View for Modals {
fn ui(&mut self, ui: &mut Ui) {
let Self {
user_modal_open,
save_modal_open,
save_progress,
role,
name,
} = self;
ui.horizontal(|ui| {
if ui.button("Open User Modal").clicked() {
*user_modal_open = true;
}
if ui.button("Open Save Modal").clicked() {
*save_modal_open = true;
}
});
ui.label("Click one of the buttons to open a modal.");
ui.label("Modals have a backdrop and prevent interaction with the rest of the UI.");
ui.label(
"You can show modals on top of each other and close the topmost modal with \
escape or by clicking outside the modal.",
);
if *user_modal_open {
let modal = Modal::new(Id::new("Modal A")).show(ui.ctx(), |ui| {
ui.set_width(250.0);
ui.heading("Edit User");
ui.label("Name:");
ui.text_edit_singleline(name);
ComboBox::new("role", "Role")
.selected_text(*role)
.show_ui(ui, |ui| {
for r in Self::ROLES {
ui.selectable_value(role, r, r);
}
});
ui.separator();
egui::Sides::new().show(
ui,
|_ui| {},
|ui| {
if ui.button("Save").clicked() {
*save_modal_open = true;
}
if ui.button("Cancel").clicked() {
ui.close();
}
},
);
});
if modal.should_close() {
*user_modal_open = false;
}
}
if *save_modal_open {
let modal = Modal::new(Id::new("Modal B")).show(ui.ctx(), |ui| {
ui.set_width(200.0);
ui.heading("Save? Are you sure?");
ui.add_space(32.0);
egui::Sides::new().show(
ui,
|_ui| {},
|ui| {
if ui.button("Yes Please").clicked() {
*save_progress = Some(0.0);
}
if ui.button("No Thanks").clicked() {
ui.close();
}
},
);
});
if modal.should_close() {
*save_modal_open = false;
}
}
if let Some(progress) = *save_progress {
Modal::new(Id::new("Modal C")).show(ui.ctx(), |ui| {
ui.set_width(70.0);
ui.heading("Saving…");
ProgressBar::new(progress).ui(ui);
if progress >= 1.0 {
*save_progress = None;
*save_modal_open = false;
*user_modal_open = false;
} else {
*save_progress = Some(progress + 0.003);
ui.request_repaint();
}
});
}
ui.vertical_centered(|ui| {
ui.add(crate::egui_github_link_file!());
});
}
}
#[cfg(test)]
mod tests {
use crate::Demo as _;
use crate::demo::modals::Modals;
use egui::accesskit::Role;
use egui::{Key, Popup};
use egui_kittest::kittest::Queryable as _;
use egui_kittest::{Harness, SnapshotResults};
#[test]
fn clicking_escape_when_popup_open_should_not_close_modal() {
let initial_state = Modals {
user_modal_open: true,
..Modals::default()
};
let mut harness = Harness::new_ui_state(
|ui, modals| {
modals.show(ui, &mut true);
},
initial_state,
);
harness.get_by_role(Role::ComboBox).click();
harness.run_ok();
assert!(Popup::is_any_open(&harness.ctx));
assert!(harness.state().user_modal_open);
harness.key_press(Key::Escape);
harness.run_ok();
assert!(!Popup::is_any_open(&harness.ctx));
assert!(harness.state().user_modal_open);
}
#[test]
fn escape_should_close_top_modal() {
let initial_state = Modals {
user_modal_open: true,
save_modal_open: true,
..Modals::default()
};
let mut harness = Harness::new_ui_state(
|ui, modals| {
modals.show(ui, &mut true);
},
initial_state,
);
assert!(harness.state().user_modal_open);
assert!(harness.state().save_modal_open);
harness.key_press(Key::Escape);
harness.run();
assert!(harness.state().user_modal_open);
assert!(!harness.state().save_modal_open);
}
#[test]
fn should_match_snapshot() {
let initial_state = Modals {
user_modal_open: true,
..Modals::default()
};
let mut harness = Harness::new_ui_state(
|ui, modals| {
modals.show(ui, &mut true);
},
initial_state,
);
let mut results = SnapshotResults::new();
harness.run();
results.add(harness.try_snapshot("modals_1"));
harness.get_by_label("Save").click();
harness.run_ok();
results.add(harness.try_snapshot("modals_2"));
harness.get_by_label("Yes Please").click();
harness.run_ok();
results.add(harness.try_snapshot("modals_3"));
}
#[test]
fn backdrop_should_prevent_focusing_lower_area() {
let initial_state = Modals {
save_modal_open: true,
save_progress: Some(0.0),
..Modals::default()
};
let mut harness = Harness::new_ui_state(
|ui, modals| {
modals.show(ui, &mut true);
},
initial_state,
);
harness.run_ok();
harness.get_by_label("Yes Please").click();
harness.run_ok();
harness.snapshot("modals_backdrop_should_prevent_focusing_lower_area");
}
}