diff --git a/crates/ui-egui/src/formula_bar.rs b/crates/ui-egui/src/formula_bar.rs index 753585f10..75d0020db 100644 --- a/crates/ui-egui/src/formula_bar.rs +++ b/crates/ui-egui/src/formula_bar.rs @@ -65,7 +65,7 @@ pub fn show(app: &mut SheetApp, ui: &mut egui::Ui) { fn active_input(app: &SheetApp) -> String { let Some(d) = app.session.active() else { return String::new() }; let Some(sh) = d.wb.active() else { return String::new() }; - let a = d.selection.active; + let a = sh.merge_at(d.selection.active).map(|m| m.start).unwrap_or(d.selection.active); if let Some(c) = sh.cell(a) { if c.formula.is_some() && d.wb.styles.get(c.style).protection.hidden && sh.is_protected() { return String::new(); diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 8eb1eca1a..d89165650 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -276,7 +276,7 @@ impl SheetApp { pub fn begin_edit(&mut self, text: Option, from_formula_bar: bool) { let Some(d) = self.session.active() else { return }; let Some(sh) = d.wb.active() else { return }; - let at = d.selection.active; + let at = sh.merge_at(d.selection.active).map(|m| m.start).unwrap_or(d.selection.active); let current = sh.cell(at).map(|c| c.input_text()).unwrap_or_default(); let (text, replace) = match text { Some(t) => (t, true), diff --git a/crates/ui-egui/tests/merged_cells.rs b/crates/ui-egui/tests/merged_cells.rs new file mode 100644 index 000000000..c28e0806e --- /dev/null +++ b/crates/ui-egui/tests/merged_cells.rs @@ -0,0 +1,118 @@ +//! Merged-cell edits must read and write the visible, top-left cell. + +use egui::{Event, Key, Modifiers, PointerButton, Pos2}; +use egui_kittest::kittest::Queryable as _; +use gridcraft_engine::Session; +use gridcraft_engine::core::{CellRef, Value}; +use gridcraft_ui_egui::{SheetApp, grid::Geo}; +use serde_json::json; + +type Harness = egui_kittest::Harness<'static, SheetApp>; + +fn harness(input: &str) -> Harness { + let mut session = Session::new(); + session.new_workbook(); + session.execute("home.mergeCells", json!({"range": "A1:B2"})).unwrap(); + session.execute("cell.set", json!({"cell": "A1", "input": input})).unwrap(); + let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_ui_state( + |ui, app: &mut SheetApp| { + app.logic(ui.ctx()); + app.ui(ui); + }, + SheetApp::new(session, Default::default()), + ); + h.run_steps(4); + h +} + +fn cell_center(h: &Harness, cell: &str) -> Pos2 { + 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); + geo.cell_rect(sheet, CellRef::parse(cell).unwrap()).center() +} + +fn click_cell(h: &mut Harness, cell: &str) { + let pos = cell_center(h, cell); + 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(2); +} + +fn text(h: &mut Harness, text: &str) { + h.input_mut().events.push(Event::Text(text.into())); + h.run_steps(2); +} + +fn key(h: &mut Harness, key: Key) { + for pressed in [true, false] { + h.input_mut().events.push(Event::Key { key, physical_key: None, pressed, repeat: false, modifiers: Modifiers::NONE }); + } + h.run_steps(2); +} + +fn value(h: &Harness, cell: &str) -> Value { + h.state().session.active().unwrap().wb.active().unwrap().value(CellRef::parse(cell).unwrap()) +} + +#[test] +fn typing_into_a_covered_cell_remains_visible_after_clicking_away() { + let mut h = harness(""); + click_cell(&mut h, "B2"); + assert_eq!(h.state().session.active().unwrap().selection.active.a1(), "B2"); + text(&mut h, "hello"); + click_cell(&mut h, "D4"); + + assert!(h.state().editor.is_none()); + assert_eq!(value(&h, "A1"), Value::from("hello"), "the cell painted for the merge must contain the committed text"); + assert_eq!(value(&h, "B2"), Value::Empty, "editing a merge must not hide text in a covered cell"); + click_cell(&mut h, "B2"); + assert!(h.query_by_label("hello").is_some(), "the formula bar must show the merged content when a covered cell is selected"); +} + +#[test] +fn f2_and_double_click_edit_the_existing_merged_content() { + let mut h = harness("before"); + click_cell(&mut h, "B2"); + key(&mut h, Key::F2); + let editor = h.state().editor.as_ref().unwrap(); + assert_eq!(editor.cell.a1(), "A1"); + assert_eq!(editor.text, "before"); + text(&mut h, "!"); + key(&mut h, Key::Enter); + assert_eq!(value(&h, "A1"), Value::from("before!")); + + click_cell(&mut h, "D4"); + click_cell(&mut h, "B2"); + click_cell(&mut h, "B2"); + let editor = h.state().editor.as_ref().unwrap(); + assert_eq!(editor.cell.a1(), "A1"); + assert_eq!(editor.text, "before!"); + text(&mut h, " again"); + click_cell(&mut h, "D4"); + assert_eq!(value(&h, "A1"), Value::from("before! again")); + assert_eq!(value(&h, "B2"), Value::Empty); +} + +#[test] +fn formula_bar_reads_and_edits_the_merge_anchor_from_a_covered_cell() { + let mut h = harness("formula bar text"); + click_cell(&mut h, "B2"); + h.get_by_label("formula bar text").click(); + h.run_steps(2); + + let editor = h.state().editor.as_ref().unwrap(); + assert!(editor.from_formula_bar); + assert_eq!(editor.cell.a1(), "A1"); + assert_eq!(editor.text, "formula bar text"); + text(&mut h, " edited"); + key(&mut h, Key::Enter); + assert_eq!(value(&h, "A1"), Value::from("formula bar text edited")); + assert_eq!(value(&h, "B2"), Value::Empty); + click_cell(&mut h, "D4"); + click_cell(&mut h, "B2"); + assert!(h.query_by_label("formula bar text edited").is_some()); +}