mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-31 07:38:32 +00:00
commit
d0cc17852e
4 files changed
+68
-9
No files matched your search
@@ -518,9 +518,23 @@ fn editor_arrow_keys(
|
||||
ed: &mut crate::editor::EditState,
|
||||
mut action: Option<EditorAction>,
|
||||
) -> (Option<EditorAction>, bool) {
|
||||
let (up, down, left, right, shift) = ui.input(|i| {
|
||||
(i.key_pressed(Key::ArrowUp), i.key_pressed(Key::ArrowDown), i.key_pressed(Key::ArrowLeft), i.key_pressed(Key::ArrowRight), i.modifiers.shift)
|
||||
// The modifiers come with the key event: an arrow pressed and released within one frame (a
|
||||
// control client's or an automation tool's keystroke) has already lost them from `i.modifiers`.
|
||||
let arrow = ui.input(|i| {
|
||||
i.events.iter().find_map(|e| match e {
|
||||
egui::Event::Key { key: key @ (Key::ArrowUp | Key::ArrowDown | Key::ArrowLeft | Key::ArrowRight), pressed: true, modifiers, .. } => {
|
||||
Some((*key, *modifiers))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
});
|
||||
let (up, down, left, right) = (
|
||||
arrow.is_some_and(|(k, _)| k == Key::ArrowUp),
|
||||
arrow.is_some_and(|(k, _)| k == Key::ArrowDown),
|
||||
arrow.is_some_and(|(k, _)| k == Key::ArrowLeft),
|
||||
arrow.is_some_and(|(k, _)| k == Key::ArrowRight),
|
||||
);
|
||||
let (shift, jump) = arrow.map_or((false, false), |(_, m)| (m.shift, m.command));
|
||||
if (up || down) && !ed.autocomplete.is_empty() {
|
||||
ui.input_mut(|i| {
|
||||
i.consume_key(egui::Modifiers::NONE, Key::ArrowUp);
|
||||
@@ -545,13 +559,18 @@ fn editor_arrow_keys(
|
||||
let rtl = app.session.active().and_then(|d| d.wb.active()).is_some_and(|s| s.right_to_left);
|
||||
let dc = if rtl { -dc } else { dc };
|
||||
if ed.can_point() {
|
||||
// Ctrl+arrow (⌘arrow) jumps the pointed cell to the edge of its data, Ctrl+Shift+arrow
|
||||
// extends the pointed range there, as the same keys do on the selection.
|
||||
ui.input_mut(|i| {
|
||||
for key in [Key::ArrowUp, Key::ArrowDown, Key::ArrowLeft, Key::ArrowRight] {
|
||||
i.consume_key(egui::Modifiers::NONE, key);
|
||||
i.consume_key(egui::Modifiers::SHIFT, key);
|
||||
for modifiers in
|
||||
[egui::Modifiers::NONE, egui::Modifiers::SHIFT, egui::Modifiers::COMMAND, egui::Modifiers::COMMAND | egui::Modifiers::SHIFT]
|
||||
{
|
||||
i.consume_key(modifiers, key);
|
||||
}
|
||||
}
|
||||
});
|
||||
crate::grid::point_move(app, ed, dr, dc, shift);
|
||||
crate::grid::point_move(app, ed, dr, dc, shift, jump);
|
||||
(action, false)
|
||||
} else {
|
||||
action = Some((dr, dc, false, false));
|
||||
|
||||
@@ -1014,11 +1014,13 @@ pub fn follow_link(app: &mut SheetApp, target: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Used by `EditState` users to keep the active cell visible while pointing.
|
||||
pub fn point_move(app: &mut SheetApp, ed: &mut EditState, dr: i64, dc: i64, extend: bool) {
|
||||
/// Moves the pointed cell one step, or with `jump` (Ctrl+arrow) to the edge of its data region;
|
||||
/// `extend` (Shift) moves the far end of the pointed range instead. Keeps the pointed cell visible.
|
||||
pub fn point_move(app: &mut SheetApp, ed: &mut EditState, dr: i64, dc: i64, extend: bool, jump: bool) {
|
||||
let Some(d) = app.session.active() else { return };
|
||||
let Some(sheet) = d.wb.active() else { return };
|
||||
let from = ed.point_cell.unwrap_or(ed.cell);
|
||||
let to = from.offset_clamped(dr, dc);
|
||||
let to = if jump { gridcraft_engine::selection::jump(sheet, from, dr.signum(), dc.signum()) } else { from.offset_clamped(dr, dc) };
|
||||
let anchor = if extend {
|
||||
// The anchor is the corner opposite the moving end: the written range is normalized, so
|
||||
// after extending up or left its first cell is the moving end, not the anchor.
|
||||
|
||||
@@ -331,6 +331,43 @@ fn shift_arrows_in_point_mode_extend_from_the_pointed_cell() {
|
||||
assert_eq!(edited(&h).as_deref(), Some("=SUM(A8:A10"), "Shift+Down shrinks back towards the anchor");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ctrl_arrows_in_point_mode_jump_to_the_edge_of_the_data() {
|
||||
// Typing `=COUNTIF(`, clicking the top of a column and pressing Ctrl+Shift+Down (⌘⇧↓) points
|
||||
// at the whole column of data, as the same keys select it outside an edit.
|
||||
let mut s = blank();
|
||||
s.execute("range.setValues", json!({"range": "A1:B5", "values": [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]})).unwrap();
|
||||
s.execute("selection.set", json!({"cell": "D1"})).unwrap();
|
||||
let mut h = harness(s);
|
||||
text(&mut h, "=COUNTIF(");
|
||||
click_cell(&mut h, "A1");
|
||||
let edited = |h: &egui_kittest::Harness<'static, SheetApp>| h.state().editor.as_ref().map(|e| e.text.clone());
|
||||
assert_eq!(edited(&h).as_deref(), Some("=COUNTIF(A1"));
|
||||
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::COMMAND | Modifiers::SHIFT));
|
||||
key(&mut h, Key::ArrowDown, Modifiers::COMMAND | Modifiers::SHIFT);
|
||||
assert_eq!(edited(&h).as_deref(), Some("=COUNTIF(A1:A5"), "Ctrl+Shift+Down extends to the last filled cell");
|
||||
key(&mut h, Key::ArrowRight, Modifiers::COMMAND | Modifiers::SHIFT);
|
||||
assert_eq!(edited(&h).as_deref(), Some("=COUNTIF(A1:B5"), "Ctrl+Shift+Right extends the moving end across the data");
|
||||
key(&mut h, Key::ArrowDown, Modifiers::COMMAND | Modifiers::SHIFT);
|
||||
assert_eq!(edited(&h).as_deref(), Some("=COUNTIF(A:B"), "past the data, Ctrl+Shift+Down reaches the sheet edge (written as whole columns)");
|
||||
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::COMMAND));
|
||||
key(&mut h, Key::ArrowUp, Modifiers::COMMAND);
|
||||
assert_eq!(edited(&h).as_deref(), Some("=COUNTIF(B5"), "Ctrl+Up without Shift points at the next filled cell");
|
||||
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::NONE));
|
||||
key(&mut h, Key::ArrowUp, Modifiers::NONE);
|
||||
assert_eq!(edited(&h).as_deref(), Some("=COUNTIF(B4"), "a plain arrow still moves one cell");
|
||||
// An automation tool (osascript `key code`) presses and releases within one frame, so by the
|
||||
// time the frame runs `i.modifiers` is already empty: the modifiers that count are the ones on
|
||||
// the press event.
|
||||
h.input_mut().events.extend([
|
||||
Event::Key { key: Key::ArrowUp, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::COMMAND },
|
||||
Event::ModifiersChanged(Modifiers::NONE),
|
||||
Event::Key { key: Key::ArrowUp, physical_key: None, pressed: false, repeat: false, modifiers: Modifiers::NONE },
|
||||
]);
|
||||
h.run_steps(2);
|
||||
assert_eq!(edited(&h).as_deref(), Some("=COUNTIF(B1"), "Command released in the same frame as the press still jumps");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shortcuts_format_and_extend() {
|
||||
let mut s = blank();
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
# UI parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (editing a hyperlink loads its existing fields; earlier: Ctrl with the numpad's + inserts, as Ctrl+Shift+= does) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (Ctrl+arrow and Ctrl+Shift+arrow while pointing a formula reference; earlier: editing a hyperlink loads its existing fields; earlier: AutoSum over a selected row puts its total to the right; ribbon catalog count 276 / 292) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
|
||||
|
||||
Interaction, controls and feel, against Excel. Part of [target-app-parity.md](target-app-parity.md).
|
||||
**UI/UX fidelity: ~55% ready, 50–80 h** (estimated). Ribbon command coverage is measured
|
||||
@@ -201,6 +201,7 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12.
|
||||
| 2026-10-24 | minor | Macro step editor: step list with breakpoints, drag to reorder and a command picker, an inspector with reference boxes, a debug bar and grid overlays |
|
||||
| 2026-10-24 | minor | Automate › Macros group (Record Macro, Use Relative References, Run Macro), recording status in the status bar, Macros dialog with macros beside scripts, Record Macro dialog, Ctrl+Shift+letter macro shortcuts; View › Macros keytips open the dialogs |
|
||||
| 2026-10-24 | minor | Ribbon catalog count 275 / 291 → 276 / 292 (View › Macros › Record Macro) |
|
||||
| 2026-10-29 | minor | Formula pointing from the keyboard: Ctrl+arrow (⌘arrow) moves the pointed cell to the edge of its data and Ctrl+Shift+arrow (⌘⇧arrow) extends the pointed range there, as the same keys move and extend the selection, so typing `=COUNTIF(`, clicking the top of a column and pressing ⌘⇧↓ points at the whole column of data; the keys used to move or extend by one cell. A range reaching the sheet edge is written as whole columns or rows (`A:B`). The editor reads the modifiers of the arrow's own key event, so a keystroke pressed and released within one frame (a control client's `ui.key`, an automation tool) keeps its Shift and Ctrl |
|
||||
| 2026-10-24 | minor | Go To lists the named ranges on every sheet, as Excel's does ("The Go to popup window shows named ranges on every worksheet in your workbook"): a name scoped to another sheet appears as `Sheet2!Name`, and hidden names, constants and formulas are left out. It keeps the references entered (newest first, ten at most), and a double-click on an entry goes there. The Reference box and `edit.goTo` take several names or references separated by commas (`Price, Type`, `B14:C22,F19:G30,H21:H29`) and select them all; `edit.goTo {list: true}` returns the list |
|
||||
| 2026-10-24 | minor | Right-clicking the status bar opens a menu of its statistics, as in Excel ("If you want to customize the status bar, right-click it, and then click the options that you want"): Average, Count and Sum are on by default, and Numerical Count, Minimum and Maximum are new and off by default. The choice is a preference (`view.statusBar`), kept in `prefs.json` |
|
||||
| 2026-10-24 | minor | Ctrl+Shift+U (Control+Shift+U on a Mac) expands or collapses the formula bar, as in Excel ("Expand or collapse the formula bar. Ctrl+Shift+U"), on the grid and in the cell editor or formula bar while editing. On Windows and Linux it used to underline the selection, as Ctrl+U does. The command is `view.expandFormulaBar` (`{on?}`, toggles without it) |
|
||||
|
||||
Reference in new issue
Block a user