mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-24 10:46:50 -06:00
Merge PR #913: Keyboard: Enter after a run of Tabs goes to the next row in the column the run started from
This commit is contained in:
@@ -162,6 +162,7 @@ pub fn editor_widget(app: &mut SheetApp, ui: &mut egui::Ui, id: egui::Id, font:
|
||||
let focused = ui.ctx().memory(|m| m.focused());
|
||||
let has_focus = focused == Some(id) || (mine && (ed.request_focus || focused.is_none()));
|
||||
let mut action: Option<(i64, i64, bool, bool)> = None;
|
||||
let mut arrow = false;
|
||||
let mut cancel = false;
|
||||
if has_focus && mine {
|
||||
let (enter, tab, esc, alt_enter, cmd_enter, ctrl_shift_enter, shift) = ui.input_mut(|i| {
|
||||
@@ -249,6 +250,7 @@ pub fn editor_widget(app: &mut SheetApp, ui: &mut egui::Ui, id: egui::Id, font:
|
||||
crate::grid::point_move(app, &mut ed, dr, dc, ashift);
|
||||
} else {
|
||||
action = Some((dr, dc, false, false));
|
||||
arrow = true;
|
||||
}
|
||||
}
|
||||
// F4 cycles $ anchors of the reference before the caret.
|
||||
@@ -413,7 +415,11 @@ pub fn editor_widget(app: &mut SheetApp, ui: &mut egui::Ui, id: egui::Id, font:
|
||||
if cancel {
|
||||
app.cancel_edit();
|
||||
} else if let Some((dr, dc, array, fill)) = action {
|
||||
app.commit_edit(dr, dc, array, fill);
|
||||
if arrow {
|
||||
app.commit_edit_arrow(dr, dc);
|
||||
} else {
|
||||
app.commit_edit(dr, dc, array, fill);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -284,6 +284,9 @@ pub struct SheetApp {
|
||||
/// text noted for an unpublished cut); `None` after a copy, or once the clipboard is seen
|
||||
/// holding other text. Once the cut has been pasted, Paste leaves this text alone.
|
||||
cut_text: Option<String>,
|
||||
/// A run of Tabs: (doc uid, sheet, column it started in, cell the last Tab landed on).
|
||||
/// Enter on that cell goes to the next row in the starting column, as in Excel (#911).
|
||||
tab_run: Option<(u64, usize, u32, CellRef)>,
|
||||
pub started: f64,
|
||||
/// Every unsaved workbook has been answered for quitting (or `app.quit` asked): the next
|
||||
/// window close goes through.
|
||||
@@ -331,6 +334,7 @@ impl SheetApp {
|
||||
selected_chart: None,
|
||||
clipboard_text_at_copy: None,
|
||||
cut_text: None,
|
||||
tab_run: None,
|
||||
started: now_ms(),
|
||||
allow_close: false,
|
||||
}
|
||||
@@ -995,6 +999,15 @@ impl SheetApp {
|
||||
}
|
||||
}
|
||||
|
||||
/// Commits the edit with an arrow key. The arrow is an ordinary move, so it ends a run of Tabs
|
||||
/// instead of continuing it (Right) or going back to its column like Enter (Down), as in Excel.
|
||||
pub fn commit_edit_arrow(&mut self, dr: i64, dc: i64) -> bool {
|
||||
let run = self.tab_run.take();
|
||||
let ok = self.commit_edit(dr, dc, false, false);
|
||||
self.tab_run = if ok { None } else { run };
|
||||
ok
|
||||
}
|
||||
|
||||
pub fn cancel_edit(&mut self) {
|
||||
self.editor = None;
|
||||
self.session.mode = gridcraft_engine::Mode::Ready;
|
||||
@@ -1003,11 +1016,26 @@ impl SheetApp {
|
||||
/// Enter/Tab move inside a multi-cell selection, otherwise to the next cell.
|
||||
pub fn move_after_enter(&mut self, dr: i64, dc: i64) {
|
||||
let multi = self.session.active().is_some_and(|d| !d.selection.is_single_cell());
|
||||
// Any other move (arrows, clicks, another sheet) leaves the selection off the cell the
|
||||
// last Tab landed on, which ends the run.
|
||||
let here = self.session.active().map(|d| (d.uid, d.wb.active_sheet, d.selection.active));
|
||||
let run = self.tab_run.take().filter(|&(uid, sheet, _, at)| here == Some((uid, sheet, at)));
|
||||
if multi {
|
||||
let fwd = dr > 0 || dc > 0;
|
||||
let _ = self.session.run("selection.next", json!({"forward": fwd, "byRow": dc != 0}));
|
||||
} else if let (Some((_, _, col, at)), 1, 0) = (run, dr, dc) {
|
||||
let back = i64::from(col) - i64::from(at.col);
|
||||
let _ = self.session.run("selection.move", json!({"dr": 1, "dc": back}));
|
||||
} else {
|
||||
let _ = self.session.run("selection.move", json!({"dr": dr, "dc": dc}));
|
||||
if dr == 0 && dc != 0 {
|
||||
let start = run.map(|r| r.2);
|
||||
self.tab_run = self.session.active().and_then(|d| {
|
||||
let (uid, sheet) = (d.uid, d.wb.active_sheet);
|
||||
let col = start.or(here.map(|h| h.2.col))?;
|
||||
Some((uid, sheet, col, d.selection.active))
|
||||
});
|
||||
}
|
||||
}
|
||||
self.grid.ensure_visible = true;
|
||||
}
|
||||
|
||||
@@ -177,7 +177,8 @@ fn keyboard_navigation_does_not_replay_the_last_pointer_click() {
|
||||
(Key::Enter, Modifiers::NONE, "B5"),
|
||||
(Key::Enter, Modifiers::SHIFT, "B4"),
|
||||
(Key::Tab, Modifiers::NONE, "C4"),
|
||||
(Key::Enter, Modifiers::NONE, "C5"),
|
||||
// #911: Enter after a Tab goes back to the column the Tab started from.
|
||||
(Key::Enter, Modifiers::NONE, "B5"),
|
||||
] {
|
||||
key(&mut h, key_code, modifiers);
|
||||
let selection = &h.state().session.active().unwrap().selection;
|
||||
@@ -241,6 +242,50 @@ fn typing_enters_values_and_moves_down() {
|
||||
assert_eq!(active.as_deref(), Some("B2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enter_after_tabs_returns_to_the_column_where_the_tabs_started() {
|
||||
// #911: Excel moves to the next row in the column the run of Tabs started from.
|
||||
let mut h = harness(blank());
|
||||
for t in ["a", "b"] {
|
||||
text(&mut h, t);
|
||||
key(&mut h, Key::Tab, Modifiers::NONE);
|
||||
}
|
||||
text(&mut h, "c");
|
||||
key(&mut h, Key::Enter, Modifiers::NONE);
|
||||
assert_eq!(value(&h, "C1"), Value::from("c"));
|
||||
assert_eq!(h.state().session.active().unwrap().selection.active.a1(), "A2");
|
||||
// The next Enter is an ordinary one again.
|
||||
key(&mut h, Key::Enter, Modifiers::NONE);
|
||||
assert_eq!(h.state().session.active().unwrap().selection.active.a1(), "A3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_with_an_arrow_ends_the_run_of_tabs() {
|
||||
let mut h = harness(blank());
|
||||
key(&mut h, Key::Tab, Modifiers::NONE);
|
||||
key(&mut h, Key::ArrowRight, Modifiers::NONE);
|
||||
key(&mut h, Key::Enter, Modifiers::NONE);
|
||||
assert_eq!(h.state().session.active().unwrap().selection.active.a1(), "C2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_arrow_that_commits_typing_ends_the_run_of_tabs() {
|
||||
// Excel 365 from B2: the arrow is an ordinary move, neither one more Tab (Right) nor an Enter
|
||||
// back to the starting column (Down).
|
||||
for (arrow, after_enter) in [(Key::ArrowRight, "D3"), (Key::ArrowDown, "C4")] {
|
||||
let mut s = blank();
|
||||
s.execute("selection.set", json!({"cell": "B2"})).unwrap();
|
||||
let mut h = harness(s);
|
||||
text(&mut h, "a");
|
||||
key(&mut h, Key::Tab, Modifiers::NONE);
|
||||
text(&mut h, "b");
|
||||
key(&mut h, arrow, Modifiers::NONE);
|
||||
assert_eq!(value(&h, "C2"), Value::from("b"), "{arrow:?}");
|
||||
key(&mut h, Key::Enter, Modifiers::NONE);
|
||||
assert_eq!(h.state().session.active().unwrap().selection.active.a1(), after_enter, "{arrow:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrows_escape_and_undo() {
|
||||
let mut h = harness(blank());
|
||||
|
||||
Reference in New Issue
Block a user