mirror of
https://github.com/emilk/egui.git
synced 2026-09-01 06:10:06 -04:00
⚠️ Improved menu based on egui::Popup (#5716)
Continuation of #5713 **Silently breaking changes:** - Menus now close on click by default, this is configurable via `PopupCloseBehavior` **Additional additions:** - `Button::right_text` - `StyleModifier` This is a rewrite of the egui menus, with the following goals: - submenu buttons should work everywhere, in a popup, context menu, area, in some random Ui - remove the menu state from Ui - should work just like the previous menu - ~proper support for keyboard navigation~ - It's better now but requires further work to be perfect - support `PopupCloseBehavior` * Closes #4607 * [x] I have followed the instructions in the PR template
This commit is contained in:
188
crates/egui_kittest/tests/menu.rs
Normal file
188
crates/egui_kittest/tests/menu.rs
Normal file
@@ -0,0 +1,188 @@
|
||||
use egui::containers::menu::{Bar, MenuConfig, SubMenuButton};
|
||||
use egui::{include_image, PopupCloseBehavior, Ui};
|
||||
use egui_kittest::{Harness, SnapshotResults};
|
||||
use kittest::Queryable;
|
||||
|
||||
struct TestMenu {
|
||||
config: MenuConfig,
|
||||
checked: bool,
|
||||
}
|
||||
|
||||
impl TestMenu {
|
||||
fn new(config: MenuConfig) -> Self {
|
||||
Self {
|
||||
config,
|
||||
checked: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui) {
|
||||
ui.vertical(|ui| {
|
||||
Bar::new().config(self.config.clone()).ui(ui, |ui| {
|
||||
egui::Sides::new().show(
|
||||
ui,
|
||||
|ui| {
|
||||
ui.menu_button("Menu A", |ui| {
|
||||
_ = ui.button("Button in Menu A");
|
||||
ui.menu_button("Submenu A", |ui| {
|
||||
for i in 0..4 {
|
||||
_ = ui.button(format!("Button {i} in Submenu A"));
|
||||
}
|
||||
});
|
||||
ui.menu_image_text_button(
|
||||
include_image!("../../eframe/data/icon.png"),
|
||||
"Submenu B with icon",
|
||||
|ui| {
|
||||
_ = ui.button("Button in Submenu B");
|
||||
},
|
||||
);
|
||||
SubMenuButton::new("Submenu C (CloseOnClickOutside)")
|
||||
.config(
|
||||
MenuConfig::new()
|
||||
.close_behavior(PopupCloseBehavior::CloseOnClickOutside),
|
||||
)
|
||||
.ui(ui, |ui| {
|
||||
_ = ui.button("Button in Submenu C");
|
||||
ui.checkbox(&mut self.checked, "Checkbox in Submenu C");
|
||||
ui.menu_button("Submenu D", |ui| {
|
||||
if ui
|
||||
.button("Button in Submenu D (close on click)")
|
||||
.clicked()
|
||||
{
|
||||
ui.close();
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
ui.menu_image_text_button(
|
||||
include_image!("../../eframe/data/icon.png"),
|
||||
"Menu B with icon",
|
||||
|ui| {
|
||||
_ = ui.button("Button in Menu B");
|
||||
},
|
||||
);
|
||||
_ = ui.button("Menu Button");
|
||||
ui.menu_button("Menu C", |ui| {
|
||||
_ = ui.button("Button in Menu C");
|
||||
});
|
||||
},
|
||||
|ui| {
|
||||
ui.label("Some other label");
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn into_harness(self) -> Harness<'static, Self> {
|
||||
Harness::builder()
|
||||
.with_size(egui::Vec2::new(500.0, 300.0))
|
||||
.build_ui_state(
|
||||
|ui, menu| {
|
||||
egui_extras::install_image_loaders(ui.ctx());
|
||||
menu.ui(ui);
|
||||
},
|
||||
self,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn menu_close_on_click_outside() {
|
||||
// We're intentionally setting CloseOnClick here so we can test if a submenu can override the
|
||||
// close behavior. (Note how Submenu C has CloseOnClickOutside set)
|
||||
let mut harness =
|
||||
TestMenu::new(MenuConfig::new().close_behavior(PopupCloseBehavior::CloseOnClick))
|
||||
.into_harness();
|
||||
|
||||
harness.get_by_label("Menu A").simulate_click();
|
||||
harness.run();
|
||||
|
||||
harness
|
||||
.get_by_label("Submenu C (CloseOnClickOutside)")
|
||||
.hover();
|
||||
harness.run();
|
||||
|
||||
// We should be able to check the checkbox without closing the menu
|
||||
// Click a couple of times, just in case
|
||||
for expect_checked in [true, false, true, false] {
|
||||
harness
|
||||
.get_by_label("Checkbox in Submenu C")
|
||||
.simulate_click();
|
||||
harness.run();
|
||||
assert_eq!(expect_checked, harness.state().checked);
|
||||
}
|
||||
|
||||
// Hovering outside should not close the menu
|
||||
harness.get_by_label("Some other label").hover();
|
||||
harness.run();
|
||||
assert!(harness.query_by_label("Checkbox in Submenu C").is_some());
|
||||
|
||||
// Clicking outside should close the menu
|
||||
harness.get_by_label("Some other label").simulate_click();
|
||||
harness.run();
|
||||
assert!(harness.query_by_label("Checkbox in Submenu C").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn menu_close_on_click() {
|
||||
let mut harness =
|
||||
TestMenu::new(MenuConfig::new().close_behavior(PopupCloseBehavior::CloseOnClick))
|
||||
.into_harness();
|
||||
|
||||
harness.get_by_label("Menu A").simulate_click();
|
||||
harness.run();
|
||||
|
||||
harness.get_by_label("Submenu B with icon").hover();
|
||||
harness.run();
|
||||
|
||||
// Clicking the button should close the menu (even if ui.close() is not called by the button)
|
||||
harness.get_by_label("Button in Submenu B").simulate_click();
|
||||
harness.run();
|
||||
assert!(harness.query_by_label("Button in Submenu B").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clicking_submenu_button_should_never_close_menu() {
|
||||
// We test for this since otherwise the menu wouldn't work on touch devices
|
||||
// The other tests use .hover to open submenus, but this test explicitly uses .simulate_click
|
||||
let mut harness =
|
||||
TestMenu::new(MenuConfig::new().close_behavior(PopupCloseBehavior::CloseOnClick))
|
||||
.into_harness();
|
||||
|
||||
harness.get_by_label("Menu A").simulate_click();
|
||||
harness.run();
|
||||
|
||||
// Clicking the submenu button should not close the menu
|
||||
harness.get_by_label("Submenu B with icon").simulate_click();
|
||||
harness.run();
|
||||
|
||||
harness.get_by_label("Button in Submenu B").simulate_click();
|
||||
harness.run();
|
||||
assert!(harness.query_by_label("Button in Submenu B").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn menu_snapshots() {
|
||||
let mut harness = TestMenu::new(MenuConfig::new()).into_harness();
|
||||
|
||||
let mut results = SnapshotResults::new();
|
||||
|
||||
harness.get_by_label("Menu A").hover();
|
||||
harness.run();
|
||||
results.add(harness.try_snapshot("menu/closed_hovered"));
|
||||
|
||||
harness.get_by_label("Menu A").simulate_click();
|
||||
harness.run();
|
||||
results.add(harness.try_snapshot("menu/opened"));
|
||||
|
||||
harness
|
||||
.get_by_label("Submenu C (CloseOnClickOutside)")
|
||||
.hover();
|
||||
harness.run();
|
||||
results.add(harness.try_snapshot("menu/submenu"));
|
||||
|
||||
harness.get_by_label("Submenu D").hover();
|
||||
harness.run();
|
||||
results.add(harness.try_snapshot("menu/subsubmenu"));
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d96ef60e9cd8767c0a95cc1fb0e240014248d7b0d6f67777a5b6ca4f23e91380
|
||||
size 10732
|
||||
3
crates/egui_kittest/tests/snapshots/menu/opened.png
Normal file
3
crates/egui_kittest/tests/snapshots/menu/opened.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:23dced7849e2bdd436e0552b2d351fef5693dd688c875816aaba3607a3aa1197
|
||||
size 21756
|
||||
3
crates/egui_kittest/tests/snapshots/menu/submenu.png
Normal file
3
crates/egui_kittest/tests/snapshots/menu/submenu.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6b8790dbd01409496ca6dc6d322d69c59a02f552b244264c8f6d7ea847846d5a
|
||||
size 28979
|
||||
3
crates/egui_kittest/tests/snapshots/menu/subsubmenu.png
Normal file
3
crates/egui_kittest/tests/snapshots/menu/subsubmenu.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6f0b04c42dfb257ca650a2fe332509f847cc94f4666cc29d830086627b91fc25
|
||||
size 33737
|
||||
Reference in New Issue
Block a user