From 6d8751405589554eb908e1a5b85e8b5647cd07cd Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Mon, 2 Nov 2026 19:59:21 -0800 Subject: [PATCH] Selection: show marquee rules and offer enclosed and directional modes --- ROADMAP.md | 2 + crates/engine/src/cmd/prefscmds.rs | 7 +++ crates/engine/src/prefs.rs | 3 ++ crates/engine/src/tests/prefs.rs | 26 +++++++++++ crates/engine/src/tooling.rs | 5 ++ crates/tools/src/context.rs | 12 +++++ crates/tools/src/lib.rs | 5 +- crates/tools/src/select.rs | 48 +++++++++++++++---- crates/tools/src/select/tests.rs | 54 +++++++++++++++++++++- crates/ui-egui/src/canvas/overlays.rs | 23 +++++++++ crates/ui-egui/src/canvas/tests.rs | 59 ++++++++++++++++++++++++ crates/ui-egui/src/i18n/bg.tsv | 5 ++ crates/ui-egui/src/i18n/bs.tsv | 5 ++ crates/ui-egui/src/i18n/ca.tsv | 5 ++ crates/ui-egui/src/i18n/cs.tsv | 5 ++ crates/ui-egui/src/i18n/da.tsv | 5 ++ crates/ui-egui/src/i18n/de.tsv | 5 ++ crates/ui-egui/src/i18n/el.tsv | 5 ++ crates/ui-egui/src/i18n/es.tsv | 5 ++ crates/ui-egui/src/i18n/fi.tsv | 5 ++ crates/ui-egui/src/i18n/fr.tsv | 5 ++ crates/ui-egui/src/i18n/hu.tsv | 5 ++ crates/ui-egui/src/i18n/id.tsv | 5 ++ crates/ui-egui/src/i18n/it.tsv | 5 ++ crates/ui-egui/src/i18n/ja.tsv | 5 ++ crates/ui-egui/src/i18n/ka.tsv | 5 ++ crates/ui-egui/src/i18n/ko.tsv | 5 ++ crates/ui-egui/src/i18n/nl.tsv | 5 ++ crates/ui-egui/src/i18n/pl.tsv | 5 ++ crates/ui-egui/src/i18n/pt-br.tsv | 5 ++ crates/ui-egui/src/i18n/ru.tsv | 5 ++ crates/ui-egui/src/i18n/sv.tsv | 5 ++ crates/ui-egui/src/i18n/tests.rs | 10 ++++ crates/ui-egui/src/i18n/tr.tsv | 5 ++ crates/ui-egui/src/i18n/uk.tsv | 5 ++ crates/ui-egui/src/i18n/vi.tsv | 5 ++ crates/ui-egui/src/i18n/zh-hans.tsv | 5 ++ crates/ui-egui/src/i18n/zh-hant.tsv | 5 ++ crates/ui-egui/src/prefs_dialog/tests.rs | 18 ++++++++ docs/gaps.md | 3 ++ docs/mcp.md | 2 + docs/ui-parity.md | 3 ++ 42 files changed, 399 insertions(+), 11 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 0f9267e0e..8e9336d56 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,7 @@ # VectorCraft Roadmap +> **QoL update (2026-11-02):** Selection-tool marquees offer Touching (default), Fully Enclosed and Drag Direction in Preferences › Selection & Anchor Display. Drag Direction uses screen left/right, including rotated views, retaining its last rule within three horizontal screen pixels of the press. E reverses the rule for the current drag. A solid enclosed outline or dashed touching outline and a translated label show the active rule. The selection region remains aligned to the document axes. This is a VectorCraft addition; scores and estimates are unchanged. + - Contour add-on (beyond Illustrator): drag a selected shape's edge outward or inward for a live, editable offset copy, with a floating bar for distance and Sharp, Round or Square corners; Bake turns it into paths. Scores and estimates unchanged. - Compound Shapes add-on (beyond Illustrator): a menu for Make, Release and Expand Compound Shape and a setting, off by default, that makes the Pathfinder panel's Shape Mode buttons create compound shapes on a click. Scores and estimates unchanged. diff --git a/crates/engine/src/cmd/prefscmds.rs b/crates/engine/src/cmd/prefscmds.rs index de1b65755..0267725ef 100644 --- a/crates/engine/src/cmd/prefscmds.rs +++ b/crates/engine/src/cmd/prefscmds.rs @@ -156,6 +156,13 @@ pub const PREF_SPECS: &[PrefSpec] = &[ p!("holdToolKeys", "General", "Options", "Hold a Tool Shortcut to Use the Tool Temporarily", bool), // Selection & Anchor Display p!("selectionTolerance", "Selection & Anchor Display", "Selection", "Tolerance", num(1.0, 8.0, "px")), + p!( + "marqueeSelection", + "Selection & Anchor Display", + "Selection", + "Marquee Selection", + choice(&[("touching", "Touching"), ("enclosed", "Fully Enclosed"), ("directional", "Drag Direction")]) + ), p!("objectSelectionByPathOnly", "Selection & Anchor Display", "Selection", "Object Selection by Path Only", bool), p!("snapToPointTolerance", "Selection & Anchor Display", "Selection", "Snap to Point", num(1.0, 8.0, "px")), p!("ctrlClickSelectsBehind", "Selection & Anchor Display", "Selection", "Command Click to Select Objects Behind", bool), diff --git a/crates/engine/src/prefs.rs b/crates/engine/src/prefs.rs index 1e5147582..fc2530c03 100644 --- a/crates/engine/src/prefs.rs +++ b/crates/engine/src/prefs.rs @@ -67,6 +67,8 @@ pub struct Prefs { pub paste_offset: f64, // Selection & Anchor Display pub selection_tolerance: f64, + /// Selection-tool marquee: touching (default), enclosed, or screen drag direction. + pub marquee_selection: String, pub object_selection_by_path_only: bool, pub snap_to_point_tolerance: f64, pub ctrl_click_selects_behind: bool, @@ -378,6 +380,7 @@ impl Default for Prefs { hold_tool_keys: true, paste_offset: 10.0, selection_tolerance: 3.0, + marquee_selection: "touching".into(), object_selection_by_path_only: false, snap_to_point_tolerance: 2.0, ctrl_click_selects_behind: true, diff --git a/crates/engine/src/tests/prefs.rs b/crates/engine/src/tests/prefs.rs index 7a17fe413..52755f2cb 100644 --- a/crates/engine/src/tests/prefs.rs +++ b/crates/engine/src/tests/prefs.rs @@ -7,6 +7,32 @@ use vectorcraft_tools::{PointerEvent, PointerKind}; use crate::cmd::prefscmds::{PREF_CATEGORIES, PREF_GROUPS, PREF_SPECS, validate}; use crate::*; +/// The saved preference reaches the real tool; older preferences keep Touching and invalid +/// values fail without changing the current setting. +#[test] +fn marquee_selection_preference_drives_the_selection_tool() { + assert_eq!(serde_json::from_value::(json!({})).unwrap().marquee_selection, "touching"); + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap(); + s.execute("shape.rectangle", &json!({"x": 100, "y": 100, "width": 100, "height": 100})).unwrap(); + let v = ViewInfo::default(); + s.select_tool("selection", v).unwrap(); + for (mode, selected) in [("touching", true), ("enclosed", false), ("directional", false)] { + set_pref(&mut s, "marqueeSelection", json!(mode)); + s.execute("select.none", &json!({})).unwrap(); + for (k, x, y) in [(PointerKind::Down, 50.0, 50.0), (PointerKind::Drag, 150.0, 150.0), (PointerKind::Up, 150.0, 150.0)] { + s.pointer(&PointerEvent::new(k, x, y), v).unwrap(); + } + assert_eq!(!s.doc().unwrap().selection.objects.is_empty(), selected, "{mode}"); + let persisted = s.prefs.to_json(); + assert_eq!(serde_json::from_value::(persisted).unwrap().marquee_selection, mode); + } + assert!(s.execute("prefs.set", &json!({"key": "marqueeSelection", "value": "unknown"})).is_err()); + assert_eq!(s.prefs.marquee_selection, "directional"); + s.execute("prefs.reset", &json!({})).unwrap(); + assert_eq!(s.prefs.marquee_selection, "touching"); +} + #[test] fn every_spec_matches_a_prefs_field_and_back() { let all = Prefs::default().to_json(); diff --git a/crates/engine/src/tooling.rs b/crates/engine/src/tooling.rs index 12d595263..c1c4df67d 100644 --- a/crates/engine/src/tooling.rs +++ b/crates/engine/src/tooling.rs @@ -319,6 +319,11 @@ impl Session { slices_locked: self.menu.slices_locked, auto_add_delete: !self.prefs.disable_auto_add_delete, selection_tolerance: self.prefs.selection_tolerance, + marquee_mode: match self.prefs.marquee_selection.as_str() { + "enclosed" => vectorcraft_tools::MarqueeMode::Enclosed, + "directional" => vectorcraft_tools::MarqueeMode::Directional, + _ => vectorcraft_tools::MarqueeMode::Touching, + }, anchor_size: self.prefs.anchor_size, path_only: self.prefs.object_selection_by_path_only, type_path_only: self.prefs.type_selection_by_path_only, diff --git a/crates/tools/src/context.rs b/crates/tools/src/context.rs index 074756d84..deb305b65 100644 --- a/crates/tools/src/context.rs +++ b/crates/tools/src/context.rs @@ -1,5 +1,15 @@ use super::{Document, NodeId, Paint, Point, Selection, Unit, Vec2, distort, select, xform}; +/// Which objects a Selection-tool marquee reaches before E reverses its rule for this drag. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum MarqueeMode { + #[default] + Touching, + Enclosed, + /// Screen-right takes enclosed objects; screen-left takes touched objects. + Directional, +} + /// Paint defaults for new art (the fill/stroke proxy) and the rest of the new-art template. #[derive(Clone, Debug, PartialEq)] pub struct PaintDefaults { @@ -120,6 +130,8 @@ pub struct ToolContext<'a> { pub selection: &'a Selection, /// Screen pixels per document point. pub zoom: f64, + /// Selection-tool marquee rule; other selection tools keep their own rules. + pub marquee_mode: MarqueeMode, pub isolation: Option, pub paint: &'a PaintDefaults, pub outline: bool, diff --git a/crates/tools/src/lib.rs b/crates/tools/src/lib.rs index 2893c06ab..f16969907 100644 --- a/crates/tools/src/lib.rs +++ b/crates/tools/src/lib.rs @@ -57,7 +57,7 @@ mod context; mod cursor; mod input; -pub use context::{GlyphSnap, PaintDefaults, ScreenFrame, ToolContext}; +pub use context::{GlyphSnap, MarqueeMode, PaintDefaults, ScreenFrame, ToolContext}; pub use cursor::Cursor; pub use input::{Action, Mods, PenPose, PointerEvent, PointerKind, ToolKey}; @@ -66,6 +66,8 @@ pub use input::{Action, Mods, PenPose, PointerEvent, PointerKind, ToolKey}; pub enum Overlay { /// Marquee / rubber band rectangle. Marquee(Rect), + /// Selection-tool marquee, with its effective rule and pointer position for feedback. + SelectionMarquee { rect: Rect, enclosed: bool, pointer: Point }, /// A path preview (e.g. pen rubber band) in a colour (RGB). Path { path: BezPath, color: [u8; 3], width: f32, dashed: bool }, /// A line segment (smart guide, handle line). @@ -482,6 +484,7 @@ pub(crate) mod testutil { revision: (0, 0), selection: s, zoom: 1.0, + marquee_mode: MarqueeMode::Touching, isolation: None, paint: p, outline: false, diff --git a/crates/tools/src/select.rs b/crates/tools/src/select.rs index 6d90e79e3..5a2739139 100644 --- a/crates/tools/src/select.rs +++ b/crates/tools/src/select.rs @@ -16,8 +16,8 @@ //! The bounding box stands at the selection's own angle after a rotation, so its handles scale //! along the objects' axes. A handle drag resizes area type's frame (the type area) instead of //! scaling its type: the text reflows at its size. -//! E pressed during a marquee drag makes the marquee take only the objects entirely inside it, and -//! E pressed again makes it take the objects it touches. +//! Marquee Selection defaults to touched objects, with fully enclosed and screen drag-direction +//! alternatives. E reverses the rule for the current drag. The overlay reports the effective rule. //! Escape during a drag (a move or an Alt-drag copy, a scale, a rotation, a marquee, a widget or //! bracket drag) cancels it: the art returns to its state at the press. //! Selected area type and type on a path show an in port and an out port. A click on the out port @@ -55,6 +55,29 @@ use crate::shaperwidget; use crate::typewidget; use crate::{Action, Cursor, Mods, Overlay, PointerEvent, PointerKind, Tool, ToolContext, ToolKey, json_ids}; +/// Direction is read on screen, even in a turned view. Within three pixels of the press's +/// horizontal position, retain the previous rule so hand jitter does not flip it. +fn marquee_direction(cx: &ToolContext, start: Point, cur: Point, previous: bool) -> bool { + let dx = cx.screen.and_then(|f| Some(f.to_screen(cur)?.0 - f.to_screen(start)?.0)).unwrap_or((cur.x - start.x) * cx.zoom); + if dx > 3.0 { + true + } else if dx < -3.0 { + false + } else { + previous + } +} + +/// The same effective rule drives artwork, ruler guides and the visible feedback. +fn marquee_enclosed(cx: &ToolContext, inverted: bool, directional_inside: bool) -> bool { + let inside = match cx.marquee_mode { + crate::MarqueeMode::Touching => false, + crate::MarqueeMode::Enclosed => true, + crate::MarqueeMode::Directional => directional_inside, + }; + inside != inverted +} + #[derive(Clone, Debug, Default)] enum State { #[default] @@ -97,9 +120,10 @@ enum State { cur: Point, /// Shift: the objects inside leave the selection if selected, else join it. toggle: bool, - /// E pressed an odd number of times during the drag: the marquee takes only the objects - /// that lie entirely inside it. + /// E pressed an odd number of times during the drag: reverse the chosen rule. enclosed: bool, + /// Last horizontal direction outside the 3 screen-pixel dead zone at the press. + directional_inside: bool, }, /// Dragging a Live Corners widget. Corner(CornerDrag), @@ -952,8 +976,9 @@ impl Tool for SelectionTool { } vec![Action::Preview("object.transform".into(), params)] } - (PointerKind::Drag, State::Marquee { start, toggle, enclosed, .. }) => { - self.state = State::Marquee { start, cur: p, toggle, enclosed }; + (PointerKind::Drag, State::Marquee { start, toggle, enclosed, directional_inside, .. }) => { + self.state = + State::Marquee { start, cur: p, toggle, enclosed, directional_inside: marquee_direction(cx, start, p, directional_inside) }; vec![] } (PointerKind::Drag, State::Corner(mut c)) => { @@ -1019,13 +1044,14 @@ impl Tool for SelectionTool { out.push(Action::Commit); out } - (PointerKind::Up, State::Marquee { start, toggle, enclosed, .. }) => { + (PointerKind::Up, State::Marquee { start, toggle, enclosed, directional_inside, .. }) => { self.state = State::Idle; let r = Rect::from_points(start, p); if r.width() < Self::drag_threshold(cx) && r.height() < Self::drag_threshold(cx) { return if toggle { vec![] } else { vec![Action::Exec("select.none".into(), json!({}))] }; } let touched = marquee(cx.doc, r, cx.isolation, false); + let enclosed = marquee_enclosed(cx, enclosed, marquee_direction(cx, start, p, directional_inside)); let ids = if enclosed { inside(cx, r, touched) } else { touched }; let art = Action::Exec(if toggle { "select.toggle" } else { "select.set" }.into(), json!({ "ids": json_ids(&ids) })); std::iter::once(art).chain(crate::rulerguide::marquee_select(cx, r, enclosed)).collect() @@ -1046,7 +1072,11 @@ impl Tool for SelectionTool { } } match &self.state { - State::Marquee { start, cur, .. } => o.push(Overlay::Marquee(Rect::from_points(*start, *cur))), + State::Marquee { start, cur, enclosed, directional_inside, .. } => o.push(Overlay::SelectionMarquee { + rect: Rect::from_points(*start, *cur), + enclosed: marquee_enclosed(cx, *enclosed, *directional_inside), + pointer: *cur, + }), State::Corner(c) => o.extend(c.overlays(cx)), State::Pie(d) => o.extend(d.overlays(cx)), State::RotateAll(d) => o.extend(d.overlays(cx)), @@ -1544,7 +1574,7 @@ impl SelectionTool { self.state = if !m.shift && selected_hidden_at(cx.doc, p, cx.selection_hit_options(), &cx.selection.objects).is_some() { State::Moving { start: grab, press: p, hold: grab, began: false, deselect: None, key: None, exclude: None } } else { - State::Marquee { start: p, cur: p, toggle: m.shift, enclosed: false } + State::Marquee { start: p, cur: p, toggle: m.shift, enclosed: false, directional_inside: false } }; } diff --git a/crates/tools/src/select/tests.rs b/crates/tools/src/select/tests.rs index 242b9f928..76b1d5fbc 100644 --- a/crates/tools/src/select/tests.rs +++ b/crates/tools/src/select/tests.rs @@ -1307,7 +1307,7 @@ fn escape_cancels_a_marquee() { t.pointer(&cx, &ev(PointerKind::Drag, 120.0, 120.0)); assert_eq!(t.key(&cx, ToolKey::Escape, Mods::default()), vec![Action::Cancel], "Escape cancels the marquee"); assert!(!t.busy()); - assert!(t.overlays(&cx).iter().all(|o| !matches!(o, Overlay::Marquee(_))), "the marquee is removed"); + assert!(t.overlays(&cx).iter().all(|o| !matches!(o, Overlay::Marquee(_) | Overlay::SelectionMarquee { .. })), "the marquee is removed"); assert!(t.pointer(&cx, &ev(PointerKind::Up, 120.0, 120.0)).is_empty(), "the release selects nothing"); } @@ -1380,6 +1380,58 @@ fn e_during_a_marquee_takes_only_the_objects_inside() { assert!(t.busy() && !t.claims_key(&cx, e)); } +/// Each rule selects what its feedback promises, with E reversing it for just this gesture. +#[test] +fn marquee_rules_and_feedback_agree() { + use crate::MarqueeMode::{Directional, Enclosed, Touching}; + let (d, id) = doc_with_rect(); + let (s, paint) = (Selection::default(), paint()); + for (mode, right, expected) in + [(Touching, true, true), (Touching, false, true), (Enclosed, true, false), (Directional, true, false), (Directional, false, true)] + { + let c = ToolContext { marquee_mode: mode, ..cx(&d, &s, &paint) }; + for inverted in [false, true] { + let mut tool = SelectionTool::default(); + let (a, b) = if right { ((50.0, 50.0), (150.0, 150.0)) } else { ((150.0, 50.0), (50.0, 150.0)) }; + tool.pointer(&c, &ev(PointerKind::Down, a.0, a.1)); + tool.pointer(&c, &ev(PointerKind::Drag, b.0, b.1)); + if inverted { + tool.key(&c, ToolKey::Letter('e'), Mods::default()); + } + let selected = expected != inverted; + assert!( + tool.overlays(&c).iter().any(|o| matches!(o, Overlay::SelectionMarquee { enclosed, .. } if *enclosed != selected)), + "{mode:?} right={right} E={inverted}" + ); + let out = tool.pointer(&c, &ev(PointerKind::Up, b.0, b.1)); + assert_eq!(out, vec![Action::Exec("select.set".into(), json!({"ids": if selected { vec![id.0] } else { vec![] }}))]); + assert!(!tool.overlays(&c).iter().any(|o| matches!(o, Overlay::SelectionMarquee { .. }))); + } + } +} + +/// Screen direction survives view rotation; near the press's x position, the last mode is kept. +#[test] +fn directional_marquee_uses_screen_direction_and_a_dead_zone() { + let (d, _) = doc_with_rect(); + let (s, paint) = (Selection::default(), paint()); + for angle in [0.0, 45.0, 90.0, 180.0, 270.0] { + for zoom in [0.25, 1.0, 4.0] { + let mut screen = turned_view(angle).unwrap(); + screen.right /= zoom; + screen.down /= zoom; + let c = ToolContext { marquee_mode: crate::MarqueeMode::Directional, screen: Some(screen), zoom, ..cx(&d, &s, &paint) }; + let start = screen.at(20.0, 20.0); + assert!(marquee_direction(&c, start, screen.at(24.0, 60.0), false)); + assert!(!marquee_direction(&c, start, screen.at(16.0, 60.0), true)); + for x in [18.0, 20.0, 22.0] { + assert!(marquee_direction(&c, start, screen.at(x, 60.0), true)); + assert!(!marquee_direction(&c, start, screen.at(x, 60.0), false)); + } + } + } +} + /// With E pressed during the drag, a marquee takes only the artboard guides that lie entirely /// inside it, and no canvas guide, which runs across the whole canvas. #[test] diff --git a/crates/ui-egui/src/canvas/overlays.rs b/crates/ui-egui/src/canvas/overlays.rs index 8779380ec..a59c0c837 100644 --- a/crates/ui-egui/src/canvas/overlays.rs +++ b/crates/ui-egui/src/canvas/overlays.rs @@ -275,6 +275,29 @@ pub(super) fn draw_overlays(p: &egui::Painter, xf: &Xf, overlays: &[Overlay], t: match o { // Where the tools select: the document rectangle, turned with the view. Overlay::Marquee(r) => marquee_through(p, &xf.quad(*r)), + Overlay::SelectionMarquee { rect, enclosed, pointer } => { + let quad = xf.quad(*rect); + if *enclosed { + let points: Vec = quad.iter().chain(quad.first()).copied().collect(); + p.add(Shape::line(points.clone(), Stroke::new(3.0, Color32::from_white_alpha(230)))); + p.add(Shape::line(points, Stroke::new(1.0, Color32::from_gray(40)))); + } else { + marquee_through(p, &quad); + } + let (current, other) = if *enclosed { (tl!("Fully Enclosed"), tl!("Touching")) } else { (tl!("Touching"), tl!("Fully Enclosed")) }; + let label = format!("{current} · E: {other}"); + let galley = p.layout(label, egui::FontId::proportional(11.0), t.text, (p.clip_rect().width() - 12.0).max(1.0)); + let size = galley.size() + vec2(12.0, 8.0); + let wanted = xf.to_screen(*pointer) + vec2(14.0, 14.0); + let clip = p.clip_rect(); + let min = egui::pos2( + wanted.x.clamp(clip.min.x, (clip.max.x - size.x).max(clip.min.x)), + wanted.y.clamp(clip.min.y, (clip.max.y - size.y).max(clip.min.y)), + ); + let badge = egui::Rect::from_min_size(min, size); + p.rect_filled(badge, CornerRadius::same(3), t.panel); + p.galley(badge.min + vec2(6.0, 4.0), galley, t.text); + } Overlay::Path { path, color, width, dashed } => { let s = Stroke::new(*width, c32(*color)); if *dashed { diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index c37d6b868..2c4ef5baa 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -2,6 +2,65 @@ use super::*; use vectorcraft_engine::Session; use vectorcraft_geom::Shape as _; +/// The marquee explains E without depending on color. At the canvas edge the hint stays in +/// view, in every brightness, and the two rules have visibly different outlines. +#[test] +fn marquee_feedback_explains_the_rule_and_stays_on_canvas() { + let draw = |ui: &mut egui::Ui| { + let t = Tokens::get(ui.ctx()); + let xf = Xf { rect: ui.max_rect(), zoom: 1.0, center: Point::new(160.0, 100.0), rot: 0.0 }; + let prefs = vectorcraft_engine::Prefs::default(); + for (enclosed, y) in [(false, 80.0), (true, 180.0)] { + overlays::draw_overlays( + ui.painter(), + &xf, + &[Overlay::SelectionMarquee { rect: Rect::new(20.0, y - 60.0, 300.0, y), enclosed, pointer: Point::new(315.0, y) }], + &t, + overlays::HandleLook::of(&prefs), + ); + } + }; + for brightness in crate::theme::Brightness::ALL { + for width in [160.0, 320.0] { + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + crate::theme::apply(&ctx, brightness); + let raw = egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(Pos2::ZERO, vec2(width, 200.0))), ..Default::default() }; + let mut out = ctx.run_ui(raw, |ui| draw(ui)); + out.textures_delta.clear(); + let text = crate::tests_labels::shapes_text(&out); + assert!(text.contains("Fully Enclosed · E: Touching") && text.contains("Touching · E: Fully Enclosed"), "{text}"); + for shape in &out.shapes { + if let egui::Shape::Text(label) = &shape.shape { + assert!( + label.pos.x >= shape.clip_rect.min.x && label.pos.x + label.galley.size().x <= shape.clip_rect.max.x, + "{brightness:?}: hint clipped" + ); + assert!( + label.pos.y >= shape.clip_rect.min.y && label.pos.y + label.galley.size().y <= shape.clip_rect.max.y, + "{brightness:?}: hint clipped vertically" + ); + } + } + } + } + // Optional visual QA of our own UI. Captures never enter the repository. + if let Some(path) = std::env::var_os("VECTORCRAFT_MARQUEE_CAPTURE") { + let mut ready = false; + let mut harness = egui_kittest::Harness::builder().with_size(vec2(320.0, 200.0)).wgpu().build_ui(move |ui| { + if !ready { + crate::theme::install_fonts(ui.ctx()); + crate::theme::apply(ui.ctx(), crate::theme::Brightness::MediumDark); + ready = true; + return; + } + draw(ui); + }); + harness.run_steps(4); + harness.render().unwrap().save(path).unwrap(); + } +} + #[test] fn subpixel_dock_layout_does_not_change_pointer_document_coordinates() { let mut observed = Vec::new(); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 3ae7712f6..e4d558ca1 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2980,6 +2980,11 @@ digits Malayalam Малаялам Selected Swatches Избрани образци Selected Swatches Only Само избрани образци Selection Селекция +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Избор с рамка + Touching Докоснати обекти + Fully Enclosed Изцяло вътре + Drag Direction Посока на плъзгане Selection & Anchor Display Показване на селекция и опорни точки Selection Only Само селекция Selection Tool Инструмент „Селекция“ diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index aba170ea4..a4a185058 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -179,6 +179,11 @@ Ungroup Razgrupiši Lock Zaključaj Selection Odabir +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Pravougaoni odabir + Touching Dodirnuti objekti + Fully Enclosed Potpuno obuhvaćeni + Drag Direction Smjer povlačenja All Artwork Above Sav crtež iznad Other Layers Ostali slojevi Unlock All Otključaj sve diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 370d9b677..12457d313 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -3798,6 +3798,11 @@ digits Malayalam Malaiàlam Selected Swatches Only (0) Només les mostres seleccionades (0) Selected Symbols Only Només els símbols seleccionats Selection Selecció +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Selecció rectangular + Touching Objectes tocats + Fully Enclosed Totalment inclosos + Drag Direction Direcció de l’arrossegament Selection & Anchor Display Selecció i visualització d’ancoratges Selection Only Només la selecció Selection Tool Eina de selecció diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 5fa8b5bd0..e20f83c2b 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -187,6 +187,11 @@ Ungroup Oddělit Lock Zamknout Selection Výběr +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Obdélníkový výběr + Touching Dotčené objekty + Fully Enclosed Zcela uvnitř + Drag Direction Směr tažení All Artwork Above Veškerou kresbu výše Other Layers Ostatní vrstvy Unlock All Odemknout vše diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 382df11e6..114c3fbdb 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -172,6 +172,11 @@ Ungroup Afgruppér Lock Lås Selection Markering +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Rektangelmarkering + Touching Berørte objekter + Fully Enclosed Helt omsluttet + Drag Direction Trækretning All Artwork Above Al grafik ovenfor Other Layers Andre lag Unlock All Lås alle op diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index dfa4a3a33..64dcb319a 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -3022,6 +3022,11 @@ digits Malayalam Malayalam Selected Swatches Ausgewählte Farbfelder Selected Swatches Only Nur ausgewählte Farbfelder Selection Auswahl +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Rechteckauswahl + Touching Berührt + Fully Enclosed Vollständig eingeschlossen + Drag Direction Ziehrichtung Selection & Anchor Display Auswahl und Ankerpunktanzeige Selection Only Nur Auswahl Selection Tool Auswahl-Werkzeug diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 2aa0a151f..68d70e8f1 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1935,6 +1935,11 @@ font-technology Variable Μεταβλητές Select All Επιλογή όλων Select: Επιλογή: Selection Επιλογή +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Ορθογώνια επιλογή + Touching Αντικείμενα που τέμνονται + Fully Enclosed Πλήρως εντός + Drag Direction Κατεύθυνση σύρσης Selection Tool Επιλογή (Selection Tool) Send Backward Μεταφορά ένα βήμα πίσω Send to Back Μεταφορά στο παρασκήνιο diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 1db3e9aab..84dda1ce8 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -3062,6 +3062,11 @@ digits Malayalam Malayálam Selected Swatches Muestras seleccionadas Selected Swatches Only Solo muestras seleccionadas Selection Selección +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Selección rectangular + Touching Objetos tocados + Fully Enclosed Totalmente incluidos + Drag Direction Dirección de arrastre Selection & Anchor Display Visualización de selección y anclas Selection Only Solo la selección Selection Tool Herramienta Selección diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 0752a3fbb..b781ab310 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -194,6 +194,11 @@ Ungroup Pura ryhmitys Lock Lukitse Selection Valinta +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Suorakulmiovalinta + Touching Kosketetut + Fully Enclosed Kokonaan sisällä + Drag Direction Vetosuunta All Artwork Above Kaikki yllä oleva kuvitus Other Layers Muut tasot Unlock All Avaa kaikki lukitukset diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 755beef3e..3e2eb9c01 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -3054,6 +3054,11 @@ digits Malayalam Malayalam Selected Swatches Nuances sélectionnées Selected Swatches Only Nuances sélectionnées uniquement Selection Sélection +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Sélection rectangulaire + Touching Objets touchés + Fully Enclosed Entièrement inclus + Drag Direction Sens du glissement Selection & Anchor Display Affichage de la sélection et des points d’ancrage Selection Only Sélection uniquement Selection Tool Outil Sélection diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index eae9829b9..36c7a5a6f 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -3667,6 +3667,11 @@ mark-style Japanese Japán Selected Swatches Only Csak a kijelölt színminták Selected Swatches Only (0) Csak a kijelölt színminták (0) Selection Kijelölés +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Téglalapos kijelölés + Touching Érintett objektumok + Fully Enclosed Teljesen befoglalt + Drag Direction Húzás iránya Selection & Anchor Display Kijelölés és horgonypontok megjelenítése Selection Only Csak a kijelölés Selection Tool Kijelölés diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 433380d53..e32b28107 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -172,6 +172,11 @@ Ungroup Buka grup Lock Kunci Selection Seleksi +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Seleksi persegi panjang + Touching Objek tersentuh + Fully Enclosed Sepenuhnya di dalam + Drag Direction Arah seret All Artwork Above Semua karya seni di atas Other Layers Lapisan lain Unlock All Buka kunci semua diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 46c0bc7e5..4e0b5f341 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -3098,6 +3098,11 @@ digits Malayalam Malayalam Selected Swatches Campioni selezionati Selected Swatches Only Solo campioni selezionati Selection Selezione +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Selezione rettangolare + Touching Oggetti toccati + Fully Enclosed Interamente inclusi + Drag Direction Direzione di trascinamento Selection & Anchor Display Visualizzazione selezione e punti di ancoraggio Selection Only Solo selezione Selection Tool Strumento Selezione diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 6df9879ea..dc845fa7a 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -213,6 +213,11 @@ digits Malayalam マラヤーラム数字 Ungroup グループ解除 Lock ロック Selection 選択 +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection 矩形選択 + Touching 接触するオブジェクト + Fully Enclosed 完全に囲まれたオブジェクト + Drag Direction ドラッグ方向 All Artwork Above 前面のすべてのアートワーク Other Layers その他のレイヤー Unlock All すべてをロック解除 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index b23ea6b0c..5e3262f82 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -146,6 +146,11 @@ Ungroup დაჯგუფების მოხსნა Lock ჩაკეტვა Selection მონიშნული +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection მართკუთხა მონიშვნა + Touching შეხებული ობიექტები + Fully Enclosed სრულად მოქცეული + Drag Direction გადათრევის მიმართულება All Artwork Above ზემოთ მდებარე მთელი ნახატი Other Layers სხვა შრეები Unlock All ყველაფრის განბლოკვა diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 4a026cd0d..8f9e3e286 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -206,6 +206,11 @@ digits Malayalam 말라얄람어 Ungroup 그룹 해제 Lock 잠그기 Selection 선택 영역 +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection 사각형 선택 + Touching 닿는 개체 + Fully Enclosed 완전히 포함된 개체 + Drag Direction 드래그 방향 All Artwork Above 위의 모든 아트워크 Other Layers 다른 레이어 Unlock All 모두 잠금 해제 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 3a0ffaf16..8dccad87c 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -186,6 +186,11 @@ Ungroup Groepering opheffen Lock Vergrendelen Selection Selectie +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Rechthoekselectie + Touching Geraakt + Fully Enclosed Volledig omsloten + Drag Direction Sleeprichting All Artwork Above Alle artwork erboven Other Layers Andere lagen Unlock All Alles ontgrendelen diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index d858f8081..c5513ad3e 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -152,6 +152,11 @@ Ungroup Rozgrupuj Lock Zablokuj Selection Zaznaczenie +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Zaznaczanie prostokątne + Touching Dotknięte + Fully Enclosed Całkowicie wewnątrz + Drag Direction Kierunek przeciągania All Artwork Above Całą kompozycję powyżej Other Layers Pozostałe warstwy Unlock All Odblokuj wszystko diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 49a4edc7c..f49c6aac2 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2564,6 +2564,11 @@ digits Malayalam Malaiala Selected Swatches Amostras Selecionadas Selected Swatches Only Somente Amostras Selecionadas Selection Seleção +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Seleção retangular + Touching Objetos tocados + Fully Enclosed Totalmente contidos + Drag Direction Direção do arrasto Selection & Anchor Display Exibição de Seleção e Âncora Selection Only Somente Seleção Selection Tool Ferramenta Seleção diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index e37f1db9d..85cf2e0e6 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -3093,6 +3093,11 @@ digits Malayalam Малаялам Selected Swatches Выбранные образцы Selected Swatches Only Только выбранные образцы Selection Выделение +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Выделение рамкой + Touching Пересекаемые + Fully Enclosed Полностью внутри + Drag Direction Направление перетаскивания Selection & Anchor Display Отображение выделения и опорных точек Selection Only Только выделение Selection Tool Инструмент «Выделение» diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index f0cb484b2..d19c210b9 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -2986,6 +2986,11 @@ digits Malayalam Malayalam Selected Swatches Markerade färgrutor Selected Swatches Only Endast markerade färgrutor Selection Markering +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Rektangelmarkering + Touching Berörda objekt + Fully Enclosed Helt omslutna + Drag Direction Dragriktning Selection & Anchor Display Visning av markering och ankarpunkter Selection Only Endast markering Selection Tool Markeringsverktyg diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index ed95d4e8e..1ccbe1484 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -184,6 +184,16 @@ fn font_height_reference_labels_are_in_complete_catalogs() { } } +/// Preference choices and the marquee's E hint share the same translated labels. +#[test] +fn marquee_selection_labels_are_in_complete_catalogs() { + for lang in LANGUAGES.iter().filter(|lang| lang.complete_menus) { + for label in ["Marquee Selection", "Touching", "Fully Enclosed", "Drag Direction"] { + assert!(lang.catalog().plain(label).is_some(), "{} is missing {label}", lang.code); + } + } +} + /// Each language counts with its own plural rule: the form a count picks (`one`, then `few`, then /// `other`/`many`). #[test] diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 73ba123e0..db8509636 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -2890,6 +2890,11 @@ digits Malayalam Malayalam Selected Swatches Seçilen Renk Örnekleri Selected Swatches Only Yalnızca Seçilen Renk Örnekleri Selection Seçim +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Dikdörtgen seçim + Touching Dokunulanlar + Fully Enclosed Tamamen içeride + Drag Direction Sürükleme yönü Selection & Anchor Display Seçim ve Bağlantı Ekranı Selection Only Yalnızca Seçim Selection Tool Seçim Aracı diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index fe0edb479..12fafaa8d 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -3090,6 +3090,11 @@ digits Malayalam Малаялам Selected Swatches Вибрані зразки Selected Swatches Only Тільки вибрані зразки Selection Виділення +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Виділення рамкою + Touching Перетнуті + Fully Enclosed Повністю всередині + Drag Direction Напрямок перетягування Selection & Anchor Display Відображення виділення та опорних точок Selection Only Тільки виділення Selection Tool Інструмент «Виділення» diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index a567d484a..259881305 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -3418,6 +3418,11 @@ mark-style Japanese Kiểu Nhật Selected Swatches Mẫu màu đã chọn Selected Swatches Only Chỉ mẫu màu đã chọn Selection Vùng chọn +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection Chọn bằng khung + Touching Đối tượng chạm khung + Fully Enclosed Nằm hoàn toàn trong khung + Drag Direction Hướng kéo Selection & Anchor Display Hiển thị vùng chọn & điểm neo Selection Only Chỉ vùng chọn Selection Tool Công cụ Chọn diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index be6b5fd2b..22c78c5ac 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2934,6 +2934,11 @@ digits Malayalam 马拉雅拉姆数字 Selected Swatches 所选色板 Selected Swatches Only 仅限选择的色板 Selection 所选对象 +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection 框选 + Touching 接触对象 + Fully Enclosed 完全包含 + Drag Direction 拖动方向 Selection & Anchor Display 选择和锚点显示 Selection Only 仅所选内容 Selection Tool 选择工具 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index fd5d3df2b..34506cb1e 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -3964,6 +3964,11 @@ digits Malayalam 馬來亞拉姆數字 Selected Swatches 選取的色票 Selected Swatches Only 僅限選取的色票 Selection 選取範圍 +# Marquee selection modes (draft translations; native review welcome). + Marquee Selection 框選 + Touching 接觸物件 + Fully Enclosed 完全包含 + Drag Direction 拖曳方向 Selection & Anchor Display 選取和錨點顯示 Selection Only 僅限選取範圍 Selection Tool 選取工具 diff --git a/crates/ui-egui/src/prefs_dialog/tests.rs b/crates/ui-egui/src/prefs_dialog/tests.rs index da1814b35..8e0db81d9 100644 --- a/crates/ui-egui/src/prefs_dialog/tests.rs +++ b/crates/ui-egui/src/prefs_dialog/tests.rs @@ -2,6 +2,24 @@ use super::*; use crate::Services; use vectorcraft_engine::Session; +/// The three marquee choices are available in Preferences and saved through the same path as +/// other selection preferences. +#[test] +fn marquee_selection_choices_are_saved_from_preferences() { + let mut app = app(); + open(&mut app, Some("Selection & Anchor Display")); + let text = crate::tests_labels::painted_text(&mut app, |a, ui| show(a, ui.ctx())); + assert!(text.contains("Marquee Selection") && text.contains("Touching"), "{text}"); + app.ui.dialog.as_mut().unwrap().fields.insert("marqueeSelection".into(), json!("directional")); + confirm(&mut app).unwrap(); + assert_eq!(app.session.prefs.marquee_selection, "directional"); + let saved: Prefs = serde_json::from_value(app.ui.engine_prefs.clone()).unwrap(); + assert_eq!(saved.marquee_selection, "directional"); + open(&mut app, Some("Selection & Anchor Display")); + let text = crate::tests_labels::painted_text(&mut app, |a, ui| show(a, ui.ctx())); + assert!(text.contains("Drag Direction"), "{text}"); +} + #[test] fn geometric_snapping_is_a_saved_smart_guides_preference() { let mut a = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); diff --git a/docs/gaps.md b/docs/gaps.md index dc6971cf5..40baea54e 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,5 +1,8 @@ # Gaps +> **QoL update (2026-11-02):** Selection-tool marquees offer Touching (default), Fully Enclosed and Drag Direction in Preferences › Selection & Anchor Display. Drag Direction uses screen left/right, including rotated views, retaining its last rule within three horizontal screen pixels of the press. E reverses the rule for the current drag. A solid enclosed outline or dashed touching outline and a translated label show the active rule. The selection region remains aligned to the document axes. This is a VectorCraft addition; scores and estimates are unchanged. + +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (G5: dynamic symbols, #4938; estimate 15–26 → 14–24 h) · **Target:** Adobe Illustrator 2026 (30.x; G12: Units › East Asian Type acts, with Q and Ha units; estimate unchanged) · **Target:** Adobe Illustrator 2026 (30.x) > **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-07 · **Change:** minor (Indian-language hyphenation review and license allowlist, #4947; opening a slow file shows a progress window, part of #5930; Hindi catalog and contributor loanword preferences; agent wording pass complete; wider native proofreading welcome; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window; scores and estimates unchanged; large images in CMYK documents are shrunk before they are separated into ink planes (#4155); scores and estimates unchanged; `.ai` paths with a Calligraphic brush open brushed, with the 6D Art Pen's channels, and Windows reads the pen's tilt and barrel rotation, #372; scores and estimates unchanged; Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence diff --git a/docs/mcp.md b/docs/mcp.md index c83b2a2ff..4908480fa 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1,5 +1,7 @@ # VectorCraft MCP server +Selection-tool marquees: `prefs.set {key: "marqueeSelection", value: "touching" | "enclosed" | "directional"}` chooses touched objects (default), fully enclosed objects, or screen-right enclosed / screen-left touched objects. Direction follows the screen when the view is rotated; within three horizontal screen pixels of the press, the last rule remains (initially touching). Without a screen frame, direction uses document x at the current zoom. E reverses the rule for the current drag; Shift toggles the reached objects and Escape cancels. The outline and label show the effective rule. Ruler guides use that rule too; Direct Selection and the document-aligned selection region are unchanged. + `vectorcraft-cli mcp` runs a [Model Context Protocol](https://modelcontextprotocol.io) server on stdio (newline-delimited JSON-RPC 2.0; see [Protocol](#protocol) for version negotiation). Agents use it to draw, inspect and look at VectorCraft documents. diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 4efbee4aa..7a79a7457 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,5 +1,8 @@ # UI and interaction parity +> **QoL update (2026-11-02):** Selection-tool marquees offer Touching (default), Fully Enclosed and Drag Direction in Preferences › Selection & Anchor Display. Drag Direction uses screen left/right, including rotated views, retaining its last rule within three horizontal screen pixels of the press. E reverses the rule for the current drag. A solid enclosed outline or dashed touching outline and a translated label show the active rule. The selection region remains aligned to the document axes. This is a VectorCraft addition; scores and estimates are unchanged. + +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (Color Effects add-on: Find Nearest Swatch, #4923) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x) > **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; shape tools: a press on the selection's centre point moves it and one in the rotate zone outside a box corner turns it, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) ## Feature submission evidence