mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 09:59:47 -06:00
Align: Distribute Objects uses the spacing value with a key object
With a key object, the Align panel's six Distribute Objects buttons ignored the Distribute Spacing value. They kept the outermost objects in place, spaced the others evenly between them and could move the key object. object.distribute now takes spacing. While the selection has a key object, the key object stays in place, with the compound path or selected group that contains it, and the objects' left edges, centers or right edges (top edges, centers or bottom edges) end up spacing points apart, outward from the key object's, in their order on that line. With 0, the field's default, every object ends on the key object's line. Two objects are enough, and to and artboard are not used. The panel sends the value with the Distribute Objects buttons while there is a key object, as it does with the Distribute Spacing buttons. Without a key object, or with anchor points selected, object.distribute does not use spacing and works as before. A spacing that is not a number is an error. Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
@@ -252,7 +252,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Distribute",
|
||||
["Window", "Align"],
|
||||
None,
|
||||
"{horizontal?: \"left\"|\"center\"|\"right\", vertical?: \"top\"|\"center\"|\"bottom\", to?: \"selection\"|\"artboard\"|\"key\" (default: the selection; \"key\" distributes as \"selection\" does; not used with anchor points selected), artboard?: index (0-based; with to: \"artboard\", the artboard to distribute across; the app passes the active one; default: as object.align's), bounds?: \"preview\"|\"geometric\" (default: the Use Preview Bounds preference; preview bounds take in strokes), patterns?: bool (Transform Patterns: pattern fills and strokes transform with the art; default: prefs transformPatternTiles)} space the objects' left edges, centers or right edges (top edges, centers or bottom edges) evenly, in their order on that line: the first and last objects stay put, and fewer than three objects don't move; with to: \"artboard\", the first one's left (top) edge goes to the artboard's left (top) edge, the last one's right (bottom) edge goes to the artboard's right (bottom) edge, and two objects are enough. With anchor points selected (Direct Selection), the points are spaced evenly between the first and the last along the axis instead (#1636)",
|
||||
"{horizontal?: \"left\"|\"center\"|\"right\", vertical?: \"top\"|\"center\"|\"bottom\", spacing?: pt (used with a key object, select.key: the key object stays put and the lines are placed this far apart, outward from its line, in their order on that line; 0 puts them all on its line; not used without a key object or with anchor points selected), to?: \"selection\"|\"artboard\"|\"key\" (default: the selection; \"key\" distributes as \"selection\" does; not used with anchor points selected, or with spacing and a key object), artboard?: index (0-based; with to: \"artboard\", the artboard to distribute across; the app passes the active one; default: as object.align's), bounds?: \"preview\"|\"geometric\" (default: the Use Preview Bounds preference; preview bounds take in strokes), patterns?: bool (Transform Patterns: pattern fills and strokes transform with the art; default: prefs transformPatternTiles)} space the objects' left edges, centers or right edges (top edges, centers or bottom edges) in their order on that line: without spacing and a key object they are spaced evenly, the first and last objects stay put, and fewer than three objects don't move; with spacing and a key object, see spacing (two objects are enough); with to: \"artboard\", the first one's left (top) edge goes to the artboard's left (top) edge, the last one's right (bottom) edge goes to the artboard's right (bottom) edge, and two objects are enough. With anchor points selected (Direct Selection), the points are spaced evenly between the first and the last along the axis instead (#1636)",
|
||||
can_distribute,
|
||||
distribute
|
||||
),
|
||||
@@ -1232,6 +1232,15 @@ fn direction<'a>(p: &'a Value, cmd: &str) -> Result<(Option<&'a str>, Option<&'a
|
||||
Ok((choice(p, cmd, "horizontal", &["left", "center", "right"])?, choice(p, cmd, "vertical", &["top", "center", "bottom"])?))
|
||||
}
|
||||
|
||||
/// `spacing` of `object.distribute` and `object.distributeSpacing`: a finite number, or `None`
|
||||
/// when it is missing or null. Any other value is an error.
|
||||
fn spacing(p: &Value, cmd: &str) -> Result<Option<f64>> {
|
||||
p.get("spacing")
|
||||
.filter(|v| !v.is_null())
|
||||
.map(|v| v.as_f64().filter(|g| g.is_finite()).ok_or_else(|| bad(cmd, "`spacing` must be a number")))
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// `ids` with their bounds (`preview`: visual bounds).
|
||||
fn items_bounds(d: &Document, ids: &[NodeId], preview: bool) -> Vec<(NodeId, Rect)> {
|
||||
ids.iter().filter_map(|id| Some((*id, d.bounds_of(&[*id], preview)?))).collect()
|
||||
@@ -1294,8 +1303,8 @@ fn distribute_to(s: &Session, p: &Value, ids: &[NodeId], preview: bool, cmd: &st
|
||||
}
|
||||
|
||||
/// Returns the selected root (one of `ids`) that is the key object or contains it, such as the
|
||||
/// compound path or selected group the key is in. Align to the key object and Distribute Spacing
|
||||
/// with a spacing keep it in place.
|
||||
/// compound path or selected group the key is in. Align to the key object, and Distribute and
|
||||
/// Distribute Spacing with a spacing, keep it in place.
|
||||
fn key_root(s: &Session, ids: &[NodeId]) -> Option<NodeId> {
|
||||
let st = s.doc().ok()?;
|
||||
let ancestry = st.doc.ancestry(st.selection.key?)?;
|
||||
@@ -1439,6 +1448,7 @@ fn distribute(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let ids = selected_roots(s)?;
|
||||
let preview = preview_bounds(s, p, "object.distribute")?;
|
||||
let board = distribute_to(s, p, &ids, preview, "object.distribute")?;
|
||||
let spacing = spacing(p, "object.distribute")?;
|
||||
// Anchor points selected with Direct Selection are distributed themselves, as Align aligns them.
|
||||
if !s.doc()?.selection.anchors.is_empty() {
|
||||
return match direction(p, "object.distribute")? {
|
||||
@@ -1468,8 +1478,14 @@ fn distribute(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
};
|
||||
items.sort_by(|a, b| key(&a.1, horiz).total_cmp(&key(&b.1, horiz)));
|
||||
let n = items.len();
|
||||
// Two objects are enough on an artboard: they go to its edges.
|
||||
if n < 2 || (n < 3 && board.is_none()) {
|
||||
// With a spacing, the key object (or the compound path or selected group it is in) stays where
|
||||
// it is, and the lines are placed `spacing` apart, outward from its line, in their order.
|
||||
// Without a key object the spacing is not used. `from_key` is the key's index in `items` and
|
||||
// the spacing.
|
||||
let from_key = spacing.zip(key_root(s, &ids)).and_then(|(g, k)| Some((items.iter().position(|(id, _)| *id == k)?, g)));
|
||||
// Two objects are enough on an artboard, where they go to its edges, and when they are spaced
|
||||
// from a key object.
|
||||
if n < 2 || (n < 3 && board.is_none() && from_key.is_none()) {
|
||||
return ok();
|
||||
}
|
||||
let (first_box, last_box) = (&items[0].1, &items[n - 1].1);
|
||||
@@ -1486,7 +1502,10 @@ fn distribute(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (id, r))| {
|
||||
let target = first + (last - first) * i as f64 / (n - 1) as f64;
|
||||
let target = match from_key {
|
||||
Some((k, g)) => key(&items[k].1, horiz) + g * (i as f64 - k as f64),
|
||||
None => first + (last - first) * i as f64 / (n - 1) as f64,
|
||||
};
|
||||
let delta = target - key(r, horiz);
|
||||
(*id, if horiz { Vec2::new(delta, 0.0) } else { Vec2::new(0.0, delta) })
|
||||
})
|
||||
@@ -1499,11 +1518,7 @@ fn distribute_spacing(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let horiz = choice(p, "object.distributeSpacing", "axis", &["horizontal", "vertical"])? != Some("vertical");
|
||||
let preview = preview_bounds(s, p, "object.distributeSpacing")?;
|
||||
let board = distribute_to(s, p, &ids, preview, "object.distributeSpacing")?;
|
||||
let spacing = p
|
||||
.get("spacing")
|
||||
.filter(|v| !v.is_null())
|
||||
.map(|v| v.as_f64().filter(|g| g.is_finite()).ok_or_else(|| bad("object.distributeSpacing", "`spacing` must be a number")))
|
||||
.transpose()?;
|
||||
let spacing = spacing(p, "object.distributeSpacing")?;
|
||||
if !s.doc()?.selection.anchors.is_empty() {
|
||||
return distribute_anchors(s, "Distribute Spacing", horiz, spacing);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user