diff --git a/crates/engine/src/cmd/prefscmds.rs b/crates/engine/src/cmd/prefscmds.rs index c5272f591..ff2d79f4b 100644 --- a/crates/engine/src/cmd/prefscmds.rs +++ b/crates/engine/src/cmd/prefscmds.rs @@ -138,6 +138,7 @@ pub const PREF_SPECS: &[PrefSpec] = &[ p!("scaleStrokes", "General", "Options", "Scale Strokes & Effects", bool), p!("zoomWithMouseWheel", "General", "Options", "Zoom with Mouse Wheel", bool), p!("scrubNumericFields", "General", "Options", "Scrub Numeric Fields by Dragging", bool), + p!("trackpadRotateView", "General", "Options", "Trackpad Gesture to Rotate View", bool), // Selection & Anchor Display p!("selectionTolerance", "Selection & Anchor Display", "Selection", "Tolerance", num(1.0, 8.0, "px")), p!("objectSelectionByPathOnly", "Selection & Anchor Display", "Selection", "Object Selection by Path Only", bool), diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index a0aed8c5d..526b915a8 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -737,6 +737,8 @@ pub struct Prefs { pub zoom_with_mouse_wheel: bool, /// A horizontal drag on a numeric field or its label steps its value (#400). pub scrub_numeric_fields: bool, + /// A two-finger rotation on a Mac trackpad turns the view about the pointer once it reaches 5°. + pub trackpad_rotate_view: bool, /// Offset for Paste / duplicate (Illustrator pastes to the view centre; we offset by this). pub paste_offset: f64, // Selection & Anchor Display @@ -992,6 +994,7 @@ impl Default for Prefs { scale_strokes: false, zoom_with_mouse_wheel: false, scrub_numeric_fields: true, + trackpad_rotate_view: true, paste_offset: 10.0, selection_tolerance: 3.0, object_selection_by_path_only: false, diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index b3f5217d8..7db01c139 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -709,7 +709,7 @@ fn handle_input(app: &mut VectorcraftApp, ui: &Ui, resp: &egui::Response, rect: }); // Zoom around the pointer, and scroll ([`wheel`]): fingers pinching on a touch screen may also - // slide. + // slide. Then turn the view about the pointer for a trackpad rotation ([`trackpad_turn`]). if resp.hovered() { if (factor - 1.0).abs() > 1e-6 && let (Some(p), Some(vm)) = (hover, app.view_mut()) @@ -722,6 +722,12 @@ fn handle_input(app: &mut VectorcraftApp, ui: &Ui, resp: &egui::Response, rect: let d = Xf { rect, zoom: vm.zoom, center: vm.center, rot: vm.rotation.to_radians() }.delta_to_doc(scroll); vm.center -= d; } + if let Some(p) = hover + && let Some(deg) = trackpad_turn(ui, app.session.prefs.trackpad_rotate_view) + && let Some(vm) = app.view_mut() + { + rotate_about(vm, rect, p, deg); + } } // In Presentation Mode a click anywhere goes to the next artboard. The tool receives no press, @@ -981,6 +987,65 @@ fn zoom_about(vm: &mut View, rect: egui::Rect, p: Pos2, zoom: f64) { vm.center += before - Xf::new(rect, vm).to_doc(p); } +/// Turn view `vm` of the canvas `rect` by `degrees` clockwise, keeping the document point under +/// screen point `p` where it is. +fn rotate_about(vm: &mut View, rect: egui::Rect, p: Pos2, degrees: f64) { + let before = Xf::new(rect, vm).to_doc(p); + vm.rotation = vectorcraft_geom::normalize_deg(vm.rotation + degrees); + vm.center += before - Xf::new(rect, vm).to_doc(p); +} + +/// How far a trackpad rotation turns before it turns the view, in degrees. +const ROTATE_DEAD_ZONE: f64 = 5.0; +/// How long after its last rotation event a trackpad rotation ends, in seconds. +const ROTATE_GESTURE_END: f64 = 0.25; + +/// A trackpad rotation under way ([`trackpad_turn`]). +#[derive(Clone, Copy, Debug)] +struct TrackpadTurn { + /// Degrees it has turned so far, clockwise. + total: f64, + /// When its last rotation event came (egui's input time, in seconds). + last: f64, + /// It has reached [`ROTATE_DEAD_ZONE`] and turns the view. + turning: bool, +} + +/// General › Trackpad Gesture to Rotate View (`on`): the degrees, clockwise, that a two-finger +/// rotation on a Mac trackpad turns the view this frame. egui sends the rotation as +/// [`egui::Event::Rotate`], from winit on macOS and from Safari's gesture events in the web build. +/// A rotation does not turn the view until it reaches [`ROTATE_DEAD_ZONE`]; it then turns the view +/// by all of it, and by each later event as it comes. It ends [`ROTATE_GESTURE_END`] after its last +/// event. Rotation events that come while fingers are on a touch screen do not turn the view. +fn trackpad_turn(ui: &Ui, on: bool) -> Option { + if !on { + return None; + } + let (deg, now) = ui.input(|i| { + let rad: f32 = + if i.multi_touch().is_some() { 0.0 } else { i.events.iter().map(|e| if let egui::Event::Rotate(r) = e { *r } else { 0.0 }).sum() }; + (f64::from(rad).to_degrees(), i.time) + }); + if deg == 0.0 || !deg.is_finite() { + return None; + } + let id = egui::Id::new("canvas-trackpad-turn"); + let kept: Option = ui.data(|d| d.get_temp(id)); + let mut t = kept.filter(|t| now - t.last <= ROTATE_GESTURE_END).unwrap_or(TrackpadTurn { total: 0.0, last: now, turning: false }); + t.total += deg; + t.last = now; + let turn = if t.turning { + Some(deg) + } else if t.total.abs() >= ROTATE_DEAD_ZONE { + t.turning = true; + Some(t.total) + } else { + None + }; + ui.data_mut(|d| d.insert_temp(id, t)); + turn +} + /// One scroll bar along its axis, in screen points: the span it scrolls over (`lo..hi`) and the /// part of it the canvas shows (`v0..v1`). #[derive(Clone, Copy, Debug, PartialEq)] @@ -5111,6 +5176,96 @@ mod tests { } } + /// A canvas frame at input time `t` (seconds) with `events`, for the trackpad rotation tests. + fn timed_frame(app: &mut VectorcraftApp, ctx: &egui::Context, t: f64, events: Vec) { + let raw = egui::RawInput { + screen_rect: Some(egui::Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0))), + time: Some(t), + events, + ..Default::default() + }; + let mut out = ctx.run_ui(raw, |ui| show(app, ui)); + out.textures_delta.clear(); + } + + /// General › Trackpad Gesture to Rotate View, on by default: a two-finger rotation on a Mac + /// trackpad (egui's `Event::Rotate`, clockwise radians) turns the view about the pointer, also + /// in the frame of a pinch, which zooms about the same point. With the preference off, the view + /// does not turn. + #[test] + fn a_trackpad_rotation_turns_the_view_about_the_pointer() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + let ctx = egui::Context::default(); + timed_frame(&mut app, &ctx, 0.0, vec![]); + let rect = app.canvas_rect.unwrap(); + let at = rect.center() + vec2(60.0, 40.0); + timed_frame(&mut app, &ctx, 0.1, vec![egui::Event::PointerMoved(at)]); + let under = |app: &VectorcraftApp| Xf::new(rect, app.view().unwrap()).to_doc(at); + let before = (*app.view().unwrap(), under(&app)); + timed_frame(&mut app, &ctx, 0.2, vec![egui::Event::Rotate(0.25)]); + let after = *app.view().unwrap(); + assert!((after.rotation - before.0.rotation - 0.25f64.to_degrees()).abs() < 1e-4, "turned by 14.32°: {}", after.rotation); + assert!((after.zoom - before.0.zoom).abs() < 1e-12, "no zoom"); + assert!(under(&app).distance(before.1) < 1e-6, "the point under the pointer stays: {:?}", under(&app)); + // A pinch and a rotation in one frame, in a new gesture. + let before = (*app.view().unwrap(), under(&app)); + timed_frame(&mut app, &ctx, 1.0, vec![egui::Event::Zoom(1.25), egui::Event::Rotate(0.25)]); + let after = *app.view().unwrap(); + assert!((after.zoom / before.0.zoom - 1.25).abs() < 1e-6, "the pinch zooms: {}", after.zoom / before.0.zoom); + assert!((after.rotation - before.0.rotation - 0.25f64.to_degrees()).abs() < 1e-4, "and the rotation turns: {}", after.rotation); + assert!(under(&app).distance(before.1) < 1e-6, "about the same point: {:?}", under(&app)); + app.session.execute("prefs.set", &json!({"key": "trackpadRotateView", "value": false})).unwrap(); + let before = *app.view().unwrap(); + timed_frame(&mut app, &ctx, 2.0, vec![egui::Event::Rotate(0.25)]); + timed_frame(&mut app, &ctx, 2.1, vec![egui::Event::Rotate(0.25)]); + let after = *app.view().unwrap(); + assert!(after.rotation == before.rotation && after.center == before.center, "off: the view does not turn ({})", after.rotation); + } + + /// A trackpad rotation turns the view only once it reaches 5°, so a pinch that twists a little + /// only zooms. It then turns the view by all of it and follows each later rotation event, until + /// a quarter of a second passes without one. + #[test] + fn a_trackpad_rotation_turns_nothing_until_it_reaches_five_degrees() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + let ctx = egui::Context::default(); + timed_frame(&mut app, &ctx, 0.0, vec![]); + let at = app.canvas_rect.unwrap().center() + vec2(60.0, 40.0); + timed_frame(&mut app, &ctx, 0.1, vec![egui::Event::PointerMoved(at)]); + let rotate = |deg: f32| egui::Event::Rotate(deg.to_radians()); + let zoom = app.view().unwrap().zoom; + timed_frame(&mut app, &ctx, 1.0, vec![egui::Event::Zoom(1.25), rotate(1.0)]); + assert!(app.view().unwrap().zoom > zoom * 1.2, "the pinch zooms"); + timed_frame(&mut app, &ctx, 1.1, vec![egui::Event::Zoom(1.25), rotate(1.0)]); + timed_frame(&mut app, &ctx, 1.2, vec![egui::Event::Zoom(1.25), rotate(1.0)]); + assert_eq!(app.view().unwrap().rotation, 0.0, "3° in all does not turn the view"); + timed_frame(&mut app, &ctx, 1.3, vec![rotate(3.0)]); + assert!((app.view().unwrap().rotation - 6.0).abs() < 1e-4, "6° in all turns by 6°: {}", app.view().unwrap().rotation); + timed_frame(&mut app, &ctx, 1.4, vec![rotate(-1.0)]); + assert!((app.view().unwrap().rotation - 5.0).abs() < 1e-4, "then each event turns it: {}", app.view().unwrap().rotation); + timed_frame(&mut app, &ctx, 2.0, vec![rotate(3.0)]); + assert!((app.view().unwrap().rotation - 5.0).abs() < 1e-4, "a new rotation starts under 5° again: {}", app.view().unwrap().rotation); + } + + /// Rotation events that come while two fingers are on a touch screen (Safari on an iPad sends + /// them with the touches) do not turn the view. + #[test] + fn a_rotation_with_fingers_on_a_touch_screen_turns_nothing() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + let ctx = egui::Context::default(); + timed_frame(&mut app, &ctx, 0.0, vec![]); + let c = app.canvas_rect.unwrap().center(); + let touch = |n: u64, phase, pos| egui::Event::Touch { device_id: egui::TouchDeviceId(1), id: egui::TouchId(n), phase, pos, force: None }; + timed_frame(&mut app, &ctx, 0.1, vec![egui::Event::PointerMoved(c - vec2(40.0, 0.0))]); + let before = app.view().unwrap().rotation; + let start = vec![touch(1, egui::TouchPhase::Start, c - vec2(40.0, 0.0)), touch(2, egui::TouchPhase::Start, c + vec2(40.0, 0.0))]; + timed_frame(&mut app, &ctx, 0.2, [start, vec![egui::Event::Rotate(0.25)]].concat()); + assert_eq!(app.view().unwrap().rotation, before, "the view does not turn"); + } + /// General › Display Print Size at 100% Zoom (#394): `view.actualSize` is one document point per /// screen point with it off, and 96 screen points per document inch with it on. #[test] diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 5923f939e..567263cfc 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2488,6 +2488,7 @@ Rotate 90° CW Завъртане на 90° по часовниковата стрелка Rotate Tool Инструмент „Завъртане“ Rotate View Tool Инструмент „Завъртане на изглед“ + Trackpad Gesture to Rotate View Завъртане на изгледа с жест на тракпада Rotate… Завъртане… Rotation Завъртане Roughen… Грапавост… diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index f3392aaca..73416d3c5 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1227,6 +1227,7 @@ lighting Light Světlo Reshape Tool Nástroj Přetvarování Rotate Tool Nástroj Otočení Rotate View Tool Nástroj Otočení zobrazení + Trackpad Gesture to Rotate View Otáčet zobrazení gestem na trackpadu Rotate View Otočení zobrazení Rounded Rectangle Tool Nástroj Zaoblený obdélník Scale Tool Nástroj Změna velikosti diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index af453f6da..ecaa6f275 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1152,6 +1152,7 @@ lighting Light Lys Reshape Tool Omformningsværktøj Rotate Tool Roteringsværktøj Rotate View Tool Visningsroteringsværktøj + Trackpad Gesture to Rotate View Rotér visning med bevægelse på pegefeltet Rotate View Rotér visning Rounded Rectangle Tool Afrundet rektangelværktøj Scale Tool Skaleringsværktøj diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 53e9761d4..802e182d1 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2500,6 +2500,7 @@ mark-style Japanese Japanisch Rotate 90° CW Um 90° im Uhrzeigersinn drehen Rotate Tool Drehen-Werkzeug Rotate View Tool Ansichtdrehung-Werkzeug + Trackpad Gesture to Rotate View Ansicht mit Trackpad-Geste drehen Rotate… Drehen… Rotation Drehung Roughen… Aufrauen… diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 19ae037cd..b6528bebb 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1670,6 +1670,7 @@ Roman Hanging Punctuation Κρεμαστή στίξη όρθιας γραφής Rotate Περιστροφή Rotate View Tool Εργαλείο περιστροφής προβολής (Rotate View Tool) + Trackpad Gesture to Rotate View Περιστροφή προβολής με χειρονομία στην επιφάνεια αφής Rotate… Περιστροφή… Rotation Περιστροφή Rows: Γραμμές: diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index e350de389..61be275b3 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2534,6 +2534,7 @@ Rotate 90° CW Rotar 90° en sentido horario Rotate Tool Herramienta Rotar Rotate View Tool Herramienta Rotar vista + Trackpad Gesture to Rotate View Rotar vista con un gesto del trackpad Rotate… Rotar… Rotation Rotación Roughen… Hacer áspero… diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 3838ec01f..a3a6e7902 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -1203,6 +1203,7 @@ lighting Light Valo Reshape Tool Uudelleenmuotoilutyökalu Rotate Tool Kiertotyökalu Rotate View Tool Näkymän kiertotyökalu + Trackpad Gesture to Rotate View Kierrä näkymää ohjauslevyn eleellä Rotate View Kierrä näkymää Rounded Rectangle Tool Pyöristetyn suorakulmion työkalu Scale Tool Skaalaustyökalu diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 099e2c273..bf3205141 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2531,6 +2531,7 @@ Rotate 90° CW Rotation de 90° horaire Rotate Tool Outil Rotation Rotate View Tool Outil Faire pivoter l’affichage + Trackpad Gesture to Rotate View Faire pivoter l’affichage avec un geste du trackpad Rotate… Rotation… Rotation Rotation Roughen… Rugosité… diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 08ae8a4dd..efabf2eef 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -3095,6 +3095,7 @@ mark-style Japanese Japán Rotate Tool Elforgatás Rotate View Nézet elforgatása Rotate View Tool Nézet elforgatása + Trackpad Gesture to Rotate View Nézet elforgatása kézmozdulattal az érintőpadon Rotate… Elforgatás… Rotation Elforgatás Rotation relative to: Elforgatás viszonyítási alapja: diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index fb10eb8a9..acd7a1fbd 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1152,6 +1152,7 @@ lighting Light Cahaya Reshape Tool Alat pembentuk ulang Rotate Tool Alat putar Rotate View Tool Alat putar tampilan + Trackpad Gesture to Rotate View Putar tampilan dengan gestur trackpad Rotate View Putar tampilan Rounded Rectangle Tool Alat persegi panjang bulat Scale Tool Alat skala diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index ee593b23b..87f94d523 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2551,6 +2551,7 @@ mark-style Japanese Giapponese Rotate 90° CW Ruota di 90° in senso orario Rotate Tool Strumento Ruota Rotate View Tool Strumento Ruota vista + Trackpad Gesture to Rotate View Ruota vista con un gesto sul trackpad Rotate… Ruota… Rotation Rotazione Roughen… Irruvidisci… diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 238fc633d..f903169bd 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2482,6 +2482,7 @@ mark-style Japanese 日本式 Rotate 回転 Rotate Tool 回転ツール Rotate View Tool 回転ビューツール + Trackpad Gesture to Rotate View トラックパッドのジェスチャーでビューを回転 Rotation 回転 Round Cap 丸型線端 Round Join ラウンド結合 diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index e185f412f..d0d967763 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2476,6 +2476,7 @@ mark-style Japanese 일본식 Rotate 회전 Rotate Tool 회전 도구 Rotate View Tool 보기 회전 도구 + Trackpad Gesture to Rotate View 트랙패드 제스처로 보기 회전 Rotation 회전 Round Cap 둥근 끝 Round Join 둥근 연결 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index de7483a8b..b911939ac 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1152,6 +1152,7 @@ lighting Light Licht Reshape Tool Hervormen-gereedschap Rotate Tool Draaien-gereedschap Rotate View Tool Weergave draaien-gereedschap + Trackpad Gesture to Rotate View Weergave draaien met trackpadgebaar Rotate View Weergave draaien Rounded Rectangle Tool Afgeronde rechthoek-gereedschap Scale Tool Schalen-gereedschap diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 3e8c86fb8..f680fe99e 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1149,6 +1149,7 @@ lighting Light Światło Reshape Tool Narzędzie Zmiana kształtu Rotate Tool Narzędzie Obrót Rotate View Tool Narzędzie Obrót widoku + Trackpad Gesture to Rotate View Obracaj widok gestem na gładziku Rounded Rectangle Tool Narzędzie Zaokrąglony prostokąt Scale Tool Narzędzie Skalowanie Scallop Tool Narzędzie Ząbkowanie diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index c6f79cb2b..18c98489f 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2094,6 +2094,7 @@ Rotate Girar Rotate Tool Ferramenta Girar Rotate View Tool Ferramenta Girar Visualização + Trackpad Gesture to Rotate View Girar Visualização com Gesto do Trackpad Rotate… Girar… Rotation Rotação Roughen… Enrugar… diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 16da8def5..473985077 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2547,6 +2547,7 @@ mark-style Japanese Японский Rotate 90° CW Повернуть на 90° по часовой стрелке Rotate Tool Инструмент «Поворот» Rotate View Tool Инструмент «Поворот вида» + Trackpad Gesture to Rotate View Поворачивать вид жестом трекпада Rotate… Повернуть… Rotation Поворот Roughen… Огрубление… diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index ffb4b06b5..d09d3b30d 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -2488,6 +2488,7 @@ Rotate 90° CW Rotera 90° medurs Rotate Tool Rotationsverktyg Rotate View Tool Verktyget Rotera vy + Trackpad Gesture to Rotate View Rotera vyn med en gest på styrplattan Rotate… Rotera… Rotation Rotation Roughen… Ojämn… diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 6e862b841..bf4c2c3ef 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2543,6 +2543,7 @@ mark-style Japanese Японський Rotate 90° CW Повернути на 90° за годинниковою стрілкою Rotate Tool Інструмент «Поворот» Rotate View Tool Інструмент «Поворот виду» + Trackpad Gesture to Rotate View Повертати вид жестом трекпада Rotate… Повернути… Rotation Поворот Roughen… Надати нерівності… diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 3a9ceb717..1c7209bc6 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2834,6 +2834,7 @@ mark-style Japanese Kiểu Nhật Rotate 90° CW Xoay 90° theo chiều kim đồng hồ Rotate Tool Công cụ Xoay Rotate View Tool Công cụ Xoay chế độ xem + Trackpad Gesture to Rotate View Xoay chế độ xem bằng cử chỉ trên bàn di chuột Rotate… Xoay… Rotation Góc xoay Rotation relative to: Xoay tương đối với: diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index db7e698f8..2f45a416e 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2441,6 +2441,7 @@ mark-style Japanese 日式 Rotate 90° CW 顺时针旋转 90° Rotate Tool 旋转工具 Rotate View Tool 旋转视图工具 + Trackpad Gesture to Rotate View 使用触控板手势旋转视图 Rotate… 旋转… Rotation 旋转 Roughen… 粗糙效果… diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index de1e1121e..15cebb0e8 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2525,6 +2525,7 @@ mark-style Japanese 日式 Rotate 90° CW 順時針旋轉 90° Rotate Tool 旋轉工具 Rotate View Tool 旋轉檢視工具 + Trackpad Gesture to Rotate View 使用觸控式軌跡板手勢旋轉檢視 Rotate… 旋轉… Rotation 旋轉 Roughen… 粗糙效果… diff --git a/docs/hardware-parity.md b/docs/hardware-parity.md index f7ada46b3..4fe604865 100644 --- a/docs/hardware-parity.md +++ b/docs/hardware-parity.md @@ -22,7 +22,7 @@ on huge documents. Pen input is pressure-only and Windows-first. | Pen pressure | yes (Wacom, Windows Ink, macOS tablet events) | **no** (winit reports none, #852) | yes (pointer force via `WM_POINTER`) | X11 only via XWayland (Preferences › Performance › Linux Display Server, #1151); none on Wayland (#491, #764) | browser pointer events, with coalesced events; Apple Pencil on iPad (hover, palm rejection: fingers navigate after a pen contact until reload) | partial: 4–8 h for macOS and Wayland (winit 0.31) | | Pen tilt, bearing, barrel rotation (6D Art Pen) | yes, drives brushes | no | no | no | no | missing: 4–8 h after the input layer (#372) | | Pen eraser end | yes | no | no | no | no | missing | -| Touch gestures (pinch zoom, two-finger pan, rotate view, Touch workspace) | yes (Windows touch, Surface) | trackpad pinch and scroll | pinch, pan; two-finger tap undo, three-finger redo (#585) | same where winit reports touch | eframe until the first pen contact, then Pointer Events (pen and fingers told apart); long press opens the context menu | partial: touch workspace and rotate gesture missing, 3–5 h | +| Touch gestures (pinch zoom, two-finger pan, rotate view, Touch workspace) | yes (Windows touch, Surface) | trackpad pinch, scroll and rotate | pinch, pan; two-finger tap undo, three-finger redo (#585) | same where winit reports touch | eframe until the first pen contact, then Pointer Events (pen and fingers told apart); long press opens the context menu | partial: touch workspace and touch-screen rotation missing, 3–5 h | | Trackpad (smooth scroll, pinch) | yes | yes | precision touchpads | yes | yes | done | | HiDPI / Retina, per-monitor scale | yes | yes | yes | yes | yes | done; a window dragged between monitors of different scale can still grow on Windows (#1459, open); the custom title bar's window on Windows stays truly borderless so the pointer lines up with what is drawn (#1791, #1758; awaiting the reporter's confirmation) | | Multiple monitors: panels and document windows on another screen | yes | no (one OS window) | no | no | — | missing: 6–10 h (with multiple windows, gap G7) | diff --git a/docs/mcp.md b/docs/mcp.md index 826b11938..08f9031df 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -2123,7 +2123,9 @@ The Selection & Anchor Display and General preferences apply to `pointer_gesture The preferences for the view need the desktop app (`vectorcraft-cli mcp --connect`): `zoomWithMouseWheel` (the wheel zooms about the pointer, Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways; the control channel's -`ui.wheel` turns the wheel), `zoomToSelection` (on: Zoom In and Zoom Out centre the selection), `showToolTips`, +`ui.wheel` turns the wheel), `trackpadRotateView` (on by default: a two-finger rotation on a Mac trackpad, in the +macOS app or in Safari, turns the view about the pointer once it reaches 5°; with it off, the gesture does not turn +the view), `zoomToSelection` (on: Zoom In and Zoom Out centre the selection), `showToolTips`, `anchorSize` (1–7), `gridColor`, `gridStyle`, `gridsInBack`, `guideColor`, `guideStyle`, `showPixelGrid` (on by default: in View › Pixel Preview at 600% zoom and above, a line at every document pixel over the art), `recentFontsCount`, `antiAliasedArtwork` (on by default; off, the canvas draws the art with hard edges — a pixel is diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index ca6a222b1..f756ba8f6 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -561,6 +561,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur View › Rotate View turns the view to the status bar's preset angles (`view.rotate {angle}`, which takes any angle). View › Rotate View to Selection turns the view to the angle of the selected objects' bounding box, so the box is square to the window (`view.rotateToSelection`). When the view angle changes (a Rotate View drag, the status bar's angle menu, Reset Rotate View or a saved view) and the desktop app renders the frame at the new angle on the render worker, the canvas draws the last frame turned to the new angle until that frame is ready. It used to draw no art until then. With the view rotated, a second Escape less than a second after the first resets the rotation, as Reset Rotate View does. The first Escape may end something else, such as isolation mode or a path being drawn. The second resets the view only when it has nothing else to end, and an Escape that closes a menu does not count. After a click, or a press of another key that is not a modifier (Shift, Control, Alt/Option or Command), the next Escape counts as the first. With the Rotate View tool chosen, one Escape resets the view. + A two-finger rotation on a Mac trackpad turns the view about the pointer once it reaches 5° (Preferences › General › Trackpad Gesture to Rotate View, on by default). The status bar navigator's field shows the active artboard's number and name, and its drop-down lists every artboard by number and name, as the Artboards panel does. Choosing an artboard in the list makes it the active artboard and fits it in the window (`view.goToArtboard`). The list opens on the active artboard's row and lays out only the rows in view and at most one more. - **Geometry and effects:** - **Open-path anchor Delete:** Delete/Backspace removes selected anchors and their adjoining segments, leaving drawable runs as separate open subpaths of the original object. Surviving curves and appearance stay intact; one Undo restores the document and selection, and a second Delete removes the selected remainder. Explicit Remove Anchor Points still reconnects/refits. Four core regressions, one full-frame Delete/Backspace regression and three existing removal/segment controls pass, covering these distinctions, multiple deletions, transformed geometry and mixed whole-object selection. Behaviour references: the legacy public [Illustrator reference guide, Add or delete an anchor point](https://helpx.adobe.com/content/dam/help/en/pdf/illustrator_reference.pdf) distinguishes keyboard deletion from dedicated removal; the current [Add or remove anchor points](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/add-or-remove-anchor-points.html) describes the dedicated tools. Other G11 gaps and scores/hours remain unchanged.