mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-27 11:37:13 -06:00
LR-BEHAV-TEXTFIELD: Return and Esc with the field's menu open
Return with the menu open left the edit hanging (the field gave up the focus, taken for a click in the menu) and it ended later as Away: a name field that submits on Return never submitted. It now ends the edit as Return and closes the menu. Esc with the menu open, or with the focus in the menu, cancelled the whole edit; it now closes only the menu, as native menus do, and the field takes the focus back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
52e61d6765
commit
17b1bdeeb3
@@ -140,6 +140,7 @@ impl<'a> TextField<'a> {
|
||||
// only the host can read the clipboard: it answers with a paste event
|
||||
Action::Paste => ui.ctx().send_viewport_cmd(egui::ViewportCommand::RequestPaste),
|
||||
Action::SelectAll => select_all(ui.ctx(), id, self.text),
|
||||
Action::Refocus => {}
|
||||
}
|
||||
}
|
||||
let mut edit = if self.multiline { egui::TextEdit::multiline(self.text) } else { egui::TextEdit::singleline(self.text) };
|
||||
@@ -199,9 +200,13 @@ impl<'a> TextField<'a> {
|
||||
.is_some();
|
||||
|
||||
let mut ending = None;
|
||||
let close_menu = |ui: &Ui| egui::Popup::close_id(ui.ctx(), egui::Popup::default_response_id(&response));
|
||||
// egui reports a lost focus on two frames: only an edit under way ends
|
||||
if response.lost_focus() && memo.before.is_some() {
|
||||
if escape {
|
||||
if escape && menu_was_open {
|
||||
// Esc closes only the menu, as in native menus: the field takes the focus back
|
||||
memo.action = Some(Action::Refocus);
|
||||
} else if escape {
|
||||
if let Some(before) = memo.before.take()
|
||||
&& *self.text != before
|
||||
{
|
||||
@@ -209,15 +214,19 @@ impl<'a> TextField<'a> {
|
||||
response.mark_changed();
|
||||
}
|
||||
ending = Some(Ending::Escape);
|
||||
} else if enter && !self.multiline {
|
||||
ending = Some(Ending::Return);
|
||||
close_menu(ui);
|
||||
} else if menu_was_open || memo.menu_open || memo.action.is_some() {
|
||||
// the click went to the menu: the edit goes on once the menu is done with
|
||||
memo.away = true;
|
||||
} else {
|
||||
ending = Some(Ending::Away);
|
||||
}
|
||||
if enter && !self.multiline && ending == Some(Ending::Away) {
|
||||
ending = Some(Ending::Return);
|
||||
}
|
||||
} else if memo.away && escape && memo.action.is_none() {
|
||||
// Esc with the focus in the menu (Tab took it there) closes only the menu
|
||||
memo.action = Some(Action::Refocus);
|
||||
memo.away = false;
|
||||
} else if memo.away && !memo.menu_open && memo.action.is_none() && !response.has_focus() {
|
||||
// the menu closed without a choice: the click outside it left the field
|
||||
ending = Some(Ending::Away);
|
||||
@@ -253,6 +262,8 @@ enum Action {
|
||||
Copy,
|
||||
Paste,
|
||||
SelectAll,
|
||||
/// Only take the focus back (Esc closed the menu).
|
||||
Refocus,
|
||||
}
|
||||
|
||||
fn select_all(ctx: &egui::Context, id: egui::Id, text: &str) {
|
||||
@@ -493,6 +504,48 @@ mod tests {
|
||||
assert_eq!(rig.endings, vec![Ending::Return]);
|
||||
}
|
||||
|
||||
/// Return with the menu open ends the edit as Return does, and closes the menu.
|
||||
#[test]
|
||||
fn return_with_the_menu_open_ends_the_edit() {
|
||||
let mut rig = Rig::new("Lisbon");
|
||||
rig.select_all_by_keys();
|
||||
rig.type_text("Rome");
|
||||
rig.click(FIELD, PointerButton::Secondary);
|
||||
rig.key(Key::Enter, Modifiers::NONE);
|
||||
assert_eq!(rig.endings, vec![Ending::Return]);
|
||||
assert_eq!(rig.text, "Rome");
|
||||
assert!(rig.rect(&format!("{FIELD}:cut")).is_none(), "the menu closed");
|
||||
}
|
||||
|
||||
/// Esc with the menu open closes only the menu, as in native menus: the edit goes on, and a
|
||||
/// second Esc gives back the text.
|
||||
#[test]
|
||||
fn escape_with_the_menu_open_closes_only_the_menu() {
|
||||
let mut rig = Rig::new("Lisbon");
|
||||
rig.select_all_by_keys();
|
||||
rig.type_text("Rome");
|
||||
rig.click(FIELD, PointerButton::Secondary);
|
||||
rig.key(Key::Escape, Modifiers::NONE);
|
||||
assert!(rig.rect(&format!("{FIELD}:cut")).is_none(), "the menu closed");
|
||||
assert_eq!((rig.text.as_str(), rig.endings.clone()), ("Rome", vec![]), "the edit goes on");
|
||||
assert!(rig.focused());
|
||||
rig.key(Key::Escape, Modifiers::NONE);
|
||||
assert_eq!((rig.text.as_str(), rig.endings.clone()), ("Lisbon", vec![Ending::Escape]));
|
||||
}
|
||||
|
||||
/// Esc after Tab took the focus into the open menu also closes only the menu.
|
||||
#[test]
|
||||
fn escape_from_inside_the_menu_closes_only_the_menu() {
|
||||
let mut rig = Rig::new("Lisbon");
|
||||
rig.select_all_by_keys();
|
||||
rig.type_text("Rome");
|
||||
rig.click(FIELD, PointerButton::Secondary);
|
||||
rig.key(Key::Tab, Modifiers::NONE);
|
||||
rig.key(Key::Escape, Modifiers::NONE);
|
||||
assert_eq!((rig.text.as_str(), rig.endings.clone()), ("Rome", vec![]), "the edit goes on");
|
||||
assert!(rig.focused());
|
||||
}
|
||||
|
||||
/// Clicking elsewhere ends the edit and keeps the text.
|
||||
#[test]
|
||||
fn clicking_away_ends_the_edit() {
|
||||
|
||||
Reference in New Issue
Block a user