Selection: show marquee rules and offer enclosed and directional modes

This commit is contained in:
dotCooCoo
2026-11-08 23:51:12 +07:00
committed by bflatastic
parent 368f1c1f83
commit 6d87514055
42 changed files with 399 additions and 11 deletions
+2
View File
@@ -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.
+7
View File
@@ -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),
+3
View File
@@ -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,
+26
View File
@@ -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::<Prefs>(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::<Prefs>(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();
+5
View File
@@ -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,
+12
View File
@@ -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<NodeId>,
pub paint: &'a PaintDefaults,
pub outline: bool,
+4 -1
View File
@@ -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,
+39 -9
View File
@@ -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 }
};
}
+53 -1
View File
@@ -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]
+23
View File
@@ -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<Pos2> = 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 {
+59
View File
@@ -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();
+5
View File
@@ -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 Инструмент „Селекция“
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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ó
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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 Μεταφορά στο παρασκήνιο
Can't render this file because it contains an unexpected character in line 168 and column 5.
+5
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
172
173
174 # Marquee selection modes (draft translations; native review welcome).
175
176
177
178
179
180
181
182
+5
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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 すべてをロック解除
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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 ყველაფრის განბლოკვა
Can't render this file because it is too large.
+5
View File
@@ -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 모두 잠금 해제
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+5
View File
@@ -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 Инструмент «Выделение»
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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
Can't render this file because it contains an unexpected character in line 5 and column 25.
+10
View File
@@ -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]
+5
View File
@@ -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ı
Can't render this file because it contains an unexpected character in line 5 and column 25.
+5
View File
@@ -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 Інструмент «Виділення»
Can't render this file because it contains an unexpected character in line 182 and column 5.
+5
View File
@@ -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
Can't render this file because it contains an unexpected character in line 204 and column 5.
+5
View File
@@ -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 选择工具
Can't render this file because it contains an unexpected character in line 2 and column 73.
+5
View File
@@ -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 選取工具
Can't render this file because it contains an unexpected character in line 6 and column 47.
+18
View File
@@ -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());
+3
View File
@@ -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
+2
View File
@@ -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.
+3
View File
@@ -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