mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-12 17:02:08 -06:00
Resize drag: keep the last accepted preview and the redo history on Esc
A refused size no longer replaces the size the release applies to the selection, and Esc puts back the redo stack the drag began with. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
38f48ad974
commit
3d50413aff
@@ -244,6 +244,8 @@ pub struct GridState {
|
||||
/// undo entries are the ones from the first entry that snapshots it; earlier edits with the
|
||||
/// same label are not the drag's to merge or cancel.
|
||||
pub resize_base: Option<Arc<Workbook>>,
|
||||
/// The redo stack when that drag began; Esc puts it back (the preview commands clear it).
|
||||
pub resize_redo: Vec<gridcraft_engine::HistoryEntry>,
|
||||
pub ensure_visible: bool,
|
||||
/// With `ensure_visible`: scroll to the moving end of the selection instead of the active cell
|
||||
/// (Shift/Ctrl+Shift+arrows keep the active cell on the anchor while the far corner moves).
|
||||
|
||||
@@ -308,7 +308,7 @@ fn handle_column_header_press(app: &mut SheetApp, input: &InteractionInput<'_>,
|
||||
let in_col_header = input.in_column_header;
|
||||
if in_col_header {
|
||||
if let Some((c, x)) = col_edge(geo, sh, p.x) {
|
||||
app.grid.resize_base = app.session.active().map(|d| d.wb.clone());
|
||||
begin_resize(app);
|
||||
app.grid.drag = Drag::ResizeCol { col: c, start: p.x, orig: sh.col_width(c).max(0.0) * 0.0 + (x - geo.x(sh, c)) / geo.z, to: None };
|
||||
return;
|
||||
}
|
||||
@@ -345,7 +345,7 @@ fn handle_row_header_press(app: &mut SheetApp, input: &InteractionInput<'_>, p:
|
||||
let in_row_header = input.in_row_header;
|
||||
if in_row_header {
|
||||
if let Some((r, y)) = row_edge(geo, sh, p.y) {
|
||||
app.grid.resize_base = app.session.active().map(|d| d.wb.clone());
|
||||
begin_resize(app);
|
||||
app.grid.drag = Drag::ResizeRow { row: r, start: p.y, orig: (y - geo.y(sh, r)) / geo.z, to: None };
|
||||
return;
|
||||
}
|
||||
@@ -596,6 +596,10 @@ fn handle_dragging(app: &mut SheetApp, input: &InteractionInput<'_>) {
|
||||
if steps > 0 {
|
||||
let _ = app.session.run("edit.undo", json!({"steps": steps}));
|
||||
}
|
||||
// Undo pushes the cancelled preview onto the redo stack; put back what the drag began with.
|
||||
if let Some(d) = app.session.active_mut() {
|
||||
d.redo = std::mem::take(&mut app.grid.resize_redo);
|
||||
}
|
||||
app.grid.drag = Drag::None;
|
||||
app.grid.resize_base = None;
|
||||
}
|
||||
@@ -650,21 +654,22 @@ fn handle_dragging(app: &mut SheetApp, input: &InteractionInput<'_>) {
|
||||
app.grid.drag = Drag::MoveCols { source, before };
|
||||
scroll_by(app, vec2(auto.x, 0.0));
|
||||
}
|
||||
Drag::ResizeCol { col, start, orig, .. } => {
|
||||
Drag::ResizeCol { col, start, orig, to } => {
|
||||
let w = nan_safe_size(orig + (p.x - start) / geo.z);
|
||||
app.toast =
|
||||
Some((format!("Width: {:.2} ({} pixels)", gridcraft_engine::cmd::format::points_to_chars(w as f64), w.round()), crate::now_ms()));
|
||||
// Preview on the dragged column only; the release applies it to the selection.
|
||||
let _ = app.session.run("home.columnWidth", json!({"cols": RangeRef::cols(col, col).a1(), "width": w.round()}));
|
||||
// Preview on the dragged column only; the release applies it to the selection. A
|
||||
// rejected size leaves the last shown preview (and `to`) as it was.
|
||||
let ok = app.session.run("home.columnWidth", json!({"cols": RangeRef::cols(col, col).a1(), "width": w.round()})).is_ok();
|
||||
coalesce_resize_undo(app);
|
||||
app.grid.drag = Drag::ResizeCol { col, start, orig, to: Some(w) };
|
||||
app.grid.drag = Drag::ResizeCol { col, start, orig, to: if ok { Some(w) } else { to } };
|
||||
}
|
||||
Drag::ResizeRow { row, start, orig, .. } => {
|
||||
Drag::ResizeRow { row, start, orig, to } => {
|
||||
let h = nan_safe_size(orig + (p.y - start) / geo.z);
|
||||
app.toast = Some((format!("Height: {:.2} ({} pixels)", h * 0.75, h.round()), crate::now_ms()));
|
||||
let _ = app.session.run("home.rowHeight", json!({"rows": format!("{}:{}", row + 1, row + 1), "height": h.round()}));
|
||||
let ok = app.session.run("home.rowHeight", json!({"rows": format!("{}:{}", row + 1, row + 1), "height": h.round()})).is_ok();
|
||||
coalesce_resize_undo(app);
|
||||
app.grid.drag = Drag::ResizeRow { row, start, orig, to: Some(h) };
|
||||
app.grid.drag = Drag::ResizeRow { row, start, orig, to: if ok { Some(h) } else { to } };
|
||||
}
|
||||
Drag::Freeze { cols, .. } => {
|
||||
app.grid.drag = Drag::Freeze { cols, to: Some(freeze_boundary(geo, sh, p, cols)) };
|
||||
@@ -940,6 +945,12 @@ fn coalesce_last_undo(app: &mut SheetApp) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts a header resize drag: remembers the workbook and the redo stack it began with.
|
||||
fn begin_resize(app: &mut SheetApp) {
|
||||
app.grid.resize_base = app.session.active().map(|d| d.wb.clone());
|
||||
app.grid.resize_redo = app.session.active().map(|d| d.redo.clone()).unwrap_or_default();
|
||||
}
|
||||
|
||||
/// Undo entries a header resize drag has pushed so far: those from the first one that snapshots
|
||||
/// the workbook the drag started from (`resize_base`). A failed command pushes none.
|
||||
fn resize_steps(app: &SheetApp) -> usize {
|
||||
|
||||
@@ -295,3 +295,82 @@ fn table_filter_autofit_works_through_command_and_header_double_click() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn press_escape(h: &mut egui_kittest::Harness<'_, SheetApp>) {
|
||||
h.input_mut().events.push(Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE });
|
||||
h.step();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_rejected_final_preview_releases_with_the_size_last_shown() {
|
||||
// The engine refuses rows over 546 px and columns over 1800 px; the release must apply the last
|
||||
// accepted preview to the whole selection, not retry the refused size.
|
||||
for rows in [false, true] {
|
||||
let mut h = harness(if rows { "1:3" } else { "A:C" });
|
||||
let before = sizes(&h, rows);
|
||||
let (start, zoom) = edge(&h, rows, 1);
|
||||
let unit = |d: f32| if rows { vec2(0.0, d) } else { vec2(d, 0.0) };
|
||||
button(&mut h, start, true);
|
||||
h.input_mut().events.push(Event::PointerMoved(start + unit(15.0)));
|
||||
h.step();
|
||||
h.input_mut().events.push(Event::PointerMoved(start + unit(30.0)));
|
||||
h.step();
|
||||
let shown = sizes(&h, rows)[1];
|
||||
assert_eq!(shown, (before[1] + 30.0 / zoom).round());
|
||||
h.input_mut().events.push(Event::PointerMoved(start + unit(4000.0 * zoom)));
|
||||
h.step();
|
||||
assert_eq!(sizes(&h, rows)[1], shown, "the refused size is not shown");
|
||||
button(&mut h, start + unit(4000.0 * zoom), false);
|
||||
h.run_steps(2);
|
||||
let after = sizes(&h, rows);
|
||||
for (index, size) in after.iter().take(3).enumerate() {
|
||||
assert_eq!(*size, shown, "line {index} takes the size that was shown (rows: {rows})");
|
||||
}
|
||||
assert_eq!(after[3], before[3]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escaping_a_resize_drag_leaves_redo_history_as_it_was() {
|
||||
for rows in [false, true] {
|
||||
for with_redo in [false, true] {
|
||||
let mut h = harness(if rows { "1:3" } else { "A:C" });
|
||||
let cmd = if rows { ("home.rowHeight", "rows", "4:4", "height") } else { ("home.columnWidth", "cols", "D:D", "width") };
|
||||
let mut redo_len = 0;
|
||||
let mut d_size = 0.0;
|
||||
if with_redo {
|
||||
h.state_mut().session.execute(cmd.0, json!({cmd.1: cmd.2, cmd.3: 80})).unwrap();
|
||||
h.state_mut().session.execute("edit.undo", json!({})).unwrap();
|
||||
h.run_steps(2);
|
||||
redo_len = h.state().session.active().unwrap().redo.len();
|
||||
assert_eq!(redo_len, 1);
|
||||
d_size = sizes(&h, rows)[3];
|
||||
}
|
||||
let before = sizes(&h, rows);
|
||||
let (start, _) = edge(&h, rows, 1);
|
||||
let delta = if rows { vec2(0.0, 30.0) } else { vec2(30.0, 0.0) };
|
||||
button(&mut h, start, true);
|
||||
h.input_mut().events.push(Event::PointerMoved(start + delta / 2.0));
|
||||
h.step();
|
||||
h.input_mut().events.push(Event::PointerMoved(start + delta));
|
||||
h.step();
|
||||
assert_ne!(sizes(&h, rows), before, "the drag previews");
|
||||
press_escape(&mut h);
|
||||
button(&mut h, start + delta, false);
|
||||
h.run_steps(2);
|
||||
assert_eq!(sizes(&h, rows), before);
|
||||
assert_eq!(h.state().session.active().unwrap().redo.len(), redo_len, "Esc leaves the redo stack alone (rows: {rows}, redo: {with_redo})");
|
||||
// Ctrl+Y brings back only what was undone before the drag.
|
||||
let redone = h.state_mut().session.execute("edit.redo", json!({}));
|
||||
if with_redo {
|
||||
redone.unwrap();
|
||||
let redone = sizes(&h, rows);
|
||||
assert_eq!(redone[3], 80.0);
|
||||
assert_ne!(redone[3], d_size);
|
||||
assert_eq!(redone[..3], before[..3], "the cancelled drag does not come back");
|
||||
} else {
|
||||
assert!(redone.is_err(), "nothing to redo after a cancelled drag");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user