diff --git a/crates/ui-egui/src/grid.rs b/crates/ui-egui/src/grid.rs index 1e2cc24..be8a578 100644 --- a/crates/ui-egui/src/grid.rs +++ b/crates/ui-egui/src/grid.rs @@ -1743,6 +1743,29 @@ fn interact(app: &mut SheetApp, ui: &mut egui::Ui, resp: &egui::Response, geo: & } } + // Double-click the fill handle: a double-click is also a click, so run it before the handle press returns. + if resp.double_clicked() + && let Some(p) = pos + && on_handle(p) + { + // Fill down as far as the neighbouring column goes. + let src = sel.current(); + let neighbour = if src.start.col > 0 { src.start.col - 1 } else { src.end.col + 1 }; + let mut last = src.end.row; + while last + 1 < MAX_ROWS && sh.value_ref(CellRef::new(last + 1, neighbour)).is_some_and(|v| !v.is_empty()) && last - src.end.row < 1_000_000 + { + last += 1; + } + if last > src.end.row { + let target = RangeRef::new(src.start, CellRef::new(last, src.end.col)); + let _ = app.run("edit.autoFill", json!({"source": src.a1(), "target": target.a1()})); + } + app.grid.drag = Drag::None; + app.grid.drag_select = false; + resp.request_focus(); + return; + } + // Press: decide the drag mode. if resp.drag_started() || (primary_clicked && app.grid.drag == Drag::None) { // Where the press began (a drag is only recognised after the pointer has moved). @@ -2046,25 +2069,6 @@ fn interact(app: &mut SheetApp, ui: &mut egui::Ui, resp: &egui::Response, geo: & app.grid.drag_select = false; } - // Double-click the fill handle. - if resp.double_clicked() - && let Some(p) = pos - && on_handle(p) - { - // Fill down as far as the neighbouring column goes. - let src = sel.current(); - let neighbour = if src.start.col > 0 { src.start.col - 1 } else { src.end.col + 1 }; - let mut last = src.end.row; - while last + 1 < MAX_ROWS && sh.value_ref(CellRef::new(last + 1, neighbour)).is_some_and(|v| !v.is_empty()) && last - src.end.row < 1_000_000 - { - last += 1; - } - if last > src.end.row { - let target = RangeRef::new(src.start, CellRef::new(last, src.end.col)); - let _ = app.run("edit.autoFill", json!({"source": src.a1(), "target": target.a1()})); - } - } - // Right-click: context menu. if resp.secondary_clicked() && let Some(p) = pos diff --git a/crates/ui-egui/tests/fill_handle_double_click.rs b/crates/ui-egui/tests/fill_handle_double_click.rs new file mode 100644 index 0000000..88b8737 --- /dev/null +++ b/crates/ui-egui/tests/fill_handle_double_click.rs @@ -0,0 +1,59 @@ +//! Double-clicking the fill handle fills down to the neighbouring column's last used row, +//! even with the pointer up for a frame between the two clicks. + +use egui::{Event, Modifiers, PointerButton, Pos2, vec2}; +use gridcraft_engine::Session; +use gridcraft_engine::core::{CellRef, Value}; +use gridcraft_ui_egui::{SheetApp, grid::Geo}; +use serde_json::json; + +fn harness() -> egui_kittest::Harness<'static, SheetApp> { + let mut session = Session::new(); + session.new_workbook(); + session.execute("range.setValues", json!({"range": "A1", "values": [[1], [2], [3], [4], [5], [6]]})).unwrap(); + session.execute("range.setValues", json!({"range": "B1", "values": [[10], [20], [30]]})).unwrap(); + session.execute("selection.set", json!({"range": "B1:B3"})).unwrap(); + let mut h = egui_kittest::Harness::builder().with_step_dt(1.0 / 60.0).with_size(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 handle(h: &egui_kittest::Harness<'_, SheetApp>) -> 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::new(2, 1)).right_bottom() - vec2(1.0, 1.0) +} + +fn button(h: &mut egui_kittest::Harness<'_, SheetApp>, pos: Pos2, pressed: bool) { + h.input_mut() + .events + .extend([Event::PointerMoved(pos), Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE }]); + h.step(); +} + +fn number(h: &egui_kittest::Harness<'_, SheetApp>, row: u32) -> Value { + h.state().session.active().unwrap().wb.active().unwrap().value(CellRef::new(row, 1)) +} + +#[test] +fn double_click_on_the_fill_handle_fills_down_with_a_frame_between_the_clicks() { + let mut h = harness(); + let pos = handle(&h); + button(&mut h, pos, true); + button(&mut h, pos, false); + h.step(); // One idle frame between the clicks, well inside the double-click interval. + button(&mut h, pos, true); + button(&mut h, pos, false); + h.run_steps(2); + + assert_eq!([number(&h, 3), number(&h, 4), number(&h, 5)], [Value::Number(40.0), Value::Number(50.0), Value::Number(60.0)]); + assert_eq!(h.state().session.active().unwrap().selection.a1(), "B1:B6"); + assert_eq!(h.state().session.journal.iter().filter(|(id, _)| id == "edit.autoFill").count(), 1); +}