M6: Escape in an open drop-down closes only its list, and a note on the list's fixed colors

This commit is contained in:
Eric Kaden
2026-10-22 09:22:21 -04:00
parent db7b36f8c7
commit 5d2b0bc063
3 changed files with 28 additions and 5 deletions
+8 -5
View File
@@ -2519,8 +2519,13 @@ pub fn shortcuts(view: &mut DocView, ctx: &egui::Context, speed: crate::scroll_s
view.view_history(true);
}
let key = |k| (arrows || !matches!(k, Key::ArrowLeft | Key::ArrowRight | Key::ArrowUp | Key::ArrowDown)) && ctx.input(|i| i.key_pressed(k));
// An open form field (a drop-down's or a list box's items) takes the arrows, Page Up / Page
// Down, Home, End and Escape. A focused check box or radio button uses none of them, so the
// page keys still work there.
let field_open = view.forms.focus.is_some() && form_box.is_none();
// An open menu takes Escape first and closes; the selection stays for the next one (#3535).
if key(Key::Escape) && !egui::Popup::is_any_open(ctx) {
// So does an open field, whose list closes.
if key(Key::Escape) && !egui::Popup::is_any_open(ctx) && !field_open {
if view.camera.is_some() || view.camera_done.is_some() {
view.pending_action = Some(ViewAction::CancelCamera);
} else if view.selection.is_some() {
@@ -2529,10 +2534,8 @@ pub fn shortcuts(view: &mut DocView, ctx: &egui::Context, speed: crate::scroll_s
view.find = None;
}
}
// An open form field takes the arrows, Page Up / Page Down, Home and End (a drop-down's or a
// list box's items), so the page under it stays put. A focused check box or radio button
// uses none of them, so the page keys still work there.
if view.forms.focus.is_some() && form_box.is_none() {
// The open field's keys leave the page under it where it is.
if field_open {
return;
}
if key(Key::Home) {
+2
View File
@@ -899,6 +899,8 @@ fn choice_list(
let resp = egui::Area::new(egui::Id::new(("form-choices", view))).order(egui::Order::Foreground).fixed_pos(rect.left_bottom()).show(ctx, |ui| {
let (outer, area) = ui.allocate_exact_size(vec2(width, shown as f32 * row + 2.0), egui::Sense::hover());
let p = ui.painter();
// The list is drawn as the form's own, a white list in the field's font with a blue
// highlight, in light and dark themes alike, so its colors are fixed, not the app's tokens.
p.rect_filled(outer, CornerRadius::ZERO, Color32::WHITE);
// The wheel scrolls the list, a row at a time, and not the page under it.
if area.contains_pointer() && n > shown {
+18
View File
@@ -1525,6 +1525,24 @@ fn the_mouse_wheel_scrolls_a_long_drop_down_list_and_not_the_page() {
assert_eq!(h.state().views[0].page_screen_rect(0), page, "the page stayed where it was");
}
/// Escape in an open drop-down closes its list and nothing else: the find bar, which Escape
/// closes when no field is open, stays open.
#[test]
fn escape_closes_an_open_drop_downs_list_and_leaves_the_page_alone() {
let mut h = harness();
long_country(&mut h);
h.state_mut().views[0].open_find();
h.run_steps(3);
assert!(h.state().views[0].find.is_some());
click_field(&mut h, "country", 0);
assert!(egui_kittest::kittest::Queryable::query_by_label(&h, "0000").is_some(), "the list is open");
h.key_press(egui::Key::Escape);
h.run_steps(3);
assert!(egui_kittest::kittest::Queryable::query_by_label(&h, "0000").is_none(), "Escape closed the list");
assert!(h.state().views[0].forms.focus.is_none(), "and the field");
assert!(h.state().views[0].find.is_some(), "the find bar stays open");
}
/// An auto-sized drop-down's list draws its items at the size the field shows its value at, so a
/// long chosen item fits the list as it fits the field instead of growing past it.
#[test]