mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 05:37:23 -06:00
Objects on Path: double-clicking the tool opens its options
Double-clicking the Objects on Path tool opens Objects on Path Tool Options: a nine-point Pivot grid, a Rotate field, Reset (the center and 0) and Save. Save sets the tool's options, which last across tool switches and are saved with the preferences. The tool sends them with each attach, and object.objectsOnPath.attach without pivot or rotate uses them, so they apply to the objects attached next; groups attached before keep theirs.
This commit is contained in:
@@ -5,8 +5,9 @@
|
||||
//! bottom. Called without a path (Object › Objects on Path › Attach) it chooses the Objects on Path
|
||||
//! tool, whose click on a path attaches the selected objects to that path. The objects go along the
|
||||
//! path column by column from left to right, top to bottom in each column, spread evenly from its
|
||||
//! first anchor and turned to follow it. `object.objectsOnPath.options` sets the group's Pivot and
|
||||
//! Rotate, and `object.objectsOnPath.expand` makes it a plain group.
|
||||
//! first anchor and turned to follow it. Pivot and Rotate come from the command's params, otherwise
|
||||
//! from the tool's options (Objects on Path Tool Options). `object.objectsOnPath.options` sets the
|
||||
//! group's Pivot and Rotate, and `object.objectsOnPath.expand` makes it a plain group.
|
||||
//!
|
||||
//! 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,
|
||||
@@ -38,7 +39,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Attach",
|
||||
["Object", "Objects on Path"],
|
||||
None,
|
||||
"{path?: id of the path to attach to (without it: choose the Objects on Path tool, whose click on a path attaches the selection to it), ids?: the objects to attach (default: the selection; the path is left out of it), pivot?: 0-8 (the reference point of each object that attaches to the path, row by row from the top left; default 4, the center), rotate?: degrees counter-clockwise from upright on the path (default 0)} put the objects and the path in an Objects on Path group: the path at the bottom, the objects spread evenly along it from its first anchor (the last one at the end of an open path) in their order on the canvas, column by column from left to right and top to bottom in each column, each turned to follow the path; editing the path moves them → {id}",
|
||||
"{path?: id of the path to attach to (without it: choose the Objects on Path tool, whose click on a path attaches the selection to it), ids?: the objects to attach (default: the selection; the path is left out of it), pivot?: 0-8 (the reference point of each object that attaches to the path, row by row from the top left; default: the Objects on Path tool's Pivot, 4, the center, until its options change it), rotate?: degrees counter-clockwise from upright on the path (default: the tool's Rotate, 0 until its options change it)} put the objects and the path in an Objects on Path group: the path at the bottom, the objects spread evenly along it from its first anchor (the last one at the end of an open path) in their order on the canvas, column by column from left to right and top to bottom in each column, each turned to follow the path; editing the path moves them → {id}",
|
||||
has_selection,
|
||||
attach
|
||||
),
|
||||
@@ -200,7 +201,11 @@ fn attach(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
return Ok(json!({ "tool": TOOL }));
|
||||
};
|
||||
let path = checked_id(s, path, C)?;
|
||||
let (pivot, rotate) = pivot_rotate(p, C, CENTER, 0.0)?;
|
||||
// Without them in `p`, the tool's options (Objects on Path Tool Options) give Pivot and Rotate.
|
||||
let saved = s.tool_options_of(TOOL);
|
||||
let tool_pivot = saved.get("pivot").and_then(Value::as_u64).filter(|n| *n <= 8).map_or(CENTER, |n| n as u8);
|
||||
let tool_rotate = saved.get("rotate").and_then(Value::as_f64).filter(|r| r.is_finite()).unwrap_or(0.0);
|
||||
let (pivot, rotate) = pivot_rotate(p, C, tool_pivot, tool_rotate)?;
|
||||
let roots = match p.get("ids") {
|
||||
Some(_) => roots_of(&s.doc()?.doc, checked_ids_param(s, p, "ids", C)?),
|
||||
None => selected_roots(s)?,
|
||||
|
||||
@@ -186,6 +186,34 @@ fn pivot_picks_the_point_that_sits_on_the_path() {
|
||||
assert!((b.y0 - 300.0).abs() < 1e-6, "hanging from the line by their top edge: {b:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_tool_options_set_pivot_and_rotate_for_the_objects_attached_next_only() {
|
||||
let mut s = session();
|
||||
let ([left, middle, right], path) = squares_and_line(&mut s);
|
||||
let other = line(&mut s, (50.0, 380.0), (350.0, 380.0));
|
||||
let third = line(&mut s, (50.0, 200.0), (350.0, 200.0));
|
||||
let before = attach(&mut s, path, &[left], json!({}));
|
||||
let rec = |s: &Session, g: NodeId| {
|
||||
let r = s.doc().unwrap().doc.node(g).unwrap().extras().objects_on_path.as_deref().map(|r| (r.pivot, r.rotate));
|
||||
r.unwrap()
|
||||
};
|
||||
assert_eq!(rec(&s, before), (4, 0.0), "the center, upright");
|
||||
// Objects on Path Tool Options' Save (`tool.setOption`): the tool keeps them while another tool
|
||||
// is chosen.
|
||||
let values = |v: Value| v.as_object().cloned().unwrap_or_default();
|
||||
s.set_tool_options(Some("objectsOnPath"), &values(json!({"pivot": 7, "rotate": 30})));
|
||||
assert_eq!(s.tool_options_of("objectsOnPath"), json!({"pivot": 7, "rotate": 30.0}));
|
||||
let next = attach(&mut s, other, &[middle], json!({}));
|
||||
assert_eq!(rec(&s, next), (7, 30.0), "the objects attached next take them");
|
||||
assert_eq!(rec(&s, before), (4, 0.0), "the ones already attached keep theirs");
|
||||
// Values given to the command win over the tool's.
|
||||
let given = attach(&mut s, third, &[right], json!({"pivot": 1, "rotate": 0}));
|
||||
assert_eq!(rec(&s, given), (1, 0.0));
|
||||
// Reset puts the center and 0 back.
|
||||
s.set_tool_options(Some("objectsOnPath"), &values(json!({"pivot": 4, "rotate": 0})));
|
||||
assert_eq!(s.tool_options_of("objectsOnPath"), json!({"pivot": 4, "rotate": 0.0}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_closed_path_spreads_the_objects_all_the_way_round_from_its_first_anchor() {
|
||||
let mut s = session();
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
//! The Objects on Path tool (#2545): a click on a path attaches the selected objects to it
|
||||
//! (`object.objectsOnPath.attach`). A path they can attach to is highlighted under the pointer,
|
||||
//! and the pointer shows [`Cursor::ObjectsOnPath`] over it.
|
||||
//! and the pointer shows [`Cursor::ObjectsOnPath`] over it. The tool's options, `pivot` (0-8, row
|
||||
//! by row from the top left; 4, the center) and `rotate` (degrees), go with each attach, so they
|
||||
//! set how the objects attached next attach; groups attached before keep theirs.
|
||||
//!
|
||||
//! Behavior: <https://helpx.adobe.com/illustrator/using/objects-on-path.html>.
|
||||
|
||||
use serde_json::json;
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::NodeId;
|
||||
use vectorcraft_doc::objects_on_path::{attachable_path, is_objects_on_path};
|
||||
use vectorcraft_doc::objects_on_path::{CENTER, attachable_path, is_objects_on_path};
|
||||
use vectorcraft_geom::{BezPath, Point};
|
||||
|
||||
use crate::{Action, Cursor, Mods, Overlay, PointerEvent, PointerKind, Tool, ToolContext, ToolKey};
|
||||
@@ -31,12 +33,21 @@ pub fn target(cx: &ToolContext, p: Point) -> Option<(NodeId, BezPath)> {
|
||||
cx.selection.objects.iter().any(|id| *id != h.leaf).then(|| (h.leaf, path.to_bezpath()))
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ObjectsOnPathTool {
|
||||
/// The path under the pointer the selection can attach to, and its outline.
|
||||
hover: Option<(NodeId, BezPath)>,
|
||||
/// Where the press was, while the button is down.
|
||||
press: Option<Point>,
|
||||
/// The reference point of each object attached next (0-8, row by row from the top left).
|
||||
pivot: u8,
|
||||
/// The angle of each object attached next, in degrees counter-clockwise from upright on the path.
|
||||
rotate: f64,
|
||||
}
|
||||
|
||||
impl Default for ObjectsOnPathTool {
|
||||
fn default() -> Self {
|
||||
Self { hover: None, press: None, pivot: CENTER, rotate: 0.0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Tool for ObjectsOnPathTool {
|
||||
@@ -60,7 +71,7 @@ impl Tool for ObjectsOnPathTool {
|
||||
let Some(start) = self.press.take().filter(|q| q.distance(p) < cx.tol(3.0)) else { return vec![] };
|
||||
let Some((path, _)) = target(cx, start) else { return vec![] };
|
||||
self.hover = None;
|
||||
vec![Action::Exec("object.objectsOnPath.attach".into(), json!({ "path": path.0 }))]
|
||||
vec![Action::Exec("object.objectsOnPath.attach".into(), json!({ "path": path.0, "pivot": self.pivot, "rotate": self.rotate }))]
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
@@ -83,6 +94,28 @@ impl Tool for ObjectsOnPathTool {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn options(&self) -> Value {
|
||||
json!({ "pivot": self.pivot, "rotate": self.rotate })
|
||||
}
|
||||
|
||||
/// `pivot` takes a whole number from 0 to 8 and `rotate` a finite number; other values are
|
||||
/// ignored.
|
||||
fn set_option(&mut self, key: &str, value: &Value) {
|
||||
match key {
|
||||
"pivot" => {
|
||||
if let Some(n) = value.as_u64().filter(|n| *n <= 8) {
|
||||
self.pivot = n as u8;
|
||||
}
|
||||
}
|
||||
"rotate" => {
|
||||
if let Some(r) = value.as_f64().filter(|r| r.is_finite()) {
|
||||
self.rotate = r;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn busy(&self) -> bool {
|
||||
self.press.is_some()
|
||||
}
|
||||
@@ -132,6 +165,26 @@ mod tests {
|
||||
assert!(matches!(acts.as_slice(), [Action::Exec(id, p)] if id == "object.objectsOnPath.attach" && p["path"] == line.0), "{acts:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_click_sends_the_tool_options_pivot_and_rotate() {
|
||||
let (d, _, square) = doc();
|
||||
let mut sel = Selection::default();
|
||||
sel.set([square]);
|
||||
let paint = paint();
|
||||
let cx = cx(&d, &sel, &paint);
|
||||
let mut t = ObjectsOnPathTool::default();
|
||||
assert_eq!(t.options(), json!({ "pivot": 4, "rotate": 0.0 }), "the center, upright");
|
||||
t.set_option("pivot", &json!(0));
|
||||
t.set_option("rotate", &json!(45.5));
|
||||
// Values out of range are ignored.
|
||||
t.set_option("pivot", &json!(9));
|
||||
t.set_option("rotate", &json!("x"));
|
||||
assert_eq!(t.options(), json!({ "pivot": 0, "rotate": 45.5 }));
|
||||
t.pointer(&cx, &ev(PointerKind::Down, 200.0, 300.0));
|
||||
let acts = t.pointer(&cx, &ev(PointerKind::Up, 200.0, 300.0));
|
||||
assert!(matches!(acts.as_slice(), [Action::Exec(_, p)] if p["pivot"] == 0 && p["rotate"] == 45.5), "{acts:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_during_the_press_cancels_the_click() {
|
||||
let (d, _, square) = doc();
|
||||
|
||||
@@ -65,6 +65,8 @@ const ROWS: &[Row] = &[
|
||||
"highlightColor",
|
||||
],
|
||||
),
|
||||
// Objects on Path Tool Options: the Pivot and Rotate of the objects attached next.
|
||||
(&["objectsOnPath"], None, &["pivot", "rotate"]),
|
||||
(&["artboard"], None, &["moveArt", "scaleArt"]),
|
||||
// The add-ons' tools (`crate::addons`).
|
||||
(&[crate::addons::anchor_eraser::ID], None, &["size", "tolerance", "pressure"]),
|
||||
|
||||
@@ -66,6 +66,7 @@ pub mod new_color_group;
|
||||
mod new_document;
|
||||
pub mod new_swatch;
|
||||
pub mod new_text_style;
|
||||
pub mod objects_on_path_options;
|
||||
pub mod office_export;
|
||||
pub mod package;
|
||||
pub(crate) mod path_ops;
|
||||
@@ -377,6 +378,7 @@ registry! {
|
||||
LiquifyOptions: [liquify::KIND] => liquify::SPEC,
|
||||
FreehandOptions: [freehand::KIND] => freehand::SPEC,
|
||||
ShapeBuilderOptions: [shape_builder_options::KIND] => shape_builder_options::SPEC,
|
||||
ObjectsOnPathOptions: [objects_on_path_options::KIND] => objects_on_path_options::SPEC,
|
||||
FlareOptions: [flare_options::KIND] => flare_options::SPEC,
|
||||
SymbolOptions: [symbol_options::KIND] => symbol_options::SPEC,
|
||||
DeleteSymbol: [symbol_delete::KIND] => symbol_delete::SPEC,
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
//! Objects on Path Tool Options (double-click the Objects on Path tool): the Pivot and Rotate of
|
||||
//! the objects the tool attaches next. Groups attached before keep theirs. Save sets them with
|
||||
//! `tool.setOption {tool: "objectsOnPath", values}`; they last across tool switches and are saved
|
||||
//! with the preferences. Reset puts the center and 0 back.
|
||||
//!
|
||||
//! Fields: `pivot` (0-8, row by row from the top left; 4, the center) and `rotate` (degrees
|
||||
//! counter-clockwise from upright on the path).
|
||||
//!
|
||||
//! Behavior: <https://helpx.adobe.com/illustrator/using/objects-on-path.html>.
|
||||
|
||||
use serde_json::{Map, Value, json};
|
||||
use vectorcraft_doc::objects_on_path::CENTER;
|
||||
|
||||
use super::{DialogSpec, run_and_close};
|
||||
use crate::state::Dialog;
|
||||
use crate::{VectorcraftApp, widgets};
|
||||
|
||||
/// The dialog kind of Objects on Path Tool Options.
|
||||
pub const KIND: &str = "objectsOnPathOptions";
|
||||
|
||||
/// The tool whose options the dialog edits.
|
||||
const TOOL: &str = "objectsOnPath";
|
||||
|
||||
pub(super) const SPEC: DialogSpec = DialogSpec { heading, body, confirm, ok: Some("Save"), reset: Some(reset), min_width: 300.0, ..DialogSpec::FORM };
|
||||
|
||||
/// Width of the label column.
|
||||
const LABEL_W: f32 = 70.0;
|
||||
|
||||
fn heading(_: &Dialog) -> String {
|
||||
let label = vectorcraft_tools::tool_info(TOOL).map_or("", |t| t.label);
|
||||
crate::i18n::fmt(tl!("{tool} Options"), &[("tool", tl!(label))])
|
||||
}
|
||||
|
||||
/// Open the tool's options with their current values.
|
||||
pub fn open(app: &mut VectorcraftApp) -> Value {
|
||||
let o = app.session.tool_options_of(TOOL);
|
||||
let fields: Map<String, Value> = ["pivot", "rotate"].iter().filter_map(|key| Some((key.to_string(), o.get(*key)?.clone()))).collect();
|
||||
app.ui.dialog = Some(Dialog { kind: KIND.into(), fields });
|
||||
json!({ "dialog": KIND })
|
||||
}
|
||||
|
||||
/// Reset: the center and 0.
|
||||
fn reset(_: &VectorcraftApp, d: &mut Dialog) {
|
||||
d.fields.insert("pivot".into(), json!(CENTER));
|
||||
d.fields.insert("rotate".into(), json!(0.0));
|
||||
}
|
||||
|
||||
fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
widgets::label_row(ui, tl!("Pivot:"), LABEL_W, |ui| {
|
||||
let current = d.f64("pivot", f64::from(CENTER)).clamp(0.0, 8.0) as usize;
|
||||
if let Some(i) = widgets::reference_point(ui, current) {
|
||||
d.fields.insert("pivot".into(), json!(i));
|
||||
}
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
widgets::label_row(ui, tl!("Rotate:"), LABEL_W, |ui| {
|
||||
if let Some(r) = widgets::range_field(ui, "objects-on-path-rotate", d.f64("rotate", 0.0), -360.0..=360.0, "°", 1, 64.0) {
|
||||
d.fields.insert("rotate".into(), json!(r));
|
||||
}
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
let r = widgets::flat_button(ui, tl!("Reset"), 64.0);
|
||||
r.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), tl!("Reset")));
|
||||
if r.clicked() {
|
||||
reset(app, d);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Save: set the tool's Pivot and Rotate.
|
||||
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
let values: Map<String, Value> = ["pivot", "rotate"].iter().filter_map(|k| Some((k.to_string(), d.fields.get(*k)?.clone()))).collect();
|
||||
run_and_close(app, "tool.setOption", json!({ "tool": TOOL, "values": values }))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use vectorcraft_engine::Session;
|
||||
|
||||
fn app() -> VectorcraftApp {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 400, "height": 400})).unwrap();
|
||||
app
|
||||
}
|
||||
|
||||
/// The labels the open dialog paints.
|
||||
fn painted(app: &mut VectorcraftApp) -> String {
|
||||
crate::tests_labels::painted_text(app, |app, ui| super::super::show(app, ui.ctx()))
|
||||
}
|
||||
|
||||
/// The Pivot and Rotate of the Objects on Path group `g`.
|
||||
fn record(app: &VectorcraftApp, g: &Value) -> (u8, f64) {
|
||||
let id = vectorcraft_doc::NodeId(g["id"].as_u64().unwrap());
|
||||
let n = app.session.doc().unwrap().doc.node(id).unwrap();
|
||||
n.extras().objects_on_path.as_deref().map(|r| (r.pivot, r.rotate)).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_clicking_the_tool_opens_its_options_and_save_sets_the_next_attach() {
|
||||
let mut app = app();
|
||||
let first = app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 20, "height": 20})).unwrap();
|
||||
let line = app.run("shape.line", json!({"x1": 50, "y1": 300, "x2": 350, "y2": 300})).unwrap();
|
||||
app.run("select.set", json!({"ids": [first["id"]]})).unwrap();
|
||||
let before = app.run("object.objectsOnPath.attach", json!({"path": line["id"]})).unwrap();
|
||||
let r = app.run("tool.options", json!({"tool": "objectsOnPath"})).unwrap();
|
||||
assert_eq!(r["dialog"], json!(KIND));
|
||||
let text = painted(&mut app);
|
||||
for label in ["Objects on Path Tool Options", "Pivot:", "Rotate:", "Reset", "Save", "Cancel"] {
|
||||
assert!(text.contains(label), "{label} in {text}");
|
||||
}
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
assert_eq!((d.f64("pivot", -1.0), d.f64("rotate", -1.0)), (4.0, 0.0), "the center and 0");
|
||||
d.fields.insert("pivot".into(), json!(7));
|
||||
d.fields.insert("rotate".into(), json!(30));
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
assert!(app.ui.dialog.is_none());
|
||||
// They last while another tool is chosen, and the dialog opens on them again.
|
||||
app.select_tool("objectsOnPath");
|
||||
app.select_tool("selection");
|
||||
assert_eq!(app.session.tool_options_of(TOOL), json!({"pivot": 7, "rotate": 30.0}));
|
||||
// The objects attached next take them; the group attached before keeps its own.
|
||||
let second = app.run("shape.rectangle", json!({"x": 100, "y": 10, "width": 20, "height": 20})).unwrap();
|
||||
let other = app.run("shape.line", json!({"x1": 50, "y1": 380, "x2": 350, "y2": 380})).unwrap();
|
||||
app.run("select.set", json!({"ids": [second["id"]]})).unwrap();
|
||||
let next = app.run("object.objectsOnPath.attach", json!({"path": other["id"]})).unwrap();
|
||||
assert_eq!(record(&app, &next), (7, 30.0));
|
||||
assert_eq!(record(&app, &before), (4, 0.0));
|
||||
// Reset puts the center and 0 back, and Save keeps them.
|
||||
app.run("tool.options", json!({"tool": "objectsOnPath"})).unwrap();
|
||||
let d = app.ui.dialog.as_ref().unwrap();
|
||||
assert_eq!((d.f64("pivot", -1.0), d.f64("rotate", -1.0)), (7.0, 30.0));
|
||||
super::super::reset(&mut app).unwrap();
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
assert_eq!(app.session.tool_options_of(TOOL), json!({"pivot": 4, "rotate": 0.0}));
|
||||
}
|
||||
}
|
||||
@@ -1368,6 +1368,37 @@ fn artboard_options_x_and_y_count_from_the_ruler_origin_at_the_reference_point()
|
||||
assert!(has(&shown, "Width:") && !has(&shown, "X:") && !has(&shown, "Y:"), "{shown:?}");
|
||||
}
|
||||
|
||||
/// Objects on Path Tool Options: a click on one of the nine Pivot points and a typed Rotate set the
|
||||
/// fields, Enter saves them as the tool's options, and the Reset button puts the center and 0 back.
|
||||
#[test]
|
||||
fn objects_on_path_tool_options_pivot_grid_rotate_field_and_reset() {
|
||||
let mut app = app();
|
||||
app.run("tool.options", json!({"tool": "objectsOnPath"})).unwrap();
|
||||
let mut f = Frames::new();
|
||||
let bottom = f.control(&mut app, "Bottom", egui::accesskit::Role::RadioButton);
|
||||
f.press(&mut app, bottom, true);
|
||||
f.press(&mut app, bottom, false);
|
||||
assert_eq!(app.ui.dialog.as_ref().unwrap().f64("pivot", -1.0), 7.0);
|
||||
// The field is right of the 70 pt label column.
|
||||
let shown = f.texts(&mut app);
|
||||
let label = shown.iter().find(|(t, _)| t == "Rotate:").unwrap_or_else(|| panic!("no Rotate: in {shown:?}")).1;
|
||||
let at = egui::pos2(label.left() + 70.0 + 30.0, label.center().y);
|
||||
f.press(&mut app, at, true);
|
||||
f.press(&mut app, at, false);
|
||||
f.run(&mut app, vec![egui::Event::Text("45".into())]);
|
||||
f.key(&mut app, egui::Key::Enter, egui::Modifiers::NONE);
|
||||
assert!(app.ui.dialog.is_none(), "Enter saves the dialog");
|
||||
assert_eq!(app.session.tool_options_of("objectsOnPath"), json!({"pivot": 7, "rotate": 45.0}));
|
||||
app.run("tool.options", json!({"tool": "objectsOnPath"})).unwrap();
|
||||
let reset = f.button(&mut app, "Reset");
|
||||
f.press(&mut app, reset, true);
|
||||
f.press(&mut app, reset, false);
|
||||
let d = app.ui.dialog.as_ref().expect("Reset keeps the dialog open");
|
||||
assert_eq!((d.f64("pivot", -1.0), d.f64("rotate", -1.0)), (4.0, 0.0));
|
||||
confirm(&mut app).unwrap();
|
||||
assert_eq!(app.session.tool_options_of("objectsOnPath"), json!({"pivot": 4, "rotate": 0.0}));
|
||||
}
|
||||
|
||||
/// #1876: a new Width or Height in Artboard Options keeps the reference point where it was, as the
|
||||
/// Transform panel's does: about the center by default, so X and Y still read the same, and from
|
||||
/// the top-left corner with that reference point.
|
||||
|
||||
@@ -1426,6 +1426,8 @@ font-technology Variable Променливи
|
||||
Attach Прикрепяне
|
||||
Objects on Path Options Опции за обекти по пътя
|
||||
Objects on Path Tool Инструмент „Обекти по пътя“
|
||||
Pivot: Опорна точка:
|
||||
Rotate: Завъртане:
|
||||
Attach Objects on Path Прикрепяне на обекти към пътя
|
||||
Expand Objects on Path Разширяване на обекти по пътя
|
||||
Release Live Paint Освобождаване на динамично оцветяване
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1610,6 +1610,8 @@ digits Malayalam Malajalam
|
||||
Attach Priloži
|
||||
Objects on Path Options Opcije objekata na putanji
|
||||
Objects on Path Tool Alat objekti na putanji
|
||||
Pivot: Tačka okretanja:
|
||||
Rotate: Rotacija:
|
||||
Attach Objects on Path Priloži objekte na putanju
|
||||
Expand Objects on Path Proširi objekte na putanji
|
||||
Release Live Paint Oslobodi živo bojenje
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2640,6 +2640,8 @@ digits Malayalam Malaiàlam
|
||||
Objects on Path Objectes sobre el traçat
|
||||
Objects on Path Options Opcions dels objectes sobre el traçat
|
||||
Objects on Path Tool Eina d’objectes sobre el traçat
|
||||
Pivot: Punt de pivot:
|
||||
Rotate: Rotació:
|
||||
Objects using them keep their look but are no longer linked. Els objectes que les utilitzen conserven l’aspecte, però ja no estan enllaçats.
|
||||
Ocean Ripple Onada oceànica
|
||||
Ocean Ripple… Onada oceànica…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1636,6 +1636,8 @@ digits Malayalam Malajálamské
|
||||
Attach Připojit
|
||||
Objects on Path Options Možnosti objektů na cestě
|
||||
Objects on Path Tool Nástroj Objekty na cestě
|
||||
Pivot: Otočný bod:
|
||||
Rotate: Otočení:
|
||||
Attach Objects on Path Připojit objekty na cestu
|
||||
Expand Objects on Path Rozložit objekty na cestě
|
||||
Release Live Paint Uvolnit živou malbu
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5396,6 +5396,8 @@ swatch Bright Violet Kraftig violet
|
||||
Attach Tilknyt
|
||||
Objects on Path Options Indstillinger for objekter på kurve
|
||||
Objects on Path Tool Objekter på kurve-værktøj
|
||||
Pivot: Omdrejningspunkt:
|
||||
Rotate: Rotation:
|
||||
Attach Objects on Path Tilknyt objekter til kurve
|
||||
Expand Objects on Path Udvid objekter på kurve
|
||||
Release Live Paint Frigør live-maling
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1460,6 +1460,8 @@ file-filter Templates Vorlagen
|
||||
Attach Anhängen
|
||||
Objects on Path Options Optionen für Objekte auf Pfad
|
||||
Objects on Path Tool Objekte-auf-Pfad-Werkzeug
|
||||
Pivot: Drehpunkt:
|
||||
Rotate: Drehung:
|
||||
Attach Objects on Path Objekte an Pfad anhängen
|
||||
Expand Objects on Path Objekte auf Pfad umwandeln
|
||||
Release Live Paint „Interaktiv malen“ zurückwandeln
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1390,6 +1390,8 @@ font-technology Variable Μεταβλητές
|
||||
Attach Προσάρτηση
|
||||
Objects on Path Options Επιλογές αντικειμένων σε διαδρομή
|
||||
Objects on Path Tool Αντικείμενα σε διαδρομή (Objects on Path Tool)
|
||||
Pivot: Σημείο περιστροφής:
|
||||
Rotate: Περιστροφή:
|
||||
Attach Objects on Path Προσάρτηση αντικειμένων σε διαδρομή
|
||||
Expand Objects on Path Επέκταση αντικειμένων σε διαδρομή
|
||||
Release Live Paint Αποδέσμευση δυναμικής βαφής
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 164 and column 5.
|
@@ -1464,6 +1464,8 @@ font-technology Variable Variables
|
||||
Attach Adjuntar
|
||||
Objects on Path Options Opciones de objetos en trazado
|
||||
Objects on Path Tool Herramienta Objetos en trazado
|
||||
Pivot: Pivote:
|
||||
Rotate: Rotación:
|
||||
Attach Objects on Path Adjuntar objetos al trazado
|
||||
Expand Objects on Path Expandir objetos en trazado
|
||||
Release Live Paint Soltar pintura interactiva
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1609,6 +1609,8 @@ digits Malayalam Malajalam
|
||||
Attach Kiinnitä
|
||||
Objects on Path Options Objektit polulla -asetukset
|
||||
Objects on Path Tool Objektit polulla -työkalu
|
||||
Pivot: Kiertopiste:
|
||||
Rotate: Kierto:
|
||||
Attach Objects on Path Kiinnitä objektit polulle
|
||||
Expand Objects on Path Laajenna objektit polulla
|
||||
Release Live Paint Vapauta elävä maalaus
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1464,6 +1464,8 @@ font-technology Variable Variables
|
||||
Attach Attacher
|
||||
Objects on Path Options Options d’objets sur tracé
|
||||
Objects on Path Tool Outil Objets sur tracé
|
||||
Pivot: Pivot :
|
||||
Rotate: Rotation :
|
||||
Attach Objects on Path Attacher des objets au tracé
|
||||
Expand Objects on Path Décomposer les objets sur tracé
|
||||
Release Live Paint Annuler la peinture dynamique
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1861,6 +1861,8 @@ file-filter Templates Sablonok
|
||||
Attach Csatolás
|
||||
Objects on Path Options Objektumok útvonalon beállításai
|
||||
Objects on Path Tool Objektumok útvonalon
|
||||
Pivot: Forgáspont:
|
||||
Rotate: Elforgatás:
|
||||
Attach Objects on Path Objektumok csatolása útvonalhoz
|
||||
Expand Objects on Path Objektumok útvonalon kibontása
|
||||
Release Live Paint Élő festés megszüntetése
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5396,6 +5396,8 @@ swatch Bright Violet Violet cerah
|
||||
Attach Tempelkan
|
||||
Objects on Path Options Opsi objek pada jalur
|
||||
Objects on Path Tool Alat objek pada jalur
|
||||
Pivot: Titik poros:
|
||||
Rotate: Rotasi:
|
||||
Attach Objects on Path Tempelkan objek pada jalur
|
||||
Expand Objects on Path Perluas objek pada jalur
|
||||
Release Live Paint Lepaskan Live Paint
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2185 and column 5.
|
@@ -1484,6 +1484,8 @@ file-filter Templates Modelli
|
||||
Attach Aggancia
|
||||
Objects on Path Options Opzioni oggetti su tracciato
|
||||
Objects on Path Tool Strumento Oggetti su tracciato
|
||||
Pivot: Perno:
|
||||
Rotate: Rotazione:
|
||||
Attach Objects on Path Aggancia oggetti al tracciato
|
||||
Expand Objects on Path Espandi oggetti su tracciato
|
||||
Release Live Paint Rilascia pittura dinamica
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1774,6 +1774,8 @@ digits Malayalam マラヤーラム数字
|
||||
Attach アタッチ
|
||||
Objects on Path Options パス上のオブジェクトオプション
|
||||
Objects on Path Tool パス上のオブジェクトツール
|
||||
Pivot: ピボット:
|
||||
Rotate: 回転:
|
||||
Attach Objects on Path パス上のオブジェクトをアタッチ
|
||||
Expand Objects on Path パス上のオブジェクトを拡張
|
||||
Release Live Paint ライブペイントを解除
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5302,6 +5302,8 @@ imageTrace Outline კონტური
|
||||
Attach მიმაგრება
|
||||
Objects on Path Options ტრაექტორიაზე ობიექტების პარამეტრები
|
||||
Objects on Path Tool ტრაექტორიაზე ობიექტების ხელსაწყო
|
||||
Pivot: საყრდენი წერტილი:
|
||||
Rotate: შემობრუნება:
|
||||
Attach Objects on Path ობიექტების ტრაექტორიაზე მიმაგრება
|
||||
Expand Objects on Path ტრაექტორიაზე ობიექტების გაშლა
|
||||
Release Live Paint ცოცხალი შეღებვის მოხსნა
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1764,6 +1764,8 @@ digits Malayalam 말라얄람어
|
||||
Attach 부착
|
||||
Objects on Path Options 패스 위 개체 옵션
|
||||
Objects on Path Tool 패스 위 개체 도구
|
||||
Pivot: 피벗:
|
||||
Rotate: 회전:
|
||||
Attach Objects on Path 패스 위 개체 부착
|
||||
Expand Objects on Path 패스 위 개체 확장
|
||||
Release Live Paint 라이브 페인트 해제
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5407,6 +5407,8 @@ swatch Bright Violet Felviolet
|
||||
Attach Koppelen
|
||||
Objects on Path Options Opties voor objecten op pad
|
||||
Objects on Path Tool Objecten-op-pad-gereedschap
|
||||
Pivot: Draaipunt:
|
||||
Rotate: Rotatie:
|
||||
Attach Objects on Path Objecten aan pad koppelen
|
||||
Expand Objects on Path Objecten op pad uitbreiden
|
||||
Release Live Paint Live Paint vrijgeven
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1554,6 +1554,8 @@ digits Malayalam Malajalam
|
||||
Attach Dołącz
|
||||
Objects on Path Options Opcje obiektów na ścieżce
|
||||
Objects on Path Tool Narzędzie Obiekty na ścieżce
|
||||
Pivot: Punkt obrotu:
|
||||
Rotate: Obrót:
|
||||
Attach Objects on Path Dołącz obiekty do ścieżki
|
||||
Expand Objects on Path Rozszerz obiekty na ścieżce
|
||||
Release Live Paint Uwolnij aktywne malowanie
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1270,6 +1270,8 @@
|
||||
Attach Anexar
|
||||
Objects on Path Options Opções de Objetos no Caminho
|
||||
Objects on Path Tool Ferramenta Objetos no Caminho
|
||||
Pivot: Pivô:
|
||||
Rotate: Rotação:
|
||||
Attach Objects on Path Anexar Objetos ao Caminho
|
||||
Expand Objects on Path Expandir Objetos no Caminho
|
||||
Release Live Paint Liberar Pintura ao Vivo
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1482,6 +1482,8 @@ file-filter Templates Шаблоны
|
||||
Attach Прикрепить
|
||||
Objects on Path Options Параметры объектов на контуре
|
||||
Objects on Path Tool Инструмент «Объекты на контуре»
|
||||
Pivot: Опорная точка:
|
||||
Rotate: Поворот:
|
||||
Attach Objects on Path Прикрепить объекты к контуру
|
||||
Expand Objects on Path Разобрать объекты на контуре
|
||||
Release Live Paint Освободить быструю заливку
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1428,6 +1428,8 @@ font-technology Variable Variabla
|
||||
Attach Fäst
|
||||
Objects on Path Options Alternativ för objekt på bana
|
||||
Objects on Path Tool Verktyg för objekt på bana
|
||||
Pivot: Pivotpunkt:
|
||||
Rotate: Rotation:
|
||||
Attach Objects on Path Fäst objekt på bana
|
||||
Expand Objects on Path Expandera objekt på bana
|
||||
Release Live Paint Släpp direktmålning
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -5375,6 +5375,8 @@ theme Appearance Görünüm
|
||||
Objects on Path Yol Üzerindeki Nesneler
|
||||
Objects on Path Options Yol Üzerindeki Nesneler Seçenekleri
|
||||
Objects on Path Tool Yol Üzerindeki Nesneler Aracı
|
||||
Pivot: Pivot noktası:
|
||||
Rotate: Döndürme:
|
||||
Photocopy Fotokopi
|
||||
Photocopy… Fotokopi…
|
||||
Plaster Alçı
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -1478,6 +1478,8 @@ file-filter Templates Шаблони
|
||||
Attach Прикріпити
|
||||
Objects on Path Options Параметри об’єктів на контурі
|
||||
Objects on Path Tool Інструмент «Об’єкти на контурі»
|
||||
Pivot: Опорна точка:
|
||||
Rotate: Поворот:
|
||||
Attach Objects on Path Прикріпити об’єкти до контуру
|
||||
Expand Objects on Path Розібрати об’єкти на контурі
|
||||
Release Live Paint Звільнити швидку заливку
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 178 and column 5.
|
@@ -1701,6 +1701,8 @@ digits Hindi Hindi
|
||||
Attach Gắn
|
||||
Objects on Path Options Tùy chọn đối tượng trên đường dẫn
|
||||
Objects on Path Tool Công cụ Đối tượng trên đường dẫn
|
||||
Pivot: Điểm trục:
|
||||
Rotate: Xoay:
|
||||
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
|
||||
Release Live Paint Giải phóng tô trực tiếp
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 200 and column 5.
|
@@ -1412,6 +1412,8 @@ file-filter Templates 模板
|
||||
Attach 附加
|
||||
Objects on Path Options 路径上的对象选项
|
||||
Objects on Path Tool 路径上的对象工具
|
||||
Pivot: 枢轴:
|
||||
Rotate: 旋转:
|
||||
Attach Objects on Path 附加路径上的对象
|
||||
Expand Objects on Path 扩展路径上的对象
|
||||
Release Live Paint 释放实时上色
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1449,6 +1449,8 @@ file-filter Templates 範本
|
||||
Attach 附加
|
||||
Objects on Path Options 路徑上的物件選項
|
||||
Objects on Path Tool 路徑上的物件工具
|
||||
Pivot: 樞軸:
|
||||
Rotate: 旋轉:
|
||||
Attach Objects on Path 附加路徑上的物件
|
||||
Expand Objects on Path 展開路徑上的物件
|
||||
Release Live Paint 釋放即時上色
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
File diff suppressed because one or more lines are too long
@@ -327,8 +327,8 @@ fn flyout_press(ui: &Ui, resp: &egui::Response, rect: egui::Rect) -> Option<Flyo
|
||||
/// Options; the Print Tiling tool's resets the print tiling. As in the reference app, the Hand
|
||||
/// tool's fits the artboard in the window, the Zoom tool's shows it at 100%, the Rotate, Scale,
|
||||
/// Reflect and Shear tools' are their Object › Transform dialogs, the selection tools' are the
|
||||
/// Move dialog, and the Pencil, Paintbrush, Smooth, Blob Brush, Eraser and Shape Builder tools' are
|
||||
/// their Tool Options, as are the Line Segment, Arc, Rectangular Grid and Polar Grid tools'
|
||||
/// Move dialog, and the Pencil, Paintbrush, Smooth, Blob Brush, Eraser, Shape Builder and Objects
|
||||
/// on Path tools' are their Tool Options, as are the Line Segment, Arc, Rectangular Grid and Polar Grid tools'
|
||||
/// ([`crate::dialogs::shapes::open_options`]). The Type on a Path and Vertical Type on a Path
|
||||
/// tools' are Type on a Path Options for the selected type on a path.
|
||||
pub fn open_options(app: &mut VectorcraftApp, tool: &str) -> Result<serde_json::Value, String> {
|
||||
@@ -362,6 +362,8 @@ pub fn open_options(app: &mut VectorcraftApp, tool: &str) -> Result<serde_json::
|
||||
Ok(json!({ "dialog": crate::dialogs::perspective_options::KIND }))
|
||||
}
|
||||
"shapeBuilder" => Ok(crate::dialogs::shape_builder_options::open(app)),
|
||||
// The Objects on Path tool: the Pivot and Rotate of the objects it attaches next.
|
||||
"objectsOnPath" => Ok(crate::dialogs::objects_on_path_options::open(app)),
|
||||
// The Line Segment, Arc and grid tools: their Tool Options, the options of their next
|
||||
// drag and click.
|
||||
_ if crate::dialogs::shapes::OPTION_TOOLS.contains(&tool) => crate::dialogs::shapes::open_options(app, tool),
|
||||
|
||||
+14
-4
@@ -5691,10 +5691,12 @@ opacity mask and appearance (Release on a group around the keys and spine when t
|
||||
the path in their order on the canvas, column by column from left to right and top to bottom in each column. The first
|
||||
one sits on the path's first anchor, the last one sits on the end of an open path, and the others are spread evenly
|
||||
between them by length; on a closed path the gaps run all the way round. The attachment point of each object (`pivot`,
|
||||
0 to 8 row by row from the top left of its bounds without strokes; default 4, the center) sits on the path, and the
|
||||
object is turned to the path's direction there. An object rotated by other than a multiple of 90° is first turned to
|
||||
the nearest multiple. `rotate` tilts every object by that many degrees counter-clockwise. Without `path`, the command
|
||||
chooses the Objects on Path tool (`objectsOnPath`), whose click on a path sends `{path}`. As the path, the command
|
||||
0 to 8 row by row from the top left of its bounds without strokes) sits on the path, and the object is turned to the
|
||||
path's direction there. An object rotated by other than a multiple of 90° is first turned to the nearest multiple.
|
||||
`rotate` tilts every object by that many degrees counter-clockwise. Without `pivot` or `rotate`, the command uses the
|
||||
Objects on Path tool's options (4, the center, and 0 until they are changed). Without `path`, the command chooses the
|
||||
Objects on Path tool (`objectsOnPath`), whose click on a path sends `{path, pivot, rotate}` from its options. As the
|
||||
path, the command
|
||||
refuses a guide, a compound path, type, a path without length, a path already in an Objects on Path group, a
|
||||
clipping path, and a path inside a compound path, blend, envelope, repeat or Intertwine group. As objects, it skips
|
||||
layers and refuses graphs, objects already in an Objects on Path group, clipping paths, and objects inside a blend,
|
||||
@@ -5711,7 +5713,15 @@ on Path groups or objects in them), or on the groups of the selection when `ids`
|
||||
the group with an `objects_on_path` record; SVG, PDF and the other exports write the path and the objects as plain
|
||||
art.
|
||||
|
||||
In the desktop app, double-clicking the Objects on Path tool (`tool.options {tool: "objectsOnPath"}`) opens Objects on
|
||||
Path Tool Options (dialog `objectsOnPathOptions`): the nine-point Pivot grid and the Rotate field (°). Save sets the
|
||||
tool's options (`tool.setOption {tool: "objectsOnPath", values: {pivot, rotate}}`), which the objects attached next
|
||||
use; groups attached before keep their Pivot and Rotate. Reset (or Cancel while Alt, Option on macOS, is held, or
|
||||
`ui.dialogReset`) puts the center and 0 back and keeps the dialog open. The options last across tool switches and are
|
||||
saved with the preferences.
|
||||
|
||||
```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.expand","params":{}}}
|
||||
|
||||
@@ -930,7 +930,8 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
When the object the Blend tool picked first is deleted or undone away before the second click, the next click on an object picks that object first instead of doing nothing. Regression tests: `blend_tool_picks_again_when_its_first_pick_is_gone` in `meshblend.rs` and `the_blend_tool_picks_again_when_its_first_pick_is_deleted` in `tests/blendopts.rs`.
|
||||
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.
|
||||
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`). 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. The Space, Move All, Rotate All and Select/Move widgets, Detach, the tool's options dialog and the Properties panel section are 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`). 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. The Space, Move All, Rotate All and Select/Move widgets, Detach and the Properties panel section are missing.
|
||||
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.
|
||||
- **Swatches:** names follow the colour model and are unique; Swatch Options (process/spot, Global, Gray/RGB/HSB/CMYK/Web, live preview); editing a global or spot swatch recolours every linked fill, stroke and text run in one undo step; multi-select and delete with confirmation; New Swatch and New Color Group dialogs (a group from the selected artwork's colours); swatches in colour groups convert, separate and count like any other. The Swatches panel selects ranges and whole groups, has a find field, and drags swatches to reorder, regroup or paint art; Add Used Colors, Select All Unused, Merge, Ungroup and Sort by Kind; a CMYK document keeps applied colours in CMYK. Swatch libraries: nine generated colour libraries and five gradient libraries in a library panel, saved as `.vcswatches`, `.gpl`, swatch exchange (`.ase`) or CSS, and loaded from `.vcswatches`, `.gpl` and `.ase` files (`.ase` both ways: RGB, CMYK, Lab and Gray; global, spot and process; groups) and from color books (`.acb`, read only: the RGB, CMYK or Lab colors of a book such as a Pantone book a user owns, named with its prefix and suffix, spot unless it says process, #831); `swatch.library.copyToUser` copies a loaded library into the User Defined folder without replacing a file, and a file with the same extension and bytes as a User Defined library opens as that library. Tints of global and spot colours ("Name 40%") stay linked and separate as a percentage of their plate. Spot colours can be defined in Lab (Spot Colors options). A built-in [Registration] swatch prints on every plate.
|
||||
|
||||
Reference in New Issue
Block a user