From 66a0f457e9438d2769e38d60d3574c52f8f43e8a Mon Sep 17 00:00:00 2001 From: ham9zah Date: Tue, 13 Oct 2026 12:58:55 +0300 Subject: [PATCH] Add a Precise Cursors command and fix the misleading Caps Lock comment General > Use Precise Cursors already worked and is saved with the other preferences. This adds a `view.preciseCursors` command that toggles that same preference, so it can be given a shortcut in Edit > Keyboard Shortcuts. It has no default shortcut and no View menu item. The comment in canvas.rs claimed Caps Lock gave precise cursors; nothing reads Caps Lock (egui has no state for it), so it now describes what the code does. Tests: for the tools whose own pointer the preference replaces (Pen, the anchor tools, Curvature, Mesh, Eyedropper, Slice, Blend, Shape Builder) the pointer is their own when off and a crosshair when on; every state of the Pen's pointer turns into a crosshair; the preference round-trips through to_json/apply_prefs; the command toggles it, updates the saved UI state, is listed in the shortcut editor without a key and can be assigned one; switching it creates no undo step and leaves the document saved; with system cursors the glyph fallback is the system crosshair either way. The new label is translated in the languages that claim complete menus. Co-Authored-By: Claude Sonnet 5.5 --- crates/engine/src/tests_prefs.rs | 128 ++++++++++++++++++++++++++++ crates/ui-egui/src/canvas.rs | 29 ++++++- crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + crates/ui-egui/src/menus.rs | 64 ++++++++++++++ 18 files changed, 235 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/tests_prefs.rs b/crates/engine/src/tests_prefs.rs index 9b831a02..82082500 100644 --- a/crates/engine/src/tests_prefs.rs +++ b/crates/engine/src/tests_prefs.rs @@ -373,6 +373,134 @@ fn use_precise_cursors_makes_drawing_cursors_crosshairs() { assert_eq!(cursor(&mut s, "eyedropper"), Cursor::Eyedropper); } +/// Use Precise Cursors changes the pointer of the tools that have one of their own: off, each shows its own +/// pointer; on, a crosshair. Every other drawing tool shows a crosshair either way, and the Type tool and the +/// Scissors over no path keep theirs. +#[test] +fn use_precise_cursors_swaps_the_tools_own_pointers_for_a_crosshair() { + use vectorcraft_tools::{Cursor, Mods}; + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap(); + let at = vectorcraft_geom::Point::new(300.0, 300.0); + let cursor = |s: &mut Session, tool: &str| { + s.select_tool(tool, ViewInfo::default()).unwrap(); + s.cursor(at, Mods::default(), ViewInfo::default()) + }; + // Off: the pointer of each tool whose own pointer the preference replaces. + let own = [ + ("pen", Cursor::Pen), + ("addAnchor", Cursor::PenAdd), + ("deleteAnchor", Cursor::PenDelete), + ("anchorPoint", Cursor::Pen), + ("curvature", Cursor::Pen), + ("mesh", Cursor::PenAdd), + ("eyedropper", Cursor::Eyedropper), + ("slice", Cursor::Slice), + ("blend", Cursor::Blend), + ("shapeBuilder", Cursor::ShapeBuilder), + ]; + for (tool, c) in own { + assert_eq!(cursor(&mut s, tool), c, "{tool} off"); + } + // The tools that show a crosshair whatever the preference says. + let always = [ + "lineSegment", + "arc", + "spiral", + "rectangularGrid", + "polarGrid", + "rectangle", + "roundedRectangle", + "ellipse", + "polygon", + "star", + "flare", + "paintbrush", + "blobBrush", + "shaper", + "pencil", + "smooth", + "pathEraser", + "join", + "eraser", + "knife", + "mirrorCut", + "lineCut", + "rectCut", + "gradient", + ]; + for tool in always { + assert_eq!(cursor(&mut s, tool), Cursor::Crosshair, "{tool} off"); + } + s.execute("prefs.set", &json!({"key": "usePreciseCursors", "value": true})).unwrap(); + for (tool, _) in own { + assert_eq!(cursor(&mut s, tool), Cursor::Crosshair, "{tool} on"); + } + for tool in always { + assert_eq!(cursor(&mut s, tool), Cursor::Crosshair, "{tool} on"); + } + assert_eq!(cursor(&mut s, "type"), Cursor::Text); + assert_eq!(cursor(&mut s, "scissors"), Cursor::NotAllowed); + s.execute("prefs.set", &json!({"key": "usePreciseCursors", "value": false})).unwrap(); + for (tool, c) in own { + assert_eq!(cursor(&mut s, tool), c, "{tool} off again"); + } +} + +/// Every state of the Pen's pointer, and the other drawing pointers, turn into a crosshair; the rest stay. +#[test] +fn precise_cursors_replace_every_pen_state() { + use vectorcraft_tools::Cursor; + for c in [ + Cursor::Pen, + Cursor::PenAdd, + Cursor::PenDelete, + Cursor::PenClose, + Cursor::PenContinue, + Cursor::PenJoin, + Cursor::PenConvert, + Cursor::Eyedropper, + Cursor::Slice, + Cursor::Blend, + Cursor::BlendObject, + Cursor::BlendAnchor, + Cursor::ShapeBuilder, + Cursor::ShapeBuilderErase, + ] { + assert_eq!(c.precise(), Cursor::Crosshair, "{c:?}"); + } + for c in [Cursor::Arrow, Cursor::Move, Cursor::Text, Cursor::Hand, Cursor::NotAllowed, Cursor::SliceSelect] { + assert_eq!(c.precise(), c, "{c:?}"); + } +} + +/// Switching the preference is not an edit: no undo step, no unsaved changes. +#[test] +fn use_precise_cursors_is_not_an_edit() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap(); + let before = (s.active().unwrap().history.undo.len(), s.active().unwrap().history.redo.len()); + assert!(!s.active().unwrap().is_dirty()); + s.execute("prefs.set", &json!({"key": "usePreciseCursors", "value": true})).unwrap(); + assert!(s.prefs.use_precise_cursors); + let st = s.active().unwrap(); + assert_eq!((st.history.undo.len(), st.history.redo.len()), before, "no undo step"); + assert!(!st.is_dirty(), "the document stays saved"); +} + +/// The preference is saved with the others and reads back as it was. +#[test] +fn use_precise_cursors_is_saved_and_restored() { + let mut s = Session::new(); + assert!(!s.prefs.use_precise_cursors, "off by default"); + s.execute("prefs.set", &json!({"key": "usePreciseCursors", "value": true})).unwrap(); + let saved = s.prefs.to_json(); + assert_eq!(saved["usePreciseCursors"], json!(true)); + let mut t = Session::new(); + t.apply_prefs(serde_json::from_value(saved).unwrap()); + assert!(t.prefs.use_precise_cursors); +} + // ---------- #394, second batch ---------- fn new_doc() -> Session { diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index fbabb16a..5c767b06 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -484,7 +484,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { { crate::place::paint_cursor(app, ui.ctx(), &painter, hp); } - // Caps Lock gives precise (crosshair) cursors, like Illustrator; env opt-out for system cursors. + // The glyph cursors are drawn here; General › Use Precise Cursors (the session turns the drawing tools' pointers + // into crosshairs, `Cursor::precise`) needs no handling. VECTORCRAFT_SYSTEM_CURSORS opts out of the glyphs. let custom = std::env::var_os("VECTORCRAFT_SYSTEM_CURSORS").is_none(); // User Interface › Scale Cursor Proportional to UI: the glyphs grow with UI Scaling; // off, they keep their size. @@ -2209,6 +2210,32 @@ mod tests { use vectorcraft_engine::Session; use vectorcraft_geom::Shape as _; + /// With VECTORCRAFT_SYSTEM_CURSORS set the glyphs aren't drawn and the system's cursors stand in: those of + /// the Pen and the other drawing tools are the system crosshair already, so Use Precise Cursors makes no + /// difference to them there. + #[test] + fn system_cursors_show_the_same_crosshair_with_precise_cursors_on_or_off() { + for c in [ + Cursor::Pen, + Cursor::PenAdd, + Cursor::PenDelete, + Cursor::PenClose, + Cursor::PenContinue, + Cursor::PenJoin, + Cursor::PenConvert, + Cursor::Eyedropper, + Cursor::Slice, + Cursor::Blend, + Cursor::BlendObject, + Cursor::BlendAnchor, + Cursor::ShapeBuilder, + Cursor::ShapeBuilderErase, + ] { + assert_eq!(cursor_icon(c), egui::CursorIcon::Crosshair, "{c:?}"); + assert_eq!(cursor_icon(c), cursor_icon(c.precise()), "{c:?}"); + } + } + /// #826: in a turned view each ruler still has ticks along its whole length; at 0° the top one /// reads x where the canvas has it. #[test] diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index ea6fcfd7..b149b38c 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -494,6 +494,7 @@ Three Point Perspective Tříbodová perspektiva Save Grid as Preset… Uložit mřížku jako přednastavení… Snap to Pixel Přichytávat k obrazovým bodům + Precise Cursors Přesné kurzory Snap to Point Přichytávat k bodům Snap to Glyph Přichytávat ke glyfům New View… Nové zobrazení… diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 9a7fa4b3..314fcfe9 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2724,6 +2724,7 @@ effect Points Punkte Snap to Glyph An Glyphe ausrichten Snap to Grid Am Raster ausrichten Snap to Pixel An Pixel ausrichten + Precise Cursors Präzise Cursor Snap to Point An Punkt ausrichten Snapping Tolerance Einrasttoleranz Social Square Post 1080×1080 Quadratischer Social-Media-Beitrag 1080×1080 diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 81a0f002..62006ec7 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2774,6 +2774,7 @@ Snap to Glyph Ajustar a glifo Snap to Grid Ajustar a cuadrícula Snap to Pixel Ajustar a píxel + Precise Cursors Cursores precisos Snap to Point Ajustar a punto Snapping Tolerance Tolerancia de ajuste Social Square Post 1080×1080 Publicación cuadrada para redes 1080×1080 diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 8697d233..6c59ce3a 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2771,6 +2771,7 @@ Snap to Glyph Magnétisme du glyphe Snap to Grid Magnétisme de la grille Snap to Pixel Magnétisme du pixel + Precise Cursors Pointeurs précis Snap to Point Magnétisme du point Snapping Tolerance Tolérance du magnétisme Social Square Post 1080×1080 Publication carrée pour réseaux sociaux 1080×1080 diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index c23ad4cd..16e388e8 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -3358,6 +3358,7 @@ Snap to Glyph Illesztés glifához Snap to Grid Illesztés a rácshoz Snap to Pixel Illesztés pixelhez + Precise Cursors Pontos kurzorok Snap to Point Illesztés pontra Snapping Tolerance Illesztési tűrés Social Közösségi média diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 3f344406..b5bbbbb9 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2774,6 +2774,7 @@ Snap to Glyph Aggancia a glifo Snap to Grid Aggancia alla griglia Snap to Pixel Aggancia ai pixel + Precise Cursors Cursori di precisione Snap to Point Aggancia al punto Snapping Tolerance Tolleranza di aggancio Social Square Post 1080×1080 Post quadrato per social 1080×1080 diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index b8693800..71feaa8b 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -514,6 +514,7 @@ Three Point Perspective 3 点遠近法 Save Grid as Preset… グリッドをプリセットとして保存… Snap to Pixel ピクセルにスナップ + Precise Cursors 精密カーソル Snap to Point ポイントにスナップ Snap to Glyph グリフにスナップ New View… 新規表示… diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index a26184f2..aacc856e 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -512,6 +512,7 @@ Three Point Perspective 3점 투시 Save Grid as Preset… 격자를 프리셋으로 저장… Snap to Pixel 픽셀에 스냅 + Precise Cursors 정밀 커서 Snap to Point 포인트에 스냅 Snap to Glyph 글리프에 스냅 New View… 새 보기… diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 456fc9da..fbd84c2b 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -489,6 +489,7 @@ Three Point Perspective Perspektywa trzypunktowa Save Grid as Preset… Zapisz siatkę jako ustawienie… Snap to Pixel Przyciągaj do pikseli + Precise Cursors Precyzyjne kursory Snap to Point Przyciągaj do punktów Snap to Glyph Przyciągaj do glifów New View… Nowy widok… diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 46d78843..68edce61 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2306,6 +2306,7 @@ Snap to Glyph Ajustar ao Glifo Snap to Grid Ajustar à Grade Snap to Pixel Ajustar ao Pixel + Precise Cursors Cursores Precisos Snap to Point Ajustar ao Ponto Snapping Tolerance Tolerância de Ajuste Soft Light Luz Suave diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index c7a4e2e3..30ed911a 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2770,6 +2770,7 @@ Snap to Glyph Привязать к глифу Snap to Grid Привязать к сетке Snap to Pixel Привязать к пикселю + Precise Cursors Точные курсоры Snap to Point Привязать к точке Snapping Tolerance Допуск привязки Social Square Post 1080×1080 Квадратный пост для соцсетей 1080×1080 diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index a1cf2df4..f591f62e 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2767,6 +2767,7 @@ effect Points Точки Snap to Glyph Прив’язати до гліфу Snap to Grid Прив’язати до сітки Snap to Pixel Прив’язати до пікселя + Precise Cursors Точні курсори Snap to Point Прив’язати до точки Snapping Tolerance Допуск прив’язки Social Square Post 1080×1080 Квадратний пост для соцмереж 1080×1080 diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 7c1c6c96..f5348af2 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -3075,6 +3075,7 @@ Snap to Glyph Hút vào glyph Snap to Grid Hút vào lưới Snap to Pixel Hút vào pixel + Precise Cursors Con trỏ chính xác Snap to Point Hút vào điểm Snapping Tolerance Dung sai hút Social Square Post 1080×1080 Bài đăng vuông mạng xã hội 1080×1080 diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index fd972cdb..e8ab8808 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2661,6 +2661,7 @@ Snap to Glyph 靠齐字符 Snap to Grid 对齐网格 Snap to Pixel 对齐像素 + Precise Cursors 精确光标 Snap to Point 对齐点 Snapping Tolerance 吸附容差 Social Square Post 1080×1080 社交方形帖子 1080×1080 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index bdec1b1d..eafb4368 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2752,6 +2752,7 @@ Snap to Glyph 靠齊字符 Snap to Grid 靠齊格點 Snap to Pixel 靠齊像素 + Precise Cursors 精確游標 Snap to Point 靠齊控制點 Snapping Tolerance 靠齊容許度 Social Square Post 1080×1080 社群正方形貼文 1080×1080 diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 0a39614b..700f86c3 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -186,6 +186,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ ("view.trimView", "Trim View", "", "{} toggle: hide everything outside the artboards"), ("view.cornerWidget", "Hide Corner Widget", "", "{} toggle the live corner widgets"), ("view.snapToPixel", "Snap to Pixel", "", "{} toggle: drawing and moving land on whole pixels"), + ( + "view.preciseCursors", + "Precise Cursors", + "", + "{} toggle General › Use Precise Cursors: the drawing tools' pointers become a crosshair → true when on", + ), ("view.textThreads", "Hide Text Threads", "Cmd+Shift+Y", "{} toggle the thread lines between threaded text frames"), ("view.gradientAnnotator", "Hide Gradient Annotator", "Cmd+Alt+G", "{} toggle the Gradient tool's annotator"), ("type.hiddenCharacters", "Show Hidden Characters", "Cmd+Alt+I", "{} toggle markers for spaces, paragraph ends and story ends"), @@ -1053,6 +1059,15 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option Err("no such saved selection".into()), }, "view.snapToPixel" => flag(&mut app.ui.view.snap_to_pixel), + // The preference itself (General › Use Precise Cursors), so the command, the dialog and the saved + // preferences share one value. + "view.preciseCursors" => { + let on = !app.session.prefs.use_precise_cursors; + app.run("prefs.set", json!({"key": "usePreciseCursors", "value": on})).map(|_| { + crate::prefs_dialog::snapshot(app); + json!(on) + }) + } "view.textThreads" => flag(&mut app.ui.view.text_threads), "type.hiddenCharacters" => flag(&mut app.ui.view.hidden_chars), "type.bold" => crate::panels::character::toggle_face(app, Face::Bold), @@ -3993,6 +4008,55 @@ mod tests { assert_eq!(dynamic_label(&app, "view.cornerWidget", ""), "Show Corner Widget"); } + /// View › Precise Cursors has no menu item or default key, but the command (assignable in Keyboard Shortcuts) + /// writes General › Use Precise Cursors, the value the dialog and the saved preferences read. + #[test] + fn precise_cursors_command_toggles_the_preference() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default()); + assert!(!app.session.prefs.use_precise_cursors); + assert_eq!(app.run("view.preciseCursors", json!({})).unwrap(), json!(true)); + assert!(app.session.prefs.use_precise_cursors); + assert_eq!(app.ui.engine_prefs["usePreciseCursors"], json!(true), "saved with the UI state"); + assert!(UI_COMMANDS.iter().any(|c| c.0 == "view.preciseCursors" && c.2.is_empty()), "assignable, no default key"); + assert_eq!(app.run("view.preciseCursors", json!({})).unwrap(), json!(false)); + assert!(!app.session.prefs.use_precise_cursors); + } + + /// The command is listed in Edit › Keyboard Shortcuts without a key and takes one the user types; with the + /// key it is the command that runs. + #[test] + fn precise_cursors_command_is_listed_and_assignable_in_the_shortcut_editor() { + use crate::shortcut_editor as ed; + let e = ed::entry("view.preciseCursors").expect("listed in the editor"); + assert_eq!((e.label.as_str(), e.group.as_str(), e.is_tool), ("Precise Cursors", "View", false)); + assert_eq!(ed::default_of("view.preciseCursors"), None, "no default key"); + let mut overrides = std::collections::BTreeMap::new(); + assert_eq!(ed::effective_in(&overrides, "view.preciseCursors"), None); + assert_eq!(ed::assign(&mut overrides, "view.preciseCursors", Some("Cmd+Alt+Shift+U"), false), Ok(vec![])); + let chord = ed::effective_in(&overrides, "view.preciseCursors").expect("assigned"); + assert_eq!(ed::normalize(&chord), ed::normalize("Cmd+Alt+Shift+U")); + // A key another command holds is refused, so the new command can't take it over silently. + assert!(ed::assign(&mut overrides, "view.preciseCursors", Some("Cmd+Alt+Shift+P"), false).is_err()); + assert_eq!(ed::assign(&mut overrides, "view.preciseCursors", None, false), Ok(vec![])); + assert_eq!(ed::effective_in(&overrides, "view.preciseCursors"), None, "cleared again"); + } + + /// The command is a preference, not an edit: no undo step, no unsaved changes, with a document open. + #[test] + fn precise_cursors_command_is_not_an_edit() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default()); + app.run("file.new", json!({})).unwrap(); + let steps = |app: &VectorcraftApp| { + let st = app.session.active().unwrap(); + (st.history.undo.len(), st.history.redo.len(), st.is_dirty()) + }; + let before = steps(&app); + assert!(!before.2); + app.run("view.preciseCursors", json!({})).unwrap(); + assert!(app.session.prefs.use_precise_cursors); + assert_eq!(steps(&app), before); + } + /// Selection & Anchor Display › Zoom to Selection (#394): Zoom In and Zoom Out bring the /// selection to the middle; off, they keep the view's centre. #[test]