mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-27 19:27:21 -06:00
Fix #681: Rearrange All Artboards layouts, order and one-click access
- artboard.rearrange takes layout (gridByRow, gridByColumn, row, column;
byColumn stays as an alias of gridByColumn) and order (leftToRight,
rightToLeft); Columns is the row count of a grid by column and is
ignored by row and column. It returns {artboards, rows, columns}.
- A Rearrange All Artboards dialog: Layout and Layout Order toggles drawn
in code (widgets::layout_button, previewing the order), Columns/Rows,
Spacing, Move Artwork with Artboard and the artboard count.
- One click: Rearrange All in the Properties panel (Document section with
several artboards, Edit Artboards' Quick Actions); the Artboards panel's
Rearrange button and menu item now open the dialog too.
- Tests for every layout x order (artboards, art riding along, one undo
step), the dialog, the Properties button; the command sweep covers the
new params. Translations for ja, es, it, ru, fr, zh-hans, zh-hant and
pt-br; ROADMAP updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
23b7c68d90
commit
01df2c730b
@@ -156,7 +156,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Rearrange All Artboards…",
|
||||
["Object", "Artboards"],
|
||||
None,
|
||||
"{columns?: n (2), spacing?: pt (20), byColumn?: false, moveArtwork?: true (locked and hidden art only with prefs moveLockedWithArtboard)} lay artboards out in a grid",
|
||||
"{layout?: gridByRow|gridByColumn|row|column (gridByRow; byColumn?: true is gridByColumn), order?: leftToRight|rightToLeft (leftToRight: right-to-left mirrors the order along the rows), columns?: n (2; the rows for gridByColumn; row and column ignore it), spacing?: pt (20), moveArtwork?: true (locked and hidden art only with prefs moveLockedWithArtboard)} lay the artboards out in their order, the grid's top-left at the first artboard's → {artboards, rows, columns}",
|
||||
has_doc,
|
||||
rearrange_artboards
|
||||
),
|
||||
@@ -777,10 +777,32 @@ fn convert_to_artboards(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
}
|
||||
|
||||
fn rearrange_artboards(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let n_ab = s.doc()?.doc.artboards.len().max(1);
|
||||
let cols = (f64_or(p, "columns", 2.0).clamp(1.0, n_ab as f64)) as usize;
|
||||
const CMD: &str = "artboard.rearrange";
|
||||
use super::newdoc::ArtboardLayout as L;
|
||||
// A value given (not null) must be one of the names.
|
||||
let named = |key: &str| p.get(key).filter(|v| !v.is_null()).map(|v| v.as_str().unwrap_or_default());
|
||||
let layout = match named("layout") {
|
||||
Some(l) => L::parse(l).ok_or_else(|| bad(CMD, "layout must be gridByRow, gridByColumn, row or column"))?,
|
||||
None if bool_or(p, "byColumn", false) => L::GridByColumn,
|
||||
None => L::GridByRow,
|
||||
};
|
||||
let rtl = match named("order") {
|
||||
None => false,
|
||||
Some(o) if o.eq_ignore_ascii_case("leftToRight") => false,
|
||||
Some(o) if o.eq_ignore_ascii_case("rightToLeft") => true,
|
||||
Some(_) => return Err(bad(CMD, "order must be leftToRight or rightToLeft")),
|
||||
};
|
||||
let count = s.doc()?.doc.artboards.len();
|
||||
let n_ab = count.max(1);
|
||||
// Columns of a grid by row, rows of a grid by column (NaN casts to 0).
|
||||
let lines = (f64_or(p, "columns", 2.0).clamp(1.0, n_ab as f64) as usize).max(1);
|
||||
let (rows, cols) = match layout {
|
||||
L::GridByRow => (n_ab.div_ceil(lines), lines),
|
||||
L::GridByColumn => (lines, n_ab.div_ceil(lines)),
|
||||
L::Row => (1, n_ab),
|
||||
L::Column => (n_ab, 1),
|
||||
};
|
||||
let spacing = f64_or(p, "spacing", 20.0).clamp(-1.0e5, 1.0e5);
|
||||
let by_col = bool_or(p, "byColumn", false);
|
||||
let move_art = bool_or(p, "moveArtwork", true);
|
||||
let locked_and_hidden = s.prefs.move_locked_with_artboard;
|
||||
let scale_strokes = false;
|
||||
@@ -788,7 +810,7 @@ fn rearrange_artboards(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let rects: Vec<Rect> = d.artboards.iter().map(|a| a.rect).collect();
|
||||
let Some(first) = rects.first().copied() else { return Ok(()) };
|
||||
let sizes: Vec<(f64, f64)> = rects.iter().map(|r| (r.width(), r.height())).collect();
|
||||
let origins = super::newdoc::grid_origins(&sizes, first.origin(), cols, spacing, by_col, false);
|
||||
let origins = super::newdoc::grid_origins_rc(&sizes, first.origin(), (rows, cols), spacing, layout == L::GridByColumn, rtl);
|
||||
let deltas: Vec<Vec2> = rects.iter().zip(origins).map(|(r, o)| o - r.origin()).collect();
|
||||
if move_art {
|
||||
let tops: Vec<(NodeId, Point)> = d
|
||||
@@ -800,11 +822,11 @@ fn rearrange_artboards(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
.filter_map(|n| Some((n.id, n.geometric_bounds()?.center())))
|
||||
.collect();
|
||||
for (id, c) in tops {
|
||||
if let Some(i) = rects.iter().position(|r| r.contains(c))
|
||||
&& deltas[i] != Vec2::ZERO
|
||||
if let Some(&dl) = rects.iter().position(|r| r.contains(c)).and_then(|i| deltas.get(i))
|
||||
&& dl != Vec2::ZERO
|
||||
&& let Some(n) = d.node_mut(id)
|
||||
{
|
||||
n.transform(Affine::translate(deltas[i]), scale_strokes);
|
||||
n.transform(Affine::translate(dl), scale_strokes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -818,5 +840,5 @@ fn rearrange_artboards(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
ok()
|
||||
Ok(json!({ "artboards": count, "rows": rows, "columns": cols }))
|
||||
}
|
||||
|
||||
@@ -345,9 +345,22 @@ pub fn color_mode_id(m: ColorMode) -> &'static str {
|
||||
/// each column as wide and each row as tall as its largest artboard; `by_column` fills columns
|
||||
/// top to bottom first, `rtl` puts the first column at the right.
|
||||
pub(crate) fn grid_origins(sizes: &[(f64, f64)], origin: Point, columns: usize, spacing: f64, by_column: bool, rtl: bool) -> Vec<Point> {
|
||||
let n = sizes.len();
|
||||
let cols = columns.clamp(1, n.max(1));
|
||||
let rows = n.div_ceil(cols).max(1);
|
||||
let n = sizes.len().max(1);
|
||||
let cols = columns.clamp(1, n);
|
||||
grid_origins_rc(sizes, origin, (n.div_ceil(cols), cols), spacing, by_column, rtl)
|
||||
}
|
||||
|
||||
/// [`grid_origins`] on a grid of `rows` × `cols` cells (at least enough for every artboard when
|
||||
/// filled the `by_column` way).
|
||||
pub(crate) fn grid_origins_rc(
|
||||
sizes: &[(f64, f64)],
|
||||
origin: Point,
|
||||
(rows, cols): (usize, usize),
|
||||
spacing: f64,
|
||||
by_column: bool,
|
||||
rtl: bool,
|
||||
) -> Vec<Point> {
|
||||
let (rows, cols) = (rows.max(1), cols.max(1));
|
||||
// Grid cell (row, column) of artboard i.
|
||||
let cell = |i: usize| {
|
||||
let (r, c) = if by_column { (i % rows, i / rows) } else { (i / cols, i % cols) };
|
||||
@@ -362,7 +375,7 @@ pub(crate) fn grid_origins(sizes: &[(f64, f64)], origin: Point, columns: usize,
|
||||
}
|
||||
}
|
||||
let offset = |spans: &[f64], k: usize| spans.iter().take(k).map(|s| s + spacing).sum::<f64>();
|
||||
(0..n)
|
||||
(0..sizes.len())
|
||||
.map(|i| {
|
||||
let (r, c) = cell(i);
|
||||
Point::new(origin.x + offset(&col_w, c), origin.y + offset(&row_h, r))
|
||||
|
||||
Reference in New Issue
Block a user