diff --git a/crates/ui-egui/src/tabs.rs b/crates/ui-egui/src/tabs.rs index 0258169de..98443645b 100644 --- a/crates/ui-egui/src/tabs.rs +++ b/crates/ui-egui/src/tabs.rs @@ -8,6 +8,12 @@ use crate::SheetApp; use crate::icons::{self, Icon}; use crate::theme::{self, Tokens}; +/// The rename field asks for focus only while it has not got it yet. Asking again on the frame it lost focus (Enter or a click elsewhere) +/// would keep the field focused after it is closed, so the keyboard stayed stuck in the sheet name. +fn rename_needs_focus(has_focus: bool, lost_focus: bool) -> bool { + !has_focus && !lost_focus +} + pub fn sheet_tabs(app: &mut SheetApp, ui: &mut Ui) { let t = Tokens::get(ui.ctx()); egui::Panel::bottom("sheet_tabs").exact_size(32.0).frame(egui::Frame::NONE.fill(t.tab_bar)).show(ui, |ui| { @@ -61,7 +67,9 @@ pub fn sheet_tabs(app: &mut SheetApp, ui: &mut Ui) { let mut child = ui.new_child(egui::UiBuilder::new().max_rect(r.shrink2(vec2(6.0, 5.0)))); let ed = child.add(egui::TextEdit::singleline(&mut text).font(theme::ui_font(13.0)).desired_width(r.width() - 12.0)); app.grid.rename_text = text.clone(); - ed.request_focus(); + if rename_needs_focus(ed.has_focus(), ed.lost_focus()) { + ed.request_focus(); + } if ed.lost_focus() { if !ui.input(|i| i.key_pressed(egui::Key::Escape)) && text != name { app.run_or_alert("sheet.rename", json!({"sheet": i, "name": text})); @@ -285,3 +293,15 @@ pub fn status_bar(app: &mut SheetApp, ui: &mut Ui) { }, ); } + +#[cfg(test)] +mod tests { + use super::rename_needs_focus; + + #[test] + fn rename_field_takes_focus_once_and_lets_go_on_commit() { + assert!(rename_needs_focus(false, false), "first frame: grab focus"); + assert!(!rename_needs_focus(true, false), "already focused: do not ask again"); + assert!(!rename_needs_focus(false, true), "Enter or click-away: must not take focus back"); + } +} diff --git a/crates/ui-egui/tests/sheet_tab_rename.rs b/crates/ui-egui/tests/sheet_tab_rename.rs new file mode 100644 index 000000000..5ed2b117e --- /dev/null +++ b/crates/ui-egui/tests/sheet_tab_rename.rs @@ -0,0 +1,60 @@ +//! Renaming a sheet tab: the name field must let go of the keyboard when the user is done (#145). + +use egui::{Event, Key, Modifiers, PointerButton}; +use gridcraft_engine::Session; +use gridcraft_engine::core::CellRef; +use gridcraft_ui_egui::{SheetApp, grid::Geo}; + +fn harness() -> egui_kittest::Harness<'static, SheetApp> { + let mut s = Session::new(); + s.new_workbook(); + let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_ui_state( + |ui, app: &mut SheetApp| { + let ctx = ui.ctx().clone(); + app.logic(&ctx); + app.ui(ui); + }, + SheetApp::new(s, Default::default()), + ); + h.run_steps(4); + h +} + +fn start_rename(h: &mut egui_kittest::Harness<'static, SheetApp>, new_name: &str) { + h.state_mut().grid.renaming_tab = Some(0); + h.state_mut().grid.rename_text = new_name.to_string(); + h.run_steps(3); +} + +fn sheet_name(h: &egui_kittest::Harness<'static, SheetApp>) -> String { + h.state().session.active().unwrap().wb.sheets[0].name.clone() +} + +#[test] +fn enter_saves_the_sheet_name_and_closes_the_field() { + let mut h = harness(); + start_rename(&mut h, "Budget"); + h.input_mut().events.push(Event::Key { key: Key::Enter, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE }); + h.input_mut().events.push(Event::Key { key: Key::Enter, physical_key: None, pressed: false, repeat: false, modifiers: Modifiers::NONE }); + h.run_steps(3); + assert_eq!(h.state().grid.renaming_tab, None, "Enter must close the rename field"); + assert_eq!(sheet_name(&h), "Budget"); +} + +#[test] +fn clicking_a_cell_saves_the_sheet_name_and_closes_the_field() { + let mut h = harness(); + start_rename(&mut h, "Ledger"); + let app = h.state(); + let sheet = app.session.active().unwrap().wb.active().unwrap(); + let geo = Geo::new(sheet, app.grid.rect.unwrap(), app.view().scroll); + let pos = geo.cell_rect(sheet, CellRef::parse("B2").unwrap()).center(); + h.input_mut().events.extend([ + Event::PointerMoved(pos), + Event::PointerButton { pos, button: PointerButton::Primary, pressed: true, modifiers: Modifiers::NONE }, + Event::PointerButton { pos, button: PointerButton::Primary, pressed: false, modifiers: Modifiers::NONE }, + ]); + h.run_steps(3); + assert_eq!(h.state().grid.renaming_tab, None, "clicking elsewhere must close the rename field"); + assert_eq!(sheet_name(&h), "Ledger"); +}