diff --git a/crates/engine/src/cmd/objects_on_path.rs b/crates/engine/src/cmd/objects_on_path.rs index 296cef4dc2..7dc8aec1c9 100644 --- a/crates/engine/src/cmd/objects_on_path.rs +++ b/crates/engine/src/cmd/objects_on_path.rs @@ -12,7 +12,8 @@ //! `object.objectsOnPath.selectMember` chooses one object of a selected group (its Select/Move //! widget), and `object.objectsOnPath.moveMember` moves one object to another's place along the //! path, the objects between it and that place each moving one place toward where it was; hidden -//! and locked objects keep their places. +//! and locked objects keep their places. `object.objectsOnPath.detach` takes one object out of its +//! group, just above it, and the others move up the path. //! //! After every edit, [`refresh`] puts the objects of each Objects on Path group that changed back //! on its path: editing the path moves them, an object moved on its own goes back to its place, @@ -21,7 +22,8 @@ //! //! Behavior: (the tool and //! Object › Objects on Path › Attach; the order on the path, the attachment point, the distribution, -//! the objects squared to the path; Pivot and Rotate; path edits realign the objects; Expand). +//! the objects squared to the path; Pivot and Rotate; path edits realign the objects; Detach and +//! the objects that move up the path after it; Expand). use std::collections::{HashMap, HashSet}; use std::sync::Arc; @@ -75,6 +77,15 @@ pub fn specs() -> Vec { has_doc, move_member ), + cmd!( + "object.objectsOnPath.detach", + "Detach Object from Path", + [], + None, + "{member?: id of an object of an Objects on Path group (default: the object chosen with the selected group's Select/Move widget); neither the object nor the group may be hidden or locked} take one object off the path, out of its group and just above the group in the Layers panel, where it stays as drawn and is selected alone. The group's other objects move up the path in their order: with Auto Space they spread evenly again, and a Space value and the start are kept. The group and its path stay, even with one object or none left → {group, member}", + has_doc, + detach + ), cmd!( "object.objectsOnPath.expand", "Expand", @@ -383,6 +394,47 @@ fn move_member(s: &mut Session, p: &Value) -> Result { Ok(json!({ "group": g.0, "member": member.0, "index": index })) } +// ---------- Detach ---------- + +/// The object chosen with the Select/Move widget of the one selected Objects on Path group. +fn chosen_member(s: &Session) -> Option { + let st = s.active()?; + match st.selection.objects[..] { + [_] => st.selection.on_path_member, + _ => None, + } +} + +fn detach(s: &mut Session, p: &Value) -> Result { + const C: &str = "object.objectsOnPath.detach"; + const CHOOSE: &str = "choose the object to detach with its Select/Move widget"; + let (g, member) = if p.get("member").is_none_or(Value::is_null) { + let chosen = chosen_member(s).ok_or_else(|| bad(C, CHOOSE))?; + let found = member_param(s, &json!({ "member": chosen.0 }), "member", C)?; + if s.active().is_none_or(|st| st.selection.objects[..] != [found.0]) { + return Err(bad(C, CHOOSE)); + } + found + } else { + member_param(s, p, "member", C)? + }; + // The object goes just above the group, in the group's parent, and keeps where it is drawn. + // When the group is itself an object on another group's path, the object goes above that + // group instead (and so on outward), so it does not join that path. The refresh after the edit + // removes it from the group's record and moves the others up the path. + s.edit("Detach Object from Path", |d, sel| { + let mut above = g; + while let Some(p) = d.parent_of(above).filter(|p| d.node(*p).is_some_and(is_objects_on_path)) { + above = p; + } + let (parent, index, _) = d.position(above).ok_or(EngineError::NoNode(above))?; + d.move_node(member, parent, index + 1)?; + sel.set([member]); + Ok(()) + })?; + Ok(json!({ "group": g.0, "member": member.0 })) +} + // ---------- Expand ---------- fn expand(s: &mut Session, p: &Value) -> Result { diff --git a/crates/engine/src/cmd/plugin_call/reviewed.rs b/crates/engine/src/cmd/plugin_call/reviewed.rs index 5958a84b53..6223f8f218 100644 --- a/crates/engine/src/cmd/plugin_call/reviewed.rs +++ b/crates/engine/src/cmd/plugin_call/reviewed.rs @@ -314,6 +314,7 @@ pub(super) const ALLOWED: &[&str] = &[ "object.move", "object.nudge", "object.objectsOnPath.attach", + "object.objectsOnPath.detach", "object.objectsOnPath.expand", "object.objectsOnPath.moveMember", "object.objectsOnPath.options", diff --git a/crates/engine/src/tests/objects_on_path.rs b/crates/engine/src/tests/objects_on_path.rs index 4ae8321167..d9e76f85bf 100644 --- a/crates/engine/src/tests/objects_on_path.rs +++ b/crates/engine/src/tests/objects_on_path.rs @@ -681,3 +681,197 @@ fn the_space_saves_and_reopens_and_goes_with_a_copy() { let copy = s.doc().unwrap().selection.objects.to_vec(); assert_eq!(space(&s, copy[0]), (Some(40.0), 25.0)); } + +/// Where `id` is in the tree: its parent and its index there (0 at the bottom). +fn place_of(s: &Session, id: NodeId) -> (Option, usize) { + let (parent, index, _) = s.doc().unwrap().doc.position(id).unwrap(); + (parent, index) +} + +#[test] +fn detach_takes_the_chosen_object_off_the_path_and_the_others_move_up_in_one_undo_step() { + let mut s = session(); + let ([left, middle, right], path) = squares_and_line(&mut s); + let g = attach(&mut s, path, &[left, middle, right], json!({"rotate": 30})); + let (layer, at) = place_of(&s, g); + s.execute("object.objectsOnPath.selectMember", &json!({"member": middle.0})).unwrap(); + let drawn = s.doc().unwrap().doc.node(middle).unwrap().clone(); + let steps = s.doc().unwrap().history.undo.len(); + let v = s.execute("object.objectsOnPath.detach", &json!({})).unwrap(); + assert_eq!(v, json!({"group": g.0, "member": middle.0})); + assert_eq!(s.doc().unwrap().history.undo.len(), steps + 1, "one undo step"); + let (on_path, children) = orders(&s, g); + assert_eq!(on_path, [left, right]); + assert!(children.len() == 3 && !children.contains(&middle), "{children:?}"); + assert_eq!(place_of(&s, middle), (layer, at + 1), "just above the group, in its layer"); + assert_eq!(s.doc().unwrap().doc.node(middle), Some(&drawn), "the object keeps its art, place and rotation"); + let sel = &s.doc().unwrap().selection; + assert_eq!((sel.objects.as_slice(), sel.on_path_member), (&[middle][..], None), "the detached object alone is selected"); + // The two left stay on the ends of the path; the path stays. + assert_eq!(center(&s, left), Point::new(50.0, 300.0)); + assert_eq!(center(&s, right), Point::new(350.0, 300.0)); + assert_eq!(s.doc().unwrap().doc.node(g).unwrap().extras().objects_on_path.as_deref().unwrap().path, path); + // The detached object is free: moving it moves it, and the group does not take it back. + s.execute("object.move", &json!({"ids": [middle.0], "dx": 0, "dy": -100})).unwrap(); + assert_eq!(center(&s, middle), Point::new(200.0, 200.0)); + assert_eq!(orders(&s, g).0, [left, right]); + s.execute("edit.undo", &json!({})).unwrap(); + s.execute("edit.undo", &json!({})).unwrap(); + let (on_path, children) = orders(&s, g); + assert_eq!(on_path, [left, middle, right], "undo puts it back on the path"); + assert!(children.contains(&middle), "{children:?}"); + assert_eq!(center(&s, middle), Point::new(200.0, 300.0)); + assert_eq!(center(&s, right), Point::new(350.0, 300.0)); + let sel = &s.doc().unwrap().selection; + assert_eq!((sel.objects.as_slice(), sel.on_path_member), (&[g][..], Some(middle)), "and the choice back"); + s.execute("edit.redo", &json!({})).unwrap(); + assert_eq!(orders(&s, g).0, [left, right]); + assert_eq!(place_of(&s, middle), (layer, at + 1)); +} + +#[test] +fn after_the_first_object_is_detached_the_second_takes_the_start() { + let mut s = session(); + let ([left, middle, right], path) = squares_and_line(&mut s); + let g = attach(&mut s, path, &[left, middle, right], json!({})); + select(&mut s, &[]); + let v = s.execute("object.objectsOnPath.detach", &json!({"member": left.0})).unwrap(); + assert_eq!(v, json!({"group": g.0, "member": left.0})); + assert_eq!(orders(&s, g).0, [middle, right]); + assert_eq!(center(&s, middle), Point::new(50.0, 300.0), "the second object moves up to the start"); + assert_eq!(center(&s, right), Point::new(350.0, 300.0)); + assert_eq!(center(&s, left), Point::new(50.0, 300.0), "the detached object stays where it was drawn"); + assert_eq!(s.doc().unwrap().selection.objects.to_vec(), [left]); +} + +#[test] +fn detach_on_a_closed_path_spreads_the_others_all_the_way_round_again() { + let mut s = session(); + let ids: Vec = (0..4).map(|i| rect(&mut s, 10.0 + 40.0 * f64::from(i), 0.0, 10.0, 10.0)).collect(); + let ring = rect(&mut s, 100.0, 100.0, 200.0, 200.0); + let g = attach(&mut s, ring, &ids, json!({})); + s.execute("object.objectsOnPath.detach", &json!({"member": ids[1].0})).unwrap(); + assert_eq!(orders(&s, g).0, [ids[0], ids[2], ids[3]]); + let data = s.doc().unwrap().doc.node(ring).unwrap().path_data().unwrap().clone(); + let stops = vectorcraft_doc::objects_on_path::stops(&data, 3, None, 0.0).unwrap(); + for (id, (p, _)) in [ids[0], ids[2], ids[3]].iter().zip(stops) { + let c = center(&s, *id); + assert!((c.x - p.x).abs() < 1e-3 && (c.y - p.y).abs() < 1e-3, "a third of the way round apart: {c:?} {p:?}"); + } +} + +#[test] +fn detach_keeps_a_space_value_and_the_start() { + let mut s = session(); + let (g, [a, b, c], _) = three_on_a_line(&mut s); + s.execute("object.objectsOnPath.options", &json!({"start": 20})).unwrap(); + s.execute("object.objectsOnPath.detach", &json!({"member": a.0})).unwrap(); + assert_eq!(space(&s, g), (Some(100.0), 20.0)); + assert_eq!(center(&s, b), Point::new(70.0, 300.0), "the second object moves up to the first place"); + assert_eq!(center(&s, c), Point::new(170.0, 300.0)); + // A Space value the path is too short for stays, and two objects get more of the path. + s.execute("edit.undo", &json!({})).unwrap(); + s.execute("object.objectsOnPath.options", &json!({"spacing": 200, "start": 0})).unwrap(); + assert_eq!(center(&s, c), Point::new(350.0, 300.0), "three objects drawn 150 pt apart"); + s.execute("object.objectsOnPath.detach", &json!({"member": b.0})).unwrap(); + assert_eq!(space(&s, g), (Some(200.0), 0.0)); + assert_eq!(center(&s, a), Point::new(50.0, 300.0)); + assert_eq!(center(&s, c), Point::new(250.0, 300.0), "two objects 200 pt apart"); +} + +#[test] +fn the_group_and_its_path_stay_with_one_object_or_none_left() { + let mut s = session(); + let ([left, middle, _], path) = squares_and_line(&mut s); + let g = attach(&mut s, path, &[left, middle], json!({"pivot": 7})); + s.execute("object.objectsOnPath.options", &json!({"spacing": 30})).unwrap(); + s.execute("object.objectsOnPath.detach", &json!({"member": left.0})).unwrap(); + assert_eq!(orders(&s, g), (vec![middle], vec![path, middle])); + let b = bounds(&s, middle); + assert!((b.center().x - 50.0).abs() < 1e-6 && (b.y1 - 300.0).abs() < 1e-6, "the one left on the start, by its Pivot: {b:?}"); + s.execute("object.objectsOnPath.detach", &json!({"member": middle.0})).unwrap(); + let n = s.doc().unwrap().doc.node(g).unwrap().clone(); + let rec = n.extras().objects_on_path.as_deref().unwrap(); + assert_eq!((rec.path, rec.members.len(), rec.pivot, rec.spacing), (path, 0, 7, Some(30.0)), "a live group with no objects"); + assert_eq!(orders(&s, g).1, [path]); + // An object moved back in joins the path. + s.execute("layer.move", &json!({"ids": [left.0], "target": g.0, "place": "inside"})).unwrap(); + assert_eq!(orders(&s, g).0, [left]); +} + +#[test] +fn detach_refuses_what_is_not_one_chosen_editable_object_on_a_path() { + let mut s = session(); + let (g, [a, b, c], path) = three_on_a_line(&mut s); + let loose = rect(&mut s, 300.0, 20.0, 10.0, 10.0); + let err = |s: &mut Session, p: Value| s.execute("object.objectsOnPath.detach", &p).unwrap_err().to_string(); + let choose = "choose the object to detach with its Select/Move widget"; + let steps = s.doc().unwrap().history.undo.len(); + select(&mut s, &[g]); + assert!(err(&mut s, json!({})).contains(choose), "the group without a chosen object"); + select(&mut s, &[]); + assert!(err(&mut s, json!({})).contains(choose), "nothing selected"); + assert!(err(&mut s, json!({"member": path.0})).contains("`member` must name an object attached"), "the path"); + assert!(err(&mut s, json!({"member": g.0})).contains("`member` must name"), "the group itself"); + assert!(err(&mut s, json!({"member": loose.0})).contains("`member` must name"), "an object on no path"); + assert!(err(&mut s, json!({"member": 99999})).contains("no such object")); + assert!(err(&mut s, json!({"member": "a"})).contains("invalid object id")); + // Hidden and locked objects stay; a locked group keeps all of its objects. + select(&mut s, &[c]); + s.execute("object.lock", &json!({})).unwrap(); + assert!(err(&mut s, json!({"member": c.0})).contains("`member` names an object that is hidden or locked")); + s.execute("object.unlockAll", &json!({})).unwrap(); + select(&mut s, &[b]); + s.execute("object.hide", &json!({})).unwrap(); + assert!(err(&mut s, json!({"member": b.0})).contains("hidden or locked"), "a hidden object"); + s.execute("object.showAll", &json!({})).unwrap(); + select(&mut s, &[g]); + s.execute("object.lock", &json!({})).unwrap(); + assert!(err(&mut s, json!({"member": a.0})).contains("the Objects on Path group is hidden or locked")); + s.execute("object.unlockAll", &json!({})).unwrap(); + assert_eq!(orders(&s, g).0, [a, b, c], "nothing refused detached anything"); + let undone = s.doc().unwrap().history.undo.len(); + assert_eq!(undone, steps + 6, "only the lock, hide and show steps"); +} + +#[test] +fn an_object_detached_from_a_group_on_another_path_does_not_join_that_path() { + let mut s = session(); + let ([left, middle, right], path) = squares_and_line(&mut s); + let inner = attach(&mut s, path, &[left, middle], json!({})); + let outer_line = line(&mut s, (20.0, 380.0), (380.0, 380.0)); + let outer = attach(&mut s, outer_line, &[inner, right], json!({})); + let outer_members = orders(&s, outer).0; + s.execute("object.objectsOnPath.detach", &json!({"member": left.0})).unwrap(); + assert_eq!(orders(&s, inner).0, [middle]); + assert_eq!(orders(&s, outer).0, outer_members, "the outer path keeps its objects and takes no new one"); + let (layer, at) = place_of(&s, outer); + assert_eq!(place_of(&s, left), (layer, at + 1), "above the outer group"); +} + +#[test] +fn hidden_and_locked_objects_left_on_the_path_move_up_too() { + let mut s = session(); + let ([left, middle, right], path) = squares_and_line(&mut s); + let g = attach(&mut s, path, &[left, middle, right], json!({})); + select(&mut s, &[right]); + s.execute("object.lock", &json!({})).unwrap(); + select(&mut s, &[middle]); + s.execute("object.hide", &json!({})).unwrap(); + s.execute("object.objectsOnPath.detach", &json!({"member": left.0})).unwrap(); + assert_eq!(orders(&s, g).0, [middle, right]); + assert_eq!(center(&s, middle), Point::new(50.0, 300.0), "the hidden object moves up to the start"); + assert_eq!(center(&s, right), Point::new(350.0, 300.0)); +} + +#[test] +fn objects_moved_out_together_in_the_layers_panel_all_leave_the_path() { + let mut s = session(); + let ([left, middle, right], path) = squares_and_line(&mut s); + let g = attach(&mut s, path, &[left, middle, right], json!({})); + s.execute("layer.move", &json!({"ids": [left.0, middle.0], "target": g.0, "place": "above"})).unwrap(); + assert_eq!(orders(&s, g), (vec![right], vec![path, right])); + assert_eq!(center(&s, right), Point::new(50.0, 300.0), "the one left takes the start"); + let (layer, at) = place_of(&s, g); + assert_eq!((place_of(&s, left), place_of(&s, middle)), ((layer, at + 1), (layer, at + 2)), "above the group, in their order"); +} diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 1fb96d9c7d..695a914c02 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -1537,6 +1537,8 @@ font-technology Variable Променливи Objects on Path Tool Инструмент „Обекти по пътя“ Attach Objects on Path Прикрепяне на обекти към пътя Expand Objects on Path Разширяване на обекти по пътя + Detach Откачване + Detach Object from Path Откачване на обект от пътя Release Live Paint Освобождаване на динамично оцветяване Release Repeat Освобождаване на повторение Release Slice Освобождаване на отрязък diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index f04e9535e5..dead3c7366 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -1809,6 +1809,8 @@ digits Malayalam Malajalam Objects on Path Tool Alat objekti na putanji Attach Objects on Path Priloži objekte na putanju Expand Objects on Path Proširi objekte na putanji + Detach Odvoji + Detach Object from Path Odvoji objekat s putanje Release Live Paint Oslobodi živo bojenje Release Repeat Oslobodi ponavljanje Release Slice Oslobodi isječak @@ -4924,6 +4926,7 @@ imageTrace Outline Obris @msg select a repeat odaberite ponavljanje @msg select an Intertwine group odaberite Intertwine grupu @msg select an Objects on Path group odaberite grupu objekata na putanji +@msg choose the object to detach with its Select/Move widget odaberite objekat koji želite odvojiti njegovom ručicom Odaberi/Pomjeri @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects putanja unutar složene putanje, stapanja, omotnice, ponavljanja ili Intertwine grupe ne može primiti objekte @msg a clipping path can't take objects putanja isijecanja ne može primiti objekte @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path objekti unutar stapanja, omotnice, ponavljanja ili Intertwine grupe ne mogu se priložiti putanji diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index e6b6f398f2..5d0d4ce9bf 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -1542,6 +1542,8 @@ antiAlias Sharp Nítid Expand Live Paint Expandeix la pintura interactiva Expand Object Expandeix l’objecte Expand Objects on Path Expandeix els objectes del traçat + Detach Separa + Detach Object from Path Separa l’objecte del traçat Expand Options Opcions d’expansió Expand Panels Expandeix els panells Expand Repeat Expandeix la repetició @@ -6000,6 +6002,7 @@ digits Malayalam Malaiàlam @msg select an Intertwine group selecciona un grup Intertwine @msg select an object in perspective selecciona un objecte en perspectiva @msg select an Objects on Path group selecciona un grup d’objectes sobre un traçat +@msg choose the object to detach with its Select/Move widget tria l’objecte que vols separar amb el seu control Selecciona/Mou @msg select artwork (or a colour group) to recolor selecciona la il·lustració (o un grup de colors) per recolorejar @msg Recolor completed; {locked} and {hidden} were left unchanged Recoloració completada; sense canvis: {locked} i {hidden} @msg Recolor completed; {objects} were left unchanged Recoloració completada; sense canvis: {objects} diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index a44e3b9979..7356a6179c 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1840,6 +1840,8 @@ digits Malayalam Malajálamské Objects on Path Tool Nástroj Objekty na cestě Attach Objects on Path Připojit objekty na cestu Expand Objects on Path Rozložit objekty na cestě + Detach Odpojit + Detach Object from Path Odpojit objekt od cesty Release Live Paint Uvolnit živou malbu Release Repeat Uvolnit opakování Release Slice Uvolnit řez @@ -5091,6 +5093,7 @@ imageTrace Outline Obrys @msg select a repeat vyberte opakování @msg select an Intertwine group vyberte skupinu propletení @msg select an Objects on Path group vyberte skupinu objektů na cestě +@msg choose the object to detach with its Select/Move widget vyberte objekt k odpojení jeho ovládacím prvkem Vybrat/Přesunout @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects cesta uvnitř složené cesty, prolnutí, obálky, opakování nebo skupiny propletení nemůže přijmout objekty @msg a clipping path can't take objects ořezová cesta nemůže přijmout objekty @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path objekty uvnitř prolnutí, obálky, opakování nebo skupiny propletení nelze připojit na cestu diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index bce3bd12da..8b2d8b6683 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -5821,6 +5821,8 @@ swatch Bright Violet Kraftig violet Objects on Path Tool Objekter på kurve-værktøj Attach Objects on Path Tilknyt objekter til kurve Expand Objects on Path Udvid objekter på kurve + Detach Frigør + Detach Object from Path Frigør objekt fra kurve Release Live Paint Frigør live-maling Release Repeat Frigør gentagelse Release Slice Frigør udsnit diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 0d995ddc2e..1148856319 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1592,6 +1592,8 @@ file-filter Templates Vorlagen Objects on Path Tool Objekte-auf-Pfad-Werkzeug Attach Objects on Path Objekte an Pfad anhängen Expand Objects on Path Objekte auf Pfad umwandeln + Detach Lösen + Detach Object from Path Objekt vom Pfad lösen Release Live Paint „Interaktiv malen“ zurückwandeln Release Repeat Wiederholung zurückwandeln Release Slice Slice zurückwandeln @@ -4706,6 +4708,7 @@ theme Appearance Erscheinungsbild @msg select a repeat wählen Sie eine Wiederholung aus @msg select an Intertwine group wählen Sie eine Verflechtungsgruppe aus @msg select an Objects on Path group wählen Sie eine Gruppe „Objekte auf Pfad“ aus +@msg choose the object to detach with its Select/Move widget wählen Sie das zu lösende Objekt mit seinem Steuerelement „Auswählen/Verschieben“ aus @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects ein Pfad in einem zusammengesetzten Pfad, einer Angleichung, einer Hülle, einer Wiederholung oder einer Verflechtungsgruppe kann keine Objekte aufnehmen @msg a clipping path can't take objects ein Schnittpfad kann keine Objekte aufnehmen @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path die Objekte in einer Angleichung, einer Hülle, einer Wiederholung oder einer Verflechtungsgruppe können nicht an einen Pfad angehängt werden diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index cb409e9274..a906b40d12 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1512,6 +1512,8 @@ font-technology Variable Μεταβλητές Objects on Path Tool Αντικείμενα σε διαδρομή (Objects on Path Tool) Attach Objects on Path Προσάρτηση αντικειμένων σε διαδρομή Expand Objects on Path Επέκταση αντικειμένων σε διαδρομή + Detach Αποσύνδεση + Detach Object from Path Αποσύνδεση αντικειμένου από διαδρομή Release Live Paint Αποδέσμευση δυναμικής βαφής Release Repeat Αποδέσμευση επανάληψης Release Slice Αποδέσμευση τομής @@ -6016,6 +6018,7 @@ effect Points Σημεία @msg no bound object reports a value (locked objects and embedded images report none) κανένα συνδεδεμένο αντικείμενο δεν αναφέρει τιμή (τα κλειδωμένα αντικείμενα και οι ενσωματωμένες εικόνες δεν αναφέρουν καμία) @msg select an Intertwine group επιλέξτε μια ομάδα διαπλοκής @msg select an Objects on Path group επιλέξτε μια ομάδα αντικειμένων σε διαδρομή +@msg choose the object to detach with its Select/Move widget επιλέξτε το αντικείμενο προς αποσύνδεση με το στοιχείο ελέγχου Επιλογή/Μετακίνηση @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects μια διαδρομή μέσα σε σύνθετη διαδρομή, ανάμειξη, περίβλημα, επανάληψη ή ομάδα διαπλοκής δεν μπορεί να δεχτεί αντικείμενα @msg a clipping path can't take objects μια διαδρομή αποκοπής δεν μπορεί να δεχτεί αντικείμενα @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path τα αντικείμενα μέσα σε ανάμειξη, περίβλημα, επανάληψη ή ομάδα διαπλοκής δεν μπορούν να προσαρτηθούν σε διαδρομή diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index de9512cddb..57b9ee9cd7 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1592,6 +1592,8 @@ font-technology Variable Variables Objects on Path Tool Herramienta Objetos en trazado Attach Objects on Path Adjuntar objetos al trazado Expand Objects on Path Expandir objetos en trazado + Detach Separar + Detach Object from Path Separar objeto del trazado Release Live Paint Soltar pintura interactiva Release Repeat Soltar repetición Release Slice Soltar sector @@ -4783,6 +4785,7 @@ axis Both Ambos @msg select a repeat selecciona una repetición @msg select an Intertwine group selecciona un grupo entrelazado @msg select an Objects on Path group selecciona un grupo de objetos en trazado +@msg choose the object to detach with its Select/Move widget elige el objeto que quieres separar con su control Seleccionar/Mover @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects un trazado dentro de un trazado compuesto, una fusión, una envolvente, una repetición o un grupo entrelazado no puede recibir objetos @msg a clipping path can't take objects un trazado de recorte no puede recibir objetos @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path los objetos dentro de una fusión, una envolvente, una repetición o un grupo entrelazado no se pueden adjuntar a un trazado diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 1c3327fb1e..e80c061677 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -1813,6 +1813,8 @@ digits Malayalam Malajalam Objects on Path Tool Objektit polulla -työkalu Attach Objects on Path Kiinnitä objektit polulle Expand Objects on Path Laajenna objektit polulla + Detach Irrota + Detach Object from Path Irrota objekti polulta Release Live Paint Vapauta elävä maalaus Release Repeat Vapauta toisto Release Slice Vapauta siivu @@ -6028,6 +6030,7 @@ mark-style Japanese Japanilainen @msg no bound object reports a value (locked objects and embedded images report none) mikään sidottu objekti ei ilmoita arvoa (lukitut objektit ja upotetut kuvat eivät ilmoita arvoa) @msg select an Intertwine group valitse lomitusryhmä @msg select an Objects on Path group valitse Objektit polulla -ryhmä +@msg choose the object to detach with its Select/Move widget valitse irrotettava objekti sen Valitse/Siirrä-säätimellä @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects yhdistelmäpolun, sulautuksen, vääristyskuoren, toiston tai lomitusryhmän sisällä olevaan polkuun ei voi kiinnittää objekteja @msg a clipping path can't take objects leikkauspolkuun ei voi kiinnittää objekteja @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path sulautuksen, vääristyskuoren, toiston tai lomitusryhmän sisällä olevia objekteja ei voi kiinnittää polulle diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 3b45c4d386..450a696e99 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1586,6 +1586,8 @@ font-technology Variable Variables Objects on Path Tool Outil Objets sur tracé Attach Objects on Path Attacher des objets au tracé Expand Objects on Path Décomposer les objets sur tracé + Detach Détacher + Detach Object from Path Détacher l’objet du tracé Release Live Paint Annuler la peinture dynamique Release Repeat Annuler la répétition Release Slice Annuler la tranche @@ -4772,6 +4774,7 @@ axis Both Les deux @msg select a repeat sélectionnez une répétition @msg select an Intertwine group sélectionnez un groupe entrelacé @msg select an Objects on Path group sélectionnez un groupe d’objets sur tracé +@msg choose the object to detach with its Select/Move widget choisissez l’objet à détacher avec sa commande Sélectionner/Déplacer @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects un tracé à l’intérieur d’un tracé transparent, d’un dégradé de formes, d’une enveloppe, d’une répétition ou d’un groupe entrelacé ne peut pas recevoir d’objets @msg a clipping path can't take objects un tracé d’écrêtage ne peut pas recevoir d’objets @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path les objets à l’intérieur d’un dégradé de formes, d’une enveloppe, d’une répétition ou d’un groupe entrelacé ne peuvent pas être attachés à un tracé diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 0c6089e40a..e1c1c0ef7a 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -1706,6 +1706,8 @@ antiAlias Sharp शार्प Expand Live Paint लाइव पेंट एक्सपैंड करें Expand Object ऑब्जेक्ट का विस्तार Expand Objects on Path पाथ पर ऑब्जेक्ट्स एक्सपैंड करें + Detach अलग करें + Detach Object from Path पाथ से ऑब्जेक्ट अलग करें Expand Options ऑप्शन्स एक्सपैंड करें Expand Panels पैनलों का विस्तार Expand Repeat रिपीट एक्सपैंड करें @@ -7731,6 +7733,7 @@ antiAlias Sharp शार्प @msg select an object with a freeform gradient एक फ्रीफॉर्म ग्रेडिएंट के साथ एक ऑब्जेक्ट सिलेक्ट करें @msg select an object with an opacity mask एक opacity मास्क के साथ एक ऑब्जेक्ट सिलेक्ट करें @msg select an Objects on Path group पाथ समूह पर एक ऑब्जेक्ट चुनें +@msg choose the object to detach with its Select/Move widget अलग करने वाले ऑब्जेक्ट को उसके चुनें/ले जाएँ विजेट से चुनें @msg select area type क्षेत्र प्रकार सिलेक्ट करें @msg select area type (text in a frame) क्षेत्र प्रकार (एक फ्रेम में टेक्स्ट) सिलेक्ट करें @msg select area type and closed paths (at least one area type object) क्षेत्र प्रकार और बंद पाथ (कम से कम एक क्षेत्र प्रकार ऑब्जेक्ट) सिलेक्ट करें diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 06122f94ee..db3587cd1f 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1999,6 +1999,8 @@ file-filter Templates Sablonok Objects on Path Tool Objektumok útvonalon Attach Objects on Path Objektumok csatolása útvonalhoz Expand Objects on Path Objektumok útvonalon kibontása + Detach Leválasztás + Detach Object from Path Objektum leválasztása az útvonalról Release Live Paint Élő festés megszüntetése Release Repeat Ismétlés megszüntetése Release Slice Szelet megszüntetése @@ -5539,6 +5541,7 @@ imageTrace Outline Körvonal @msg select a repeat jelölj ki egy ismétlést @msg select an Intertwine group jelölj ki egy összefonási csoportot @msg select an Objects on Path group jelölj ki egy Objektumok útvonalon csoportot +@msg choose the object to detach with its Select/Move widget válaszd ki a leválasztandó objektumot a Kijelölés/Mozgatás vezérlőjével @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects összetett útvonalban, összemosásban, burkolóban, ismétlésben vagy összefonási csoportban lévő útvonalhoz nem csatolhatók objektumok @msg a clipping path can't take objects vágóútvonalhoz nem csatolhatók objektumok @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path összemosásban, burkolóban, ismétlésben vagy összefonási csoportban lévő objektumok nem csatolhatók útvonalhoz diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 00d2b09600..ecdb10db4f 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -5821,6 +5821,8 @@ swatch Bright Violet Violet cerah Objects on Path Tool Alat objek pada jalur Attach Objects on Path Tempelkan objek pada jalur Expand Objects on Path Perluas objek pada jalur + Detach Lepaskan + Detach Object from Path Lepaskan objek dari jalur Release Live Paint Lepaskan Live Paint Release Repeat Lepaskan perulangan Release Slice Lepaskan slice diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 869fe2e4a0..3ef40a5c95 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1623,6 +1623,8 @@ file-filter Templates Modelli Objects on Path Tool Strumento Oggetti su tracciato Attach Objects on Path Aggancia oggetti al tracciato Expand Objects on Path Espandi oggetti su tracciato + Detach Sgancia + Detach Object from Path Sgancia oggetto dal tracciato Release Live Paint Rilascia pittura dinamica Release Repeat Rilascia ripetizione Release Slice Rilascia sezione @@ -4834,6 +4836,7 @@ axis Both Entrambi @msg select a repeat seleziona una ripetizione @msg select an Intertwine group seleziona un gruppo intrecciato @msg select an Objects on Path group seleziona un gruppo di oggetti su tracciato +@msg choose the object to detach with its Select/Move widget scegli l’oggetto da sganciare con il suo controllo Seleziona/Sposta @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects un tracciato all'interno di un tracciato composto, una fusione, un involucro, una ripetizione o un gruppo intrecciato non può ricevere oggetti @msg a clipping path can't take objects un tracciato di ritaglio non può ricevere oggetti @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path gli oggetti all'interno di una fusione, un involucro, una ripetizione o un gruppo intrecciato non possono essere agganciati a un tracciato diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index a0ce9c93b9..9d88af4809 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1992,6 +1992,8 @@ antiAlias Sharp シャープ Objects on Path Tool パス上のオブジェクトツール Attach Objects on Path パス上のオブジェクトをアタッチ Expand Objects on Path パス上のオブジェクトを拡張 + Detach 切り離し + Detach Object from Path オブジェクトをパスから切り離し Release Live Paint ライブペイントを解除 Release Repeat リピートを解除 Release Slice スライスを解除 @@ -4556,6 +4558,7 @@ swatch Bright Violet ビビッドバイオレット @msg select a repeat リピートを選択してください @msg select an Intertwine group 織り交ぜグループを選択してください @msg select an Objects on Path group パス上のオブジェクトグループを選択してください +@msg choose the object to detach with its Select/Move widget 切り離すオブジェクトを選択/移動ウィジェットで選んでください @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects 複合パス、ブレンド、エンベロープ、リピートまたは織り交ぜグループ内のパスにはオブジェクトをアタッチできません @msg a clipping path can't take objects クリッピングパスにはオブジェクトをアタッチできません @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path ブレンド、エンベロープ、リピートまたは織り交ぜグループ内のオブジェクトはパスにアタッチできません diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index b19ed4009a..b57164cce1 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -5715,6 +5715,7 @@ imageTrace Outline კონტური Arrange Intertwine Area გადახლართვის არის დალაგება @msg select an Intertwine group მონიშნეთ გადახლართვის ჯგუფი @msg select an Objects on Path group მონიშნეთ ტრაექტორიაზე ობიექტების ჯგუფი +@msg choose the object to detach with its Select/Move widget მოსახსნელი ობიექტი აირჩიეთ მისი არჩევა/გადატანის ვიჯეტით @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects შედგენილ ტრაექტორიაში, გადასვლაში, გარსში, გამეორებაში ან გადახლართვის ჯგუფში მყოფ ტრაექტორიაზე ობიექტების მიმაგრება ვერ ხერხდება @msg a clipping path can't take objects მომჭრელ ტრაექტორიაზე ობიექტების მიმაგრება ვერ ხერხდება @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path გადასვლაში, გარსში, გამეორებაში ან გადახლართვის ჯგუფში მყოფი ობიექტების ტრაექტორიაზე მიმაგრება ვერ ხერხდება @@ -5977,6 +5978,8 @@ imageTrace Outline კონტური Objects on Path Tool ტრაექტორიაზე ობიექტების ხელსაწყო Attach Objects on Path ობიექტების ტრაექტორიაზე მიმაგრება Expand Objects on Path ტრაექტორიაზე ობიექტების გაშლა + Detach მოხსნა + Detach Object from Path ობიექტის ტრაექტორიიდან მოხსნა Release Live Paint ცოცხალი შეღებვის მოხსნა Release Repeat გამეორების მოხსნა Release Slice ნაჭრის მოხსნა diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 3f518f00c4..0dec091e81 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -1982,6 +1982,8 @@ antiAlias Sharp 날카롭게 Objects on Path Tool 패스 위 개체 도구 Attach Objects on Path 패스 위 개체 부착 Expand Objects on Path 패스 위 개체 확장 + Detach 분리 + Detach Object from Path 패스에서 개체 분리 Release Live Paint 라이브 페인트 해제 Release Repeat 반복 해제 Release Slice 분할 해제 @@ -4483,6 +4485,7 @@ theme Appearance 외관 @msg select a repeat 반복을 선택하세요 @msg select an Intertwine group 교차 그룹을 선택하세요 @msg select an Objects on Path group 패스 위 개체 그룹을 선택하세요 +@msg choose the object to detach with its Select/Move widget 분리할 개체를 선택/이동 위젯으로 고르세요 @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects 컴파운드 패스, 블렌드, 엔벨로프, 반복 또는 교차 그룹 안의 패스에는 개체를 부착할 수 없습니다 @msg a clipping path can't take objects 클리핑 패스에는 개체를 부착할 수 없습니다 @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path 블렌드, 엔벨로프, 반복 또는 교차 그룹 안의 개체는 패스에 부착할 수 없습니다 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 40facfda42..0d330108dc 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -185,6 +185,7 @@ @msg node {_1} is on a locked artboard knooppunt {_1} staat op een vergrendeld tekengebied @msg thumbnail: no page {n} (the file has 1) miniatuur: geen pagina {n} (het bestand heeft er 1) @msg select an Objects on Path group selecteer een groep Objecten op pad +@msg choose the object to detach with its Select/Move widget kies het object dat je wilt loskoppelen met zijn widget Selecteren/verplaatsen @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects een pad binnen een samengesteld pad, mengsel, omvorming, herhaling of verstrengelingsgroep kan geen objecten opnemen @msg a clipping path can't take objects een knippad kan geen objecten opnemen @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path objecten binnen een mengsel, omvorming, herhaling of verstrengelingsgroep kunnen niet aan een pad worden gekoppeld @@ -6391,6 +6392,8 @@ swatch Bright Violet Felviolet Objects on Path Tool Objecten-op-pad-gereedschap Attach Objects on Path Objecten aan pad koppelen Expand Objects on Path Objecten op pad uitbreiden + Detach Loskoppelen + Detach Object from Path Object van pad loskoppelen Release Live Paint Live Paint vrijgeven Release Repeat Herhaling vrijgeven Release Slice Slice vrijgeven diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 770622cc1e..9955d76ff7 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1757,6 +1757,8 @@ digits Malayalam Malajalam Objects on Path Tool Narzędzie Obiekty na ścieżce Attach Objects on Path Dołącz obiekty do ścieżki Expand Objects on Path Rozszerz obiekty na ścieżce + Detach Odłącz + Detach Object from Path Odłącz obiekt od ścieżki Release Live Paint Uwolnij aktywne malowanie Release Repeat Uwolnij powtórzenie Release Slice Uwolnij plasterek @@ -4916,6 +4918,7 @@ imageTrace Outline Kontur @msg select a repeat zaznacz powtórzenie @msg select an Intertwine group zaznacz grupę przeplotu @msg select an Objects on Path group zaznacz grupę obiektów na ścieżce +@msg choose the object to detach with its Select/Move widget wybierz obiekt do odłączenia jego uchwytem Zaznacz/Przesuń @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects do ścieżki wewnątrz ścieżki złożonej, przejścia, koperty, powtórzenia lub grupy przeplotu nie można dołączyć obiektów @msg a clipping path can't take objects do ścieżki przycinającej nie można dołączyć obiektów @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path obiektów wewnątrz przejścia, koperty, powtórzenia lub grupy przeplotu nie można dołączyć do ścieżki diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 77fce38c53..148e676987 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1410,6 +1410,8 @@ antiAlias Sharp Nítido Objects on Path Tool Ferramenta Objetos no Caminho Attach Objects on Path Anexar Objetos ao Caminho Expand Objects on Path Expandir Objetos no Caminho + Detach Desanexar + Detach Object from Path Desanexar Objeto do Caminho Release Live Paint Liberar Pintura ao Vivo Release Repeat Liberar Repetição Release Slice Liberar Fatia @@ -4239,6 +4241,7 @@ axis Both Ambos @msg select a repeat selecione uma repetição @msg select an Intertwine group selecione um grupo entrelaçado @msg select an Objects on Path group selecione um grupo de objetos no caminho +@msg choose the object to detach with its Select/Move widget escolha o objeto a desanexar com o controle Selecionar/Mover dele @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects um caminho dentro de um caminho composto, mesclagem, envelope, repetição ou grupo entrelaçado não pode receber objetos @msg a clipping path can't take objects um caminho de recorte não pode receber objetos @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path objetos dentro de uma mesclagem, envelope, repetição ou grupo entrelaçado não podem ser anexados a um caminho diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index b546e87258..0987445196 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -1612,6 +1612,8 @@ file-filter Templates Шаблоны Objects on Path Tool Инструмент «Объекты на контуре» Attach Objects on Path Прикрепить объекты к контуру Expand Objects on Path Разобрать объекты на контуре + Detach Открепить + Detach Object from Path Открепить объект от контура Release Live Paint Освободить быструю заливку Release Repeat Освободить повтор Release Slice Освободить фрагмент @@ -4820,6 +4822,7 @@ axis Both Оба @msg select a repeat выделите повтор @msg select an Intertwine group выделите группу переплетения @msg select an Objects on Path group выделите группу объектов на контуре +@msg choose the object to detach with its Select/Move widget выберите открепляемый объект его элементом «Выбрать/Переместить» @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects к контуру внутри составного контура, перехода, оболочки, повтора или группы переплетения нельзя прикрепить объекты @msg a clipping path can't take objects к контуру обрезки нельзя прикрепить объекты @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path объекты внутри перехода, оболочки, повтора или группы переплетения нельзя прикрепить к контуру diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 9271ac4bac..9b6017228b 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -1550,6 +1550,8 @@ font-technology Variable Variabla Objects on Path Tool Verktyg för objekt på bana Attach Objects on Path Fäst objekt på bana Expand Objects on Path Expandera objekt på bana + Detach Lossa + Detach Object from Path Lossa objekt från bana Release Live Paint Släpp direktmålning Release Repeat Släpp upprepning Release Slice Släpp skiva @@ -6019,6 +6021,7 @@ theme Appearance Utseende @msg no bound object reports a value (locked objects and embedded images report none) inget bundet objekt rapporterar ett värde (låsta objekt och inbäddade bilder rapporterar inget) @msg select an Intertwine group markera en sammanflätningsgrupp @msg select an Objects on Path group markera en grupp med objekt på bana +@msg choose the object to detach with its Select/Move widget välj objektet som ska lossas med dess Markera/Flytta-reglage @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects en bana inuti en sammansatt bana, en blandning, ett kuvert, en upprepning eller en sammanflätningsgrupp kan inte ta emot objekt @msg a clipping path can't take objects en klippbana kan inte ta emot objekt @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path objekt inuti en blandning, ett kuvert, en upprepning eller en sammanflätningsgrupp kan inte fästas på en bana diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index 605ff0382b..2ee4080c81 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -184,6 +184,23 @@ fn font_height_reference_labels_are_in_complete_catalogs() { } } +/// The Objects on Path Options section's Detach button, its undo step and the message for a +/// Detach with no object chosen. +#[test] +fn objects_on_path_detach_strings_are_in_complete_catalogs() { + for lang in LANGUAGES.iter().filter(|lang| lang.complete_menus) { + for label in ["Detach", "Detach Object from Path"] { + assert!(lang.catalog().plain(label).is_some(), "{} is missing {label}", lang.code); + } + } + let choose = "choose the object to detach with its Select/Move widget"; + assert!(message_literals().contains(choose)); + for code in COMPLETE_MESSAGES { + let lang = Lang::from_code(code).expect("registered"); + assert_ne!(message(lang, choose), choose, "{code}"); + } +} + /// The Window › Brush Libraries menu, built from the built-in library table, names each library. #[test] fn built_in_brush_library_names_are_in_complete_catalogs() { diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 107f30f3ea..b2cc8e18cb 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -5977,6 +5977,7 @@ theme Appearance Görünüm @msg nothing in data set {name} was applied: no object is bound to a variable it has a value for {name} veri kümesinden hiçbir şey uygulanmadı: değer içerdiği bir değişkene bağlı hiçbir nesne yok @msg no bound object reports a value (locked objects and embedded images report none) bağlı hiçbir nesne bir değer bildirmiyor (kilitli nesneler ve gömülü görüntüler değer bildirmez) @msg select an Objects on Path group bir Yol Üzerindeki Nesneler grubu seçin +@msg choose the object to detach with its Select/Move widget ayrılacak nesneyi Seç/Taşı denetimiyle seçin @msg the Simplify slider is not open Basitleştirme kaydırıcısı açık değil @msg the Smooth slider is not open Düzgünleştirme kaydırıcısı açık değil @msg {_1} is an exporter (File › Export › Export As…) {_1} bir dışa aktarma eklentisidir (Dosya › Dışa Aktar › Farklı Dışa Aktar…) @@ -6009,6 +6010,8 @@ theme Appearance Görünüm Chrome Krom Chrome… Krom… Expand Objects on Path Yol Üzerindeki Nesneleri Genişlet + Detach Ayır + Detach Object from Path Nesneyi Yoldan Ayır Graphic Pen Grafik Kalemi Graphic Pen… Grafik Kalemi… Move Anchor Point Bağlantı Noktasını Taşı diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 68aabf0253..7a51c3a074 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -1617,6 +1617,8 @@ file-filter Templates Шаблони Objects on Path Tool Інструмент «Об’єкти на контурі» Attach Objects on Path Прикріпити об’єкти до контуру Expand Objects on Path Розібрати об’єкти на контурі + Detach Відкріпити + Detach Object from Path Відкріпити об’єкт від контуру Release Live Paint Звільнити швидку заливку Release Repeat Звільнити повтор Release Slice Звільнити фрагмент @@ -4818,6 +4820,7 @@ axis Both Обидві @msg select a repeat виділіть повтор @msg select an Intertwine group виділіть групу переплетення @msg select an Objects on Path group виділіть групу об’єктів на контурі +@msg choose the object to detach with its Select/Move widget виберіть об’єкт для відкріплення його елементом «Вибрати/Перемістити» @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects до контуру всередині складеного контуру, переходу, оболонки, повтору або групи переплетення не можна прикріпити об’єкти @msg a clipping path can't take objects до відсічного контуру не можна прикріпити об’єкти @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path об’єкти всередині переходу, оболонки, повтору або групи переплетення не можна прикріпити до контуру diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 36793e6c56..e79a56d178 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1847,6 +1847,8 @@ digits Hindi Hindi Objects on Path Tool Công cụ Đối tượng trên đường dẫn Attach Objects on Path Gắn đối tượng lên đường dẫn Expand Objects on Path Mở rộng đối tượng trên đường dẫn + Detach Tách + Detach Object from Path Tách đối tượng khỏi đường dẫn Release Live Paint Giải phóng tô trực tiếp Release Repeat Giải phóng lặp lại Release Slice Giải phóng lát cắt @@ -5202,6 +5204,7 @@ theme Appearance Giao diện @msg select a repeat hãy chọn một đối tượng lặp @msg select an Intertwine group hãy chọn một nhóm đan xen @msg select an Objects on Path group hãy chọn một nhóm đối tượng trên đường dẫn +@msg choose the object to detach with its Select/Move widget hãy chọn đối tượng cần tách bằng tiện ích Chọn/Di chuyển của nó @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects không thể gắn đối tượng lên một đường dẫn nằm trong đường dẫn phức, đối tượng hòa trộn, Envelope, đối tượng lặp hoặc nhóm đan xen @msg a clipping path can't take objects không thể gắn đối tượng lên đường dẫn cắt @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path không thể gắn các đối tượng nằm trong đối tượng hòa trộn, Envelope, đối tượng lặp hoặc nhóm đan xen lên đường dẫn diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 27cad80b4a..ac09988981 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1543,6 +1543,8 @@ file-filter Templates 模板 Objects on Path Tool 路径上的对象工具 Attach Objects on Path 附加路径上的对象 Expand Objects on Path 扩展路径上的对象 + Detach 分离 + Detach Object from Path 将对象从路径分离 Release Live Paint 释放实时上色 Release Repeat 释放重复 Release Slice 释放切片 @@ -4259,6 +4261,7 @@ imageTrace Outline 轮廓 @msg select a repeat 请选择一个重复对象 @msg select an Intertwine group 请选择一个交织组 @msg select an Objects on Path group 请选择一个路径上的对象组 +@msg choose the object to detach with its Select/Move widget 请用对象的“选择/移动”控件选择要分离的对象 @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects 复合路径、混合对象、封套、重复对象或交织组中的路径无法附加对象 @msg a clipping path can't take objects 剪切路径无法附加对象 @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path 混合对象、封套、重复对象或交织组中的对象无法附加到路径 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index ab1fd359cd..7c45de741c 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1588,6 +1588,8 @@ file-filter Templates 範本 Objects on Path Tool 路徑上的物件工具 Attach Objects on Path 附加路徑上的物件 Expand Objects on Path 展開路徑上的物件 + Detach 分離 + Detach Object from Path 將物件從路徑分離 Release Live Paint 釋放即時上色 Release Repeat 釋放重複 Release Slice 釋放切片 @@ -2021,6 +2023,7 @@ file-filter Templates 範本 @msg select a repeat 請選取重複物件 @msg select an Intertwine group 請選取交織群組 @msg select an Objects on Path group 請選取路徑上的物件群組 +@msg choose the object to detach with its Select/Move widget 請用物件的「選取/移動」控制項選取要分離的物件 @msg a path inside a compound path, blend, envelope, repeat or Intertwine group can't take objects 複合路徑、漸變物件、封套、重複物件或交織群組中的路徑無法附加物件 @msg a clipping path can't take objects 剪裁路徑無法附加物件 @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path 漸變物件、封套、重複物件或交織群組中的物件無法附加到路徑 diff --git a/crates/ui-egui/src/panels/properties.rs b/crates/ui-egui/src/panels/properties.rs index 07b53cc6d8..6195de4b24 100644 --- a/crates/ui-egui/src/panels/properties.rs +++ b/crates/ui-egui/src/panels/properties.rs @@ -389,12 +389,15 @@ fn trace_section(app: &mut VectorcraftApp, ui: &mut Ui, preset: &str, view: vect }); } -/// The Objects on Path groups selected with nothing else: their ids, and the Pivot and Rotate they -/// share (`None` where they differ). +/// The Objects on Path groups selected with nothing else: their ids, the Pivot and Rotate they +/// share (`None` where they differ), and whether Detach can take an object off the path: one group, +/// neither hidden nor locked, with an object chosen with its Select/Move widget that is neither +/// hidden nor locked. struct ObjectsOnPathSelection { ids: Vec, pivot: Option, rotate: Option, + detachable: bool, } /// The selection as Objects on Path groups ([`ObjectsOnPathSelection`]), when every selected @@ -405,7 +408,11 @@ fn selected_objects_on_path(app: &VectorcraftApp) -> Option out = Some(ObjectsOnPathSelection { ids: vec![id.0], pivot: Some(rec.pivot), rotate: Some(rec.rotate) }), + None => { + let chosen = st.selection.on_path_member.filter(|m| st.selection.objects.len() == 1 && rec.members.iter().any(|x| x.id == *m)); + let detachable = chosen.is_some_and(|m| st.doc.is_editable(*id) && st.doc.is_editable(m)); + out = Some(ObjectsOnPathSelection { ids: vec![id.0], pivot: Some(rec.pivot), rotate: Some(rec.rotate), detachable }); + } Some(s) => { s.ids.push(id.0); if s.pivot != Some(rec.pivot) { @@ -425,7 +432,8 @@ fn selected_objects_on_path(app: &VectorcraftApp) -> Option). A change goes to every /// selected group in one undo step (`object.objectsOnPath.options`). Where the groups differ, no -/// Pivot square is chosen and Rotate is blank. +/// Pivot square is chosen and Rotate is blank. Detach takes the object chosen with its Select/Move +/// widget off the path (`object.objectsOnPath.detach`), and is dimmed until one is chosen. fn objects_on_path_section(app: &mut VectorcraftApp, ui: &mut Ui, oop: &ObjectsOnPathSelection) { section_header(ui, tl!("Objects on Path Options")); let fw = super::field_width(ui); @@ -452,6 +460,12 @@ fn objects_on_path_section(app: &mut VectorcraftApp, ui: &mut Ui, oop: &ObjectsO } ui.end_row(); }); + let w = ui.available_width(); + if ui.add_enabled_ui(oop.detachable, |ui| widgets::flat_button(ui, tl!("Detach"), w)).inner.clicked() + && let Err(e) = app.run("object.objectsOnPath.detach", json!({})) + { + app.status(e); + } } /// Whether the selection uses more than one colour (`recolor.colors`), which offers the Recolor diff --git a/crates/ui-egui/src/panels/properties/tests.rs b/crates/ui-egui/src/panels/properties/tests.rs index 5ed020c326..9300619765 100644 --- a/crates/ui-egui/src/panels/properties/tests.rs +++ b/crates/ui-egui/src/panels/properties/tests.rs @@ -1879,6 +1879,39 @@ fn objects_on_path_options_set_pivot_and_rotate_in_one_undo_step() { assert_eq!(undo_depth(&app), depth + 1, "one undo step for both groups"); } +/// The objects of the Objects on Path group `id`, in their order along the path. +fn on_path(app: &VectorcraftApp, id: &Value) -> Vec { + let d = &app.session.active().unwrap().doc; + let rec = d.node(vectorcraft_doc::NodeId(id.as_u64().unwrap())).unwrap().extras().objects_on_path.as_deref().unwrap(); + rec.members.iter().map(|m| m.id.0).collect() +} + +/// The help page's Detach in the Objects on Path Options section takes the object chosen with its +/// Select/Move widget off the path in one undo step and selects it. It is dimmed, and a click does +/// nothing, until one group is selected with an object chosen. +#[test] +fn detach_takes_the_chosen_object_off_the_path() { + let (mut app, ctx) = reference_setup(&[]); + let group = objects_on_path(&mut app, 150.0, 0.0); + let [first, second] = on_path(&app, &group)[..] else { panic!("two objects") }; + assert!(shows(&panel_frame(&mut app, &ctx, vec![], objects_on_path_options), "Detach")); + let depth = undo_depth(&app); + click_named(&mut app, &ctx, objects_on_path_options, "Detach"); + assert_eq!((on_path(&app, &group), undo_depth(&app)), (vec![first, second], depth), "no object chosen: dimmed"); + let other = objects_on_path(&mut app, 250.0, 0.0); + app.run("object.objectsOnPath.selectMember", json!({"member": first})).unwrap(); + app.run("select.add", json!({"ids": [other]})).unwrap(); + click_named(&mut app, &ctx, objects_on_path_options, "Detach"); + assert_eq!(on_path(&app, &group), [first, second], "two groups: dimmed"); + app.run("object.objectsOnPath.selectMember", json!({"member": first})).unwrap(); + let depth = undo_depth(&app); + click_named(&mut app, &ctx, objects_on_path_options, "Detach"); + assert_eq!(on_path(&app, &group), [second]); + assert_eq!(undo_depth(&app), depth + 1, "one undo step"); + let sel = &app.session.active().unwrap().selection; + assert_eq!((sel.objects.iter().map(|i| i.0).collect::>(), sel.on_path_member), (vec![first], None)); +} + /// #3759: with area type selected, the Properties panel's Paragraph section shows Align Top, Align /// Center, Align Bottom and Justify Vertically; a click sets the alignment on every selected frame /// in one undo step. Point type shows none; frames with rows or columns and threaded frames turn diff --git a/docs/mcp.md b/docs/mcp.md index 41e28969f4..51816c1a46 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -9896,11 +9896,26 @@ order along the path changes: Space, the start, Pivot, Rotate and the children's new place along the path counted from 0. `object.objectsOnPath.selectMember {member}` takes the same kind of `member` and returns `{group, member}`. +`object.objectsOnPath.detach {member?}` takes one object off the path: `member`, or without it the object chosen with +the Select/Move widget of the one selected group (otherwise it fails with "choose the object to detach with its +Select/Move widget"). `member` takes the same kind of object as `moveMember`, and neither it nor the group may be +hidden or locked. The object leaves the group and goes just above it in the group's layer or group, staying where it +is drawn, and becomes the selection; when the group is itself an object on another group's path, the object goes just +above that outer group, so it does not join its path. The objects after it move up the path in their order: with Auto Space they spread +evenly again (on an open path the first and last on its ends while two or more are left), and a Space value and the +start are kept, drawn as close as the path has room for. Hidden and locked objects left in the group move up too. The +group, its path and its options stay even with one object or none left, and an object moved back into it joins the +path. It is one undo step, and undo puts the object back on the path with the choice it had. It returns +`{group, member}`. In the desktop app, Detach in the Properties panel's Objects on Path Options section sends it, and +is dimmed until one group is selected with an object chosen. To take several objects off at once, drag their rows out +of the group in the Layers panel (`layer.move`). + ```json {"name":"run_command","arguments":{"command":"tool.setOption","params":{"tool":"objectsOnPath","values":{"pivot":7,"rotate":30}}}} {"name":"run_command","arguments":{"command":"object.objectsOnPath.attach","params":{"path":30,"ids":[12,15,18]}}} {"name":"run_command","arguments":{"command":"object.objectsOnPath.options","params":{"pivot":7,"rotate":15}}} {"name":"run_command","arguments":{"command":"object.objectsOnPath.options","params":{"spacing":24,"start":0}}} +{"name":"run_command","arguments":{"command":"object.objectsOnPath.detach","params":{"member":15}}} {"name":"run_command","arguments":{"command":"object.objectsOnPath.expand","params":{}}} ``` diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index d9783b0216..c145a7fb7b 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1367,10 +1367,11 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur Window › Blend opens the Blend panel: Make, the selected blend's Spacing and Orientation (with no blend selected, the options new blends start with), Reverse Front to Back, Reverse Spine, Replace Spine, Expand and Release, each running its Object › Blend command. Make is dimmed until two or more objects are selected. Spacing and Orientation are dimmed until a blend or two or more objects are selected, and the other buttons are dimmed until a blend is selected. The Blend panel's Create Spine menu (Auto, Always, Never; `object.blend.createSpine`) sets whether new blends get a spine, from the Blend tool, Make and `object.blend.make` alike ([Blend panel overview](https://helpx.adobe.com/illustrator/desktop/manage-colors/apply-transparency-and-blending/blend-panel-overview.html)). Auto and Always give every new blend a straight spine, as before; Never makes blends without one, which keep their steps on the straight lines between the keys, have Type and Ramp dimmed, refuse Reverse Spine and spine edits, release without a spine path, and get a spine from Replace Spine. The setting is kept with the preferences and leaves blends already made as they are; Auto as the starting value and keeping the setting across sessions are VectorCraft's own rules. Builds on #5177 by adambelis. Evidence: `tests::blendopts::{create_spine_sets_whether_new_blends_get_a_spine, a_blend_without_a_spine_has_none_to_edit_reverse_ease_or_release, whether_a_blend_has_a_spine_is_saved_and_old_blends_have_one}` and `panels::tests_blend::create_spine_sets_what_new_blends_get_whatever_is_selected`. With the Blend tool, Escape drops the object clicked first and stops the next click adding to the blend just made, so the next click picks a new first object; the document and the selection stay as they are. Regression tests: `escape_drops_the_blend_tools_first_pick` in `meshblend.rs` and `tests/blendopts.rs`. - Objects on Path (#2545): a click with the Objects on Path tool (in Edit Toolbar's Modify section and the Blend tool's flyout; Object › Objects on Path › Attach and the context menu with two or more objects selected choose it) attaches the selected objects to the path clicked, open or closed, in an Objects on Path group with the path at its bottom. The objects go along the path column by column from left to right, spread evenly from its first anchor (the last one sits on the end of an open path), each turned to the path at its attachment point; Pivot and Rotate set where each one attaches and how far it tilts (`object.objectsOnPath.attach`, `object.objectsOnPath.options`), and with Objects on Path groups selected the Properties panel's Objects on Path Options section shows them as a nine-point Pivot grid and a Rotate field with common angles, each change one undo step for every selected group. Editing the path moves the objects, an object moved on its own goes back, objects moved into or out of the group in the Layers panel join or leave the path, and Object › Objects on Path › Expand leaves a plain group. The group saves and reopens live and exports as plain art. With one Objects on Path group selected, the Selection and Objects on Path tools show the Rotate All widget beside its first object: dragging it turns every object about its attachment point by the angle the pointer turns, adding it to the group's Rotate in one undo step, in 15° steps with Shift ([Objects on Path](https://helpx.adobe.com/illustrator/using/objects-on-path.html)). Detach is missing. + Objects on Path (#2545): a click with the Objects on Path tool (in Edit Toolbar's Modify section and the Blend tool's flyout; Object › Objects on Path › Attach and the context menu with two or more objects selected choose it) attaches the selected objects to the path clicked, open or closed, in an Objects on Path group with the path at its bottom. The objects go along the path column by column from left to right, spread evenly from its first anchor (the last one sits on the end of an open path), each turned to the path at its attachment point; Pivot and Rotate set where each one attaches and how far it tilts (`object.objectsOnPath.attach`, `object.objectsOnPath.options`), and with Objects on Path groups selected the Properties panel's Objects on Path Options section shows them as a nine-point Pivot grid and a Rotate field with common angles, each change one undo step for every selected group. Editing the path moves the objects, an object moved on its own goes back, objects moved into or out of the group in the Layers panel join or leave the path, and Object › Objects on Path › Expand leaves a plain group. The group saves and reopens live and exports as plain art. With one Objects on Path group selected, the Selection and Objects on Path tools show the Rotate All widget beside its first object: dragging it turns every object about its attachment point by the angle the pointer turns, adding it to the group's Rotate in one undo step, in 15° steps with Shift ([Objects on Path](https://helpx.adobe.com/illustrator/using/objects-on-path.html)). Detach is in its own entry below. Objects on Path Space widget: with one Objects on Path group of two or more objects selected, the Selection and Objects on Path tools show a square handle beyond its last object; dragging it along the path, round its bends, sets the distance between the objects' attachment points in one undo step, the first object staying where it is, and with Alt (Option on macOS) the objects spread from both ends toward the center; the distance shows next to the pointer during the drag, in the document's units ([Rearrange and edit objects on a path](https://helpx.adobe.com/illustrator/desktop/manage-objects/arrange-objects/rearrange-and-edit-objects-on-a-path.html), [Objects on Path](https://helpx.adobe.com/illustrator/using/objects-on-path.html)). The group keeps the distance (`object.objectsOnPath.options {spacing, start}`) through Pivot and Rotate changes, path edits, copies, saving and reopening, and scales it with the group; by default (`"auto"`) the objects are spread evenly as before. Evidence: `objects_on_path::tests::{space_sets_the_distance_between_the_stops, space_on_a_closed_path_wraps_at_the_first_anchor_and_stops_short_of_the_first_object}` in `crates/doc`, `tests::objects_on_path::space_sets_the_distance_between_the_objects_in_one_undo_step` in `crates/engine`, `objects_on_path::tests::dragging_the_space_widget_spaces_the_objects_from_the_first_one` in `crates/tools`. Objects on Path Move All widget: with one Objects on Path group selected whose objects have room to move, the Selection and Objects on Path tools show a diamond below its first object; dragging it along the path, round its bends, moves every object along the path together in one undo step, keeping the distances between them (`object.objectsOnPath.options {start}`). On an open path the widget shows only when the distance from the first object to the last is shorter than the path, so not for objects spread evenly from end to end, and the objects stop at the path's ends; on a closed path they go on round across its first anchor. The first object's distance along the path shows next to the pointer during the drag, in the document's units ([Rearrange and edit objects on a path](https://helpx.adobe.com/illustrator/desktop/manage-objects/arrange-objects/rearrange-and-edit-objects-on-a-path.html), [Objects on Path](https://helpx.adobe.com/illustrator/using/objects-on-path.html)). Evidence: `objects_on_path::tests::{dragging_the_move_all_widget_moves_every_object_along_the_path, a_move_all_drag_on_a_closed_path_goes_round_across_the_first_anchor}` in `crates/tools`, `canvas::tests::a_selected_objects_on_path_group_shows_its_move_all_widget_when_it_has_room` in `crates/ui-egui`. Objects on Path Select/Move widget: with one Objects on Path group selected, the Selection and Objects on Path tools show a small ring on each object's attachment point, highlighted under the pointer. A click on one chooses that object and fills its ring blue while the group stays selected (`object.objectsOnPath.selectMember`); a click on the object's art still selects the group. Dragging a ring along the path moves its object to the object's place nearest the pointer, and the objects between move one place toward where it was, in one undo step (`object.objectsOnPath.moveMember`); hidden and locked objects keep their places; only the order along the path changes, not the Layers panel's order or the Space. On an open path the drag stops at the ends, on a closed path it goes on round across the first anchor, and Escape cancels it ([Rearrange and edit objects on a path](https://helpx.adobe.com/illustrator/desktop/manage-objects/arrange-objects/rearrange-and-edit-objects-on-a-path.html), [Objects on Path](https://helpx.adobe.com/illustrator/using/objects-on-path.html)). Evidence: `objects_on_path::tests::moving_an_object_shifts_the_ones_between_it_and_its_new_place` in `crates/doc`, `tests::objects_on_path::move_member_takes_the_target_s_place_and_shifts_the_objects_between` in `crates/engine`, `objects_on_path::tests::{dragging_a_select_move_widget_moves_its_object_to_the_nearest_place, a_select_move_drag_goes_round_a_closed_path_across_its_first_anchor}` in `crates/tools`, `canvas::tests::a_selected_objects_on_path_group_shows_a_select_move_widget_per_object` in `crates/ui-egui`. + Objects on Path Detach: with one Objects on Path group selected and an object chosen with its Select/Move widget, Detach in the Properties panel's Objects on Path Options section takes that one object off the path (`object.objectsOnPath.detach`), and the objects after it move up the path in their order: with Auto Space they spread evenly again, the ends of an open path staying occupied while two or more are left, and a Space value is kept ([Objects on Path](https://helpx.adobe.com/illustrator/using/objects-on-path.html), [Rearrange and edit objects on a path](https://helpx.adobe.com/illustrator/desktop/manage-objects/arrange-objects/rearrange-and-edit-objects-on-a-path.html)). Detach is dimmed until an object is chosen. VectorCraft's own rules: the object stays where it is drawn, goes just above the group in the Layers panel and is selected alone; the start is kept; the group and its path stay with one object or none left; a hidden or locked object or group is not detached; and Detach is one undo step. Several objects still leave together by dragging their rows out of the group in the Layers panel. Evidence: `tests::objects_on_path::{detach_takes_the_chosen_object_off_the_path_and_the_others_move_up_in_one_undo_step, after_the_first_object_is_detached_the_second_takes_the_start, detach_on_a_closed_path_spreads_the_others_all_the_way_round_again, detach_keeps_a_space_value_and_the_start, the_group_and_its_path_stay_with_one_object_or_none_left, an_object_detached_from_a_group_on_another_path_does_not_join_that_path, detach_refuses_what_is_not_one_chosen_editable_object_on_a_path}` in `crates/engine`, `panels::properties::tests::detach_takes_the_chosen_object_off_the_path` in `crates/ui-egui`. Double-clicking the Objects on Path tool opens Objects on Path Tool Options ([Objects on Path](https://helpx.adobe.com/illustrator/using/objects-on-path.html)): the nine-point Pivot grid and the Rotate field set the attachment point and angle of the objects attached next, and groups already attached keep theirs; Reset puts the center and 0° back. Save keeps the options across tool switches and saves them with the preferences (`tool.setOption {tool: "objectsOnPath"}`). - **Paint and appearance (M3):** - **CSS swatch libraries:** fractional linear and radial gradient stop positions are preserved, so nearby stops such as 12.5% and 12.890625% remain distinct instead of collapsing to 13%. Reference: [CSS Images 3 color stops](https://www.w3.org/TR/css-images-3/#color-stop-syntax). The original synthetic regression is `palette_io::tests::css_preserves_fractional_gradient_stop_positions` in `crates/color/src/palette_io.rs`. Scores and hours are unchanged.