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:
parent
23b7c68d90
commit
01df2c730b
File diff suppressed because one or more lines are too long
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -223,3 +223,63 @@ fn rearrange_keeps_its_grid() {
|
|||
s.execute("artboard.rearrange", &json!({"columns": 2, "spacing": 5})).unwrap();
|
||||
assert_eq!(boards(&s).iter().map(|r| (r.x0, r.y0)).collect::<Vec<_>>(), [(0.0, 0.0), (105.0, 0.0), (0.0, 55.0)]);
|
||||
}
|
||||
|
||||
/// #681: every layout and order of Rearrange All Artboards, five 100 × 50 artboards 10 pt apart
|
||||
/// with Columns (the rows of a grid by column) 2: each artboard lands in its (row, column) cell, the
|
||||
/// art on it rides along, and it all undoes in one step.
|
||||
#[test]
|
||||
fn rearrange_lays_out_by_layout_and_order() {
|
||||
type Cells = [(u32, u32); 5];
|
||||
let cells: [(&str, &str, Cells); 8] = [
|
||||
("gridByRow", "leftToRight", [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0)]),
|
||||
("gridByRow", "rightToLeft", [(0, 1), (0, 0), (1, 1), (1, 0), (2, 1)]),
|
||||
("gridByColumn", "leftToRight", [(0, 0), (1, 0), (0, 1), (1, 1), (0, 2)]),
|
||||
("gridByColumn", "rightToLeft", [(0, 2), (1, 2), (0, 1), (1, 1), (0, 0)]),
|
||||
("row", "leftToRight", [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4)]),
|
||||
("row", "rightToLeft", [(0, 4), (0, 3), (0, 2), (0, 1), (0, 0)]),
|
||||
("column", "leftToRight", [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0)]),
|
||||
("column", "rightToLeft", [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0)]),
|
||||
];
|
||||
for (layout, order, want) in cells {
|
||||
// One row, 20 pt apart; a 10 pt square 10 pt into each artboard.
|
||||
let mut s = new_doc(json!({"width": 100, "height": 50, "artboards": 5}));
|
||||
let art: Vec<u64> = boards(&s)
|
||||
.iter()
|
||||
.map(|r| {
|
||||
s.execute("shape.rectangle", &json!({"x": r.x0 + 10.0, "y": r.y0 + 10.0, "width": 10, "height": 10})).unwrap()["id"].as_u64().unwrap()
|
||||
})
|
||||
.collect();
|
||||
let before = boards(&s);
|
||||
let undo = s.doc().unwrap().history.undo.len();
|
||||
let p = json!({"layout": layout, "order": order, "columns": 2, "spacing": 10});
|
||||
let r = s.execute("artboard.rearrange", &p).unwrap();
|
||||
let (rows, cols) = want.iter().fold((0, 0), |(r, c), (wr, wc)| (r.max(wr + 1), c.max(wc + 1)));
|
||||
assert_eq!(r, json!({"artboards": 5, "rows": rows, "columns": cols}), "{layout} {order}");
|
||||
let got: Vec<(f64, f64)> = boards(&s).iter().map(|r| (r.x0, r.y0)).collect();
|
||||
let cells: Vec<(f64, f64)> = want.iter().map(|&(r, c)| (f64::from(c) * 110.0, f64::from(r) * 60.0)).collect();
|
||||
assert_eq!(got, cells, "{layout} {order}");
|
||||
assert!(boards(&s).iter().all(|r| r.width() == 100.0 && r.height() == 50.0));
|
||||
for (id, (x, y)) in art.iter().zip(&got) {
|
||||
let b = s.doc().unwrap().doc.node(NodeId(*id)).unwrap().geometric_bounds().unwrap();
|
||||
assert_eq!((b.x0, b.y0), (x + 10.0, y + 10.0), "{layout} {order}: the art moves with its artboard");
|
||||
}
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), undo + 1, "one undo step");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(boards(&s), before);
|
||||
}
|
||||
}
|
||||
|
||||
/// `byColumn: true` is Grid by Column; without `moveArtwork` the art stays; bad values are errors.
|
||||
#[test]
|
||||
fn rearrange_aliases_and_rejects_bad_values() {
|
||||
let mut s = new_doc(json!({"width": 100, "height": 50, "artboards": 3}));
|
||||
let id = s.execute("shape.rectangle", &json!({"x": 130, "y": 10, "width": 10, "height": 10})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("artboard.rearrange", &json!({"byColumn": true, "columns": 2, "spacing": 0, "moveArtwork": false})).unwrap();
|
||||
assert_eq!(boards(&s).iter().map(|r| (r.x0, r.y0)).collect::<Vec<_>>(), [(0.0, 0.0), (0.0, 50.0), (100.0, 0.0)]);
|
||||
assert_eq!(s.doc().unwrap().doc.node(NodeId(id)).unwrap().geometric_bounds().unwrap().x0, 130.0, "the art stays");
|
||||
let before = boards(&s);
|
||||
for p in [json!({"layout": "diagonal"}), json!({"order": "upward"}), json!({"layout": 3}), json!({"order": true})] {
|
||||
assert!(s.execute("artboard.rearrange", &p).is_err(), "{p}");
|
||||
}
|
||||
assert_eq!(boards(&s), before, "nothing moved");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,13 +98,19 @@ fn known_bug(id: &str, p: &Value) -> bool {
|
|||
KNOWN_BUGS.iter().any(|(c, k)| *c == id && p.get(*k).is_some())
|
||||
}
|
||||
|
||||
/// Regression: huge column counts used to panic with `capacity overflow`.
|
||||
/// Regression: huge column counts used to panic with `capacity overflow`; in every layout and
|
||||
/// order (#681), with huge and negative spacing too.
|
||||
#[test]
|
||||
fn artboard_rearrange_huge_columns() {
|
||||
for fx in Fixture::ALL {
|
||||
for cols in [json!(u64::MAX), json!(1e308)] {
|
||||
let p = json!({ "columns": cols });
|
||||
assert_eq!(probe(fx, "artboard.rearrange", &p), None);
|
||||
for layout in ["gridByRow", "gridByColumn", "row", "column"] {
|
||||
for order in ["leftToRight", "rightToLeft"] {
|
||||
for (cols, spacing) in [(json!(u64::MAX), json!(1e308)), (json!(1e308), json!(-1e308)), (json!(0), json!(0)), (json!(-5), json!(20))]
|
||||
{
|
||||
let p = json!({ "layout": layout, "order": order, "columns": cols, "spacing": spacing });
|
||||
assert_eq!(probe(fx, "artboard.rearrange", &p), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ fn lengths(command: &str) -> &'static [&'static str] {
|
|||
match command {
|
||||
"graph.create" => &["width", "height"],
|
||||
"shape.flare" => &["diameter", "pathLength"],
|
||||
"artboard.rearrange" => &["spacing"],
|
||||
"perspective.grid.set" => &["cell", "distance"],
|
||||
"object.repeat.options" => &["radius", "hSpacing", "vSpacing"],
|
||||
"text.areaOptions" => &["width", "height", "gutter", "inset", "firstBaselineMin"],
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ pub mod print;
|
|||
pub mod print_presets;
|
||||
mod psd_options;
|
||||
pub mod raster_effects;
|
||||
pub mod rearrange_artboards;
|
||||
pub mod recolor;
|
||||
mod recovery;
|
||||
pub mod saturate;
|
||||
|
|
@ -278,6 +279,7 @@ registry! {
|
|||
PerspectivePlane: [perspective_plane::KIND] => perspective_plane::SPEC,
|
||||
LayerOptions: [layer_options::KIND] => layer_options::SPEC,
|
||||
LayersPanelOptions: [layers_panel_options::KIND] => layers_panel_options::SPEC,
|
||||
RearrangeArtboards: [rearrange_artboards::KIND] => rearrange_artboards::SPEC,
|
||||
}
|
||||
|
||||
/// The button labels the shared dialog frame can show (OK, discard and the fixed Cancel/Close),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,208 @@
|
|||
//! Object → Artboards → Rearrange All Artboards (also the Artboards panel's button and menu, and
|
||||
//! the Properties panel's Rearrange All): Layout (a grid by row or by column, one row or one
|
||||
//! column), Layout Order (left to right or right to left), Columns (Rows for a grid by column),
|
||||
//! Spacing and Move Artwork with Artboard, with the document's artboard count. OK runs
|
||||
//! `artboard.rearrange` as one undo step (#681).
|
||||
//!
|
||||
//! Fields: `layout` (`gridByRow` | `gridByColumn` | `row` | `column`), `order` (`leftToRight` |
|
||||
//! `rightToLeft`), `columns` (the rows of a grid by column), `spacing` (pt) and `moveArtwork`.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_engine::cmd::newdoc::ArtboardLayout;
|
||||
|
||||
use super::{DialogSpec, form, run_and_close};
|
||||
use crate::state::Dialog;
|
||||
use crate::{VectorcraftApp, widgets};
|
||||
|
||||
/// The dialog kind of Rearrange All Artboards.
|
||||
pub const KIND: &str = "rearrangeArtboards";
|
||||
|
||||
const CMD: &str = "artboard.rearrange";
|
||||
|
||||
/// Width of the label column.
|
||||
const LABEL_W: f32 = 96.0;
|
||||
|
||||
pub(super) const SPEC: DialogSpec =
|
||||
DialogSpec { heading: |_| tl!("Rearrange All Artboards").into(), body, confirm, min_width: 320.0, ..DialogSpec::FORM };
|
||||
|
||||
/// The fields a fresh dialog starts with.
|
||||
pub fn fields() -> Value {
|
||||
json!({"layout": "gridByRow", "order": "leftToRight", "columns": 2, "spacing": 20, "moveArtwork": true})
|
||||
}
|
||||
|
||||
/// A layout's toggle: its tooltip and its artboards' cells (column, row) in their order, left to
|
||||
/// right.
|
||||
fn layout_glyph(l: ArtboardLayout) -> (&'static str, &'static [(u8, u8)]) {
|
||||
match l {
|
||||
ArtboardLayout::GridByRow => (tl!("Grid by Row"), &[(0, 0), (1, 0), (0, 1), (1, 1)]),
|
||||
ArtboardLayout::GridByColumn => (tl!("Grid by Column"), &[(0, 0), (0, 1), (1, 0), (1, 1)]),
|
||||
ArtboardLayout::Row => (tl!("Arrange by Row"), &[(0, 0), (1, 0), (2, 0)]),
|
||||
ArtboardLayout::Column => (tl!("Arrange by Column"), &[(0, 0), (0, 1), (0, 2)]),
|
||||
}
|
||||
}
|
||||
|
||||
/// `cells` mirrored left to right when `rtl` (the order the layout toggles preview).
|
||||
fn mirrored(cells: &[(u8, u8)], rtl: bool) -> Vec<(u8, u8)> {
|
||||
let last = cells.iter().map(|c| c.0).max().unwrap_or(0);
|
||||
cells.iter().map(|&(x, y)| (if rtl { last - x } else { x }, y)).collect()
|
||||
}
|
||||
|
||||
/// `artboard.rearrange` parameters from the fields.
|
||||
fn params(d: &Dialog) -> Value {
|
||||
json!({
|
||||
"layout": d.str("layout"), "order": d.str("order"), "columns": d.f64("columns", 2.0),
|
||||
"spacing": d.f64("spacing", 20.0), "moveArtwork": d.bool("moveArtwork"),
|
||||
})
|
||||
}
|
||||
|
||||
fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
let unit = app.session.general_unit();
|
||||
let n = app.session.active().map_or(0, |doc| doc.doc.artboards.len());
|
||||
let layout = ArtboardLayout::parse(&d.str("layout")).unwrap_or_default();
|
||||
let rtl = d.str("order") == "rightToLeft";
|
||||
widgets::label_row(ui, tl!("Layout:"), LABEL_W, |ui| {
|
||||
ui.spacing_mut().item_spacing.x = 2.0;
|
||||
for l in ArtboardLayout::ALL {
|
||||
let (tip, cells) = layout_glyph(l);
|
||||
if widgets::layout_button(ui, &mirrored(cells, rtl), layout == l, tip) {
|
||||
d.fields.insert("layout".into(), json!(l.id()));
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
widgets::label_row(ui, tl!("Layout Order:"), LABEL_W, |ui| {
|
||||
ui.spacing_mut().item_spacing.x = 2.0;
|
||||
// One column reads the same either way.
|
||||
ui.add_enabled_ui(layout != ArtboardLayout::Column, |ui| {
|
||||
for (value, cells, tip) in [
|
||||
("leftToRight", [(0, 0), (1, 0)], tl!("Change to Left-to-Right Layout")),
|
||||
("rightToLeft", [(1, 0), (0, 0)], tl!("Change to Right-to-Left Layout")),
|
||||
] {
|
||||
if widgets::layout_button(ui, &cells, (value == "rightToLeft") == rtl, tip) {
|
||||
d.fields.insert("order".into(), json!(value));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
// The rows of a grid by column; one row or column has no count to set.
|
||||
let label = if layout == ArtboardLayout::GridByColumn { tl!("Rows:") } else { tl!("Columns:") };
|
||||
widgets::label_row(ui, label, LABEL_W, |ui| {
|
||||
let grid = matches!(layout, ArtboardLayout::GridByRow | ArtboardLayout::GridByColumn);
|
||||
ui.add_enabled_ui(grid, |ui| {
|
||||
let most = n.max(1) as f64;
|
||||
if let Some(v) = widgets::spin_plain(ui, "rearrange-columns", d.f64("columns", 2.0), "", 0, 76.0, 1.0, 1.0, &[]) {
|
||||
d.fields.insert("columns".into(), json!(v.round().clamp(1.0, most)));
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
widgets::label_row(ui, tl!("Spacing:"), LABEL_W, |ui| {
|
||||
form::length(ui, d, "spacing", unit, 100.0);
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
widgets::label_row(ui, tl!("Options:"), LABEL_W, |ui| {
|
||||
form::check(ui, d, "moveArtwork", tl!("Move Artwork with Artboard"));
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
widgets::dim_label(ui, &crate::i18n::fmt(tl!("Artboards: {n}"), &[("n", &n.to_string())]));
|
||||
false
|
||||
}
|
||||
|
||||
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
run_and_close(app, CMD, params(d))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use vectorcraft_engine::Session;
|
||||
|
||||
/// A frame of the open dialog with `events` → the texts painted. The dialog measures itself on
|
||||
/// its first frame, so a fresh context needs two.
|
||||
fn frame(app: &mut VectorcraftApp, ctx: &egui::Context, events: Vec<egui::Event>) -> Vec<String> {
|
||||
let mut out = ctx.run_ui(egui::RawInput { events, ..Default::default() }, |ui| super::super::show(app, ui.ctx()));
|
||||
out.textures_delta.clear();
|
||||
out.shapes.iter().filter_map(|s| if let egui::epaint::Shape::Text(t) = &s.shape { Some(t.galley.text().to_string()) } else { None }).collect()
|
||||
}
|
||||
|
||||
fn context() -> egui::Context {
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
ctx
|
||||
}
|
||||
|
||||
fn origins(app: &VectorcraftApp) -> Vec<(f64, f64)> {
|
||||
app.session.active().unwrap().doc.artboards.iter().map(|a| (a.rect.x0, a.rect.y0)).collect()
|
||||
}
|
||||
|
||||
/// #681: the menu item opens the dialog with the artboard count; Columns reads Rows for a grid
|
||||
/// by column; OK lays the artboards out right to left, as one undo step.
|
||||
#[test]
|
||||
fn opens_from_the_menu_and_rearranges_by_layout_and_order() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 100, "height": 50, "artboards": 3})).unwrap();
|
||||
// The menu item has no params of its own: it opens this dialog, not the generic one.
|
||||
let (id, p) = crate::menus::click_target("Rearrange All Artboards…", CMD, &Value::Null);
|
||||
crate::menus::invoke(&mut app, &id, p);
|
||||
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some(KIND));
|
||||
let ctx = context();
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
let texts = frame(&mut app, &ctx, vec![]);
|
||||
for want in ["Rearrange All Artboards", "Layout:", "Layout Order:", "Columns:", "Spacing:", "Move Artwork with Artboard", "Artboards: 3"] {
|
||||
assert!(texts.iter().any(|t| t == want), "{want:?} in {texts:?}");
|
||||
}
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert("layout".into(), json!("gridByColumn"));
|
||||
let texts = frame(&mut app, &ctx, vec![]);
|
||||
assert!(texts.iter().any(|t| t == "Rows:") && !texts.iter().any(|t| t == "Columns:"), "{texts:?}");
|
||||
// One row, right to left, 10 pt apart.
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert("layout".into(), json!("row"));
|
||||
d.fields.insert("order".into(), json!("rightToLeft"));
|
||||
d.fields.insert("spacing".into(), json!(10));
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
let undo = app.session.doc().unwrap().history.undo.len();
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
assert!(app.ui.dialog.is_none());
|
||||
assert_eq!(origins(&app), [(220.0, 0.0), (110.0, 0.0), (0.0, 0.0)]);
|
||||
assert_eq!(app.session.doc().unwrap().history.undo.len(), undo + 1);
|
||||
}
|
||||
|
||||
/// Clicking the layout and order toggles sets the fields (the toggles' rects from the frame).
|
||||
#[test]
|
||||
fn the_toggles_set_layout_and_order() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 100, "height": 50, "artboards": 4})).unwrap();
|
||||
app.run("ui.menuDialog", json!({ "command": CMD })).unwrap();
|
||||
let ctx = context();
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
// The 26-point toggles, left to right then top to bottom: four layouts, then two orders.
|
||||
let toggles: Vec<egui::Rect> = ctx.viewport(|vp| {
|
||||
let mut r: Vec<egui::Rect> = vp
|
||||
.prev_pass
|
||||
.widgets
|
||||
.layers()
|
||||
.flat_map(|(_, w)| w.iter())
|
||||
.filter(|w| w.sense.senses_click() && w.rect.width() == 26.0 && w.rect.height() == 26.0)
|
||||
.map(|w| w.rect)
|
||||
.collect();
|
||||
r.sort_by(|a, b| a.top().total_cmp(&b.top()).then(a.left().total_cmp(&b.left())));
|
||||
r
|
||||
});
|
||||
assert_eq!(toggles.len(), 6, "{toggles:?}");
|
||||
for (i, key, want) in [(3, "layout", "column"), (1, "layout", "gridByColumn"), (5, "order", "rightToLeft")] {
|
||||
let at = toggles[i].center();
|
||||
let b = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
|
||||
frame(&mut app, &ctx, vec![egui::Event::PointerMoved(at), b(true)]);
|
||||
frame(&mut app, &ctx, vec![b(false)]);
|
||||
assert_eq!(app.ui.dialog.as_ref().unwrap().str(key), want);
|
||||
// A frame for the order toggles to follow the layout (one column greys them).
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
}
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
// Two rows (Columns 2 reads Rows for a grid by column), the first column at the right.
|
||||
assert_eq!(origins(&app), [(120.0, 0.0), (120.0, 70.0), (0.0, 0.0), (0.0, 70.0)]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1954,7 +1954,16 @@
|
|||
Rating: Clasificación:
|
||||
Real World: Mundo real:
|
||||
Real-time Drawing and Editing Dibujo y edición en tiempo real
|
||||
Rearrange All Artboards (on the roadmap) Reorganizar todas las mesas de trabajo (en la hoja de ruta)
|
||||
Rearrange All Artboards Reorganizar todas las mesas de trabajo
|
||||
Rearrange All Reorganizar todo
|
||||
Layout Order: Orden de diseño:
|
||||
Change to Left-to-Right Layout Cambiar a diseño de izquierda a derecha
|
||||
Grid by Row Cuadrícula por filas
|
||||
Grid by Column Cuadrícula por columnas
|
||||
Arrange by Row Organizar en una fila
|
||||
Arrange by Column Organizar en una columna
|
||||
Move Artwork with Artboard Mover la ilustración con la mesa de trabajo
|
||||
Artboards: {n} Mesas de trabajo: {n}
|
||||
Rearrange All Artboards… Reorganizar todas las mesas de trabajo…
|
||||
Recall Selection Recuperar selección
|
||||
RECENT RECIENTES
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -1951,7 +1951,16 @@
|
|||
Rating: Classement :
|
||||
Real World: Monde réel :
|
||||
Real-time Drawing and Editing Dessin et édition en temps réel
|
||||
Rearrange All Artboards (on the roadmap) Réorganiser tous les plans de travail (prévu)
|
||||
Rearrange All Artboards Réorganiser tous les plans de travail
|
||||
Rearrange All Tout réorganiser
|
||||
Layout Order: Ordre de disposition :
|
||||
Change to Left-to-Right Layout Passer à la disposition de gauche à droite
|
||||
Grid by Row Grille par ligne
|
||||
Grid by Column Grille par colonne
|
||||
Arrange by Row Disposer en ligne
|
||||
Arrange by Column Disposer en colonne
|
||||
Move Artwork with Artboard Déplacer l’illustration avec le plan de travail
|
||||
Artboards: {n} Plans de travail : {n}
|
||||
Rearrange All Artboards… Réorganiser tous les plans de travail…
|
||||
Recall Selection Rappeler la sélection
|
||||
RECENT RÉCENTS
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -1954,7 +1954,16 @@
|
|||
Rating: Valutazione:
|
||||
Real World: Mondo reale:
|
||||
Real-time Drawing and Editing Disegno e modifica in tempo reale
|
||||
Rearrange All Artboards (on the roadmap) Ridisponi tutte le tavole da disegno (in programma)
|
||||
Rearrange All Artboards Ridisponi tutte le tavole da disegno
|
||||
Rearrange All Ridisponi tutto
|
||||
Layout Order: Ordine del layout:
|
||||
Change to Left-to-Right Layout Passa al layout da sinistra a destra
|
||||
Grid by Row Griglia per righe
|
||||
Grid by Column Griglia per colonne
|
||||
Arrange by Row Disponi in una riga
|
||||
Arrange by Column Disponi in una colonna
|
||||
Move Artwork with Artboard Sposta la grafica con la tavola da disegno
|
||||
Artboards: {n} Tavole da disegno: {n}
|
||||
Rearrange All Artboards… Ridisponi tutte le tavole da disegno…
|
||||
Recall Selection Richiama selezione
|
||||
RECENT RECENTI
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -1984,7 +1984,16 @@
|
|||
Rating: 評価:
|
||||
Real World: 実寸:
|
||||
Real-time Drawing and Editing リアルタイム描画および編集
|
||||
Rearrange All Artboards (on the roadmap) すべてのアートボードを再配置 (今後対応予定)
|
||||
Rearrange All Artboards すべてのアートボードを再配置
|
||||
Rearrange All すべて再配置
|
||||
Layout Order: レイアウト順序:
|
||||
Change to Left-to-Right Layout 左から右のレイアウトに変更
|
||||
Grid by Row 行ごとのグリッド
|
||||
Grid by Column 列ごとのグリッド
|
||||
Arrange by Row 1 行に配置
|
||||
Arrange by Column 1 列に配置
|
||||
Move Artwork with Artboard アートボードと一緒にアートワークを移動
|
||||
Artboards: {n} アートボード: {n}
|
||||
Recall Selection 選択範囲を呼び出し
|
||||
Recent Colors 最近使用したカラー
|
||||
Recent Files 最近使ったファイル
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -1746,7 +1746,16 @@
|
|||
Rating: Avaliação:
|
||||
Real World: Mundo Real:
|
||||
Real-time Drawing and Editing Desenho e Edição em Tempo Real
|
||||
Rearrange All Artboards (on the roadmap) Reorganizar Todas as Mesas de Trabalho (no roadmap)
|
||||
Rearrange All Artboards Reorganizar Todas as Mesas de Trabalho
|
||||
Rearrange All Reorganizar Tudo
|
||||
Layout Order: Ordem do Layout:
|
||||
Change to Left-to-Right Layout Mudar para Layout da Esquerda para a Direita
|
||||
Grid by Row Grade por Linha
|
||||
Grid by Column Grade por Coluna
|
||||
Arrange by Row Organizar em Linha
|
||||
Arrange by Column Organizar em Coluna
|
||||
Move Artwork with Artboard Mover Arte com a Mesa de Trabalho
|
||||
Artboards: {n} Mesas de Trabalho: {n}
|
||||
Rearrange All Artboards… Reorganizar Todas as Mesas de Trabalho…
|
||||
Recall Selection Relembrar Seleção
|
||||
Recent Colors Cores Recentes
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -1950,7 +1950,16 @@
|
|||
Rating: Рейтинг:
|
||||
Real World: Реальный мир:
|
||||
Real-time Drawing and Editing Рисование и редактирование в реальном времени
|
||||
Rearrange All Artboards (on the roadmap) Переставить все монтажные области (в плане развития)
|
||||
Rearrange All Artboards Переставить все монтажные области
|
||||
Rearrange All Переставить все
|
||||
Layout Order: Порядок макета:
|
||||
Change to Left-to-Right Layout Переключить на макет слева направо
|
||||
Grid by Row Сетка по строкам
|
||||
Grid by Column Сетка по столбцам
|
||||
Arrange by Row Расположить в строку
|
||||
Arrange by Column Расположить в столбец
|
||||
Move Artwork with Artboard Перемещать иллюстрацию вместе с монтажной областью
|
||||
Artboards: {n} Монтажные области: {n}
|
||||
Rearrange All Artboards… Переставить все монтажные области…
|
||||
Recall Selection Восстановить выделение
|
||||
RECENT ПОСЛЕДНИЕ
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -1928,7 +1928,16 @@
|
|||
Rating: 等级:
|
||||
Real World: 真实世界:
|
||||
Real-time Drawing and Editing 实时绘图和编辑
|
||||
Rearrange All Artboards (on the roadmap) 重新排列所有画板 (规划中)
|
||||
Rearrange All Artboards 重新排列所有画板
|
||||
Rearrange All 全部重新排列
|
||||
Layout Order: 布局顺序:
|
||||
Change to Left-to-Right Layout 更改为由左至右版面配置
|
||||
Grid by Row 按行网格排列
|
||||
Grid by Column 按列网格排列
|
||||
Arrange by Row 排成一行
|
||||
Arrange by Column 排成一列
|
||||
Move Artwork with Artboard 随画板移动图稿
|
||||
Artboards: {n} 画板:{n}
|
||||
Rearrange All Artboards… 重新排列所有画板…
|
||||
Recall Selection 召回选区
|
||||
RECENT 最近使用
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
|
|
@ -1974,7 +1974,16 @@
|
|||
Rating: 評等:
|
||||
Real World: 真實世界:
|
||||
Real-time Drawing and Editing 即時繪圖與編輯
|
||||
Rearrange All Artboards (on the roadmap) 重新排列所有工作區域 (規劃中)
|
||||
Rearrange All Artboards 重新排列所有工作區域
|
||||
Rearrange All 全部重新排列
|
||||
Layout Order: 版面配置順序:
|
||||
Change to Left-to-Right Layout 變更為由左至右版面配置
|
||||
Grid by Row 依列格狀排列
|
||||
Grid by Column 依欄格狀排列
|
||||
Arrange by Row 排成一列
|
||||
Arrange by Column 排成一欄
|
||||
Move Artwork with Artboard 隨工作區域移動圖稿
|
||||
Artboards: {n} 工作區域:{n}
|
||||
Rearrange All Artboards… 重新排列所有工作區域…
|
||||
Recall Selection 叫出選取範圍
|
||||
RECENT 最近使用
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -473,7 +473,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
|||
"ui.menuDialog",
|
||||
"Menu Dialog",
|
||||
"",
|
||||
"{command: object.move|object.rotate|object.scale|object.reflect|object.shear|object.transformEach|path.average|object.path.offsetPath|object.path.simplify|object.path.splitIntoGrid|object.vectorHalftone|object.envelope.makeWithWarp|object.envelope.resetWithWarp|object.envelope.makeWithMesh|object.envelope.resetWithMesh|object.envelope.options} open the dialog that command's menu item opens (dialog kind: move, rotate, scale, reflect, shear, transformEach, …, vectorHalftone, envelopeWarp, envelopeMesh, envelopeOptions; Scale and Transform Each have `corners` and `strokes`, from the preferences, which OK updates; the envelope dialogs start from the selected envelope, Make opens as Reset (`reset: true`) while one is selected)",
|
||||
"{command: object.move|object.rotate|object.scale|object.reflect|object.shear|object.transformEach|path.average|object.path.offsetPath|object.path.simplify|object.path.splitIntoGrid|object.vectorHalftone|artboard.rearrange|object.envelope.makeWithWarp|object.envelope.resetWithWarp|object.envelope.makeWithMesh|object.envelope.resetWithMesh|object.envelope.options} open the dialog that command's menu item opens (dialog kind: move, rotate, scale, reflect, shear, transformEach, …, vectorHalftone, rearrangeArtboards, envelopeWarp, envelopeMesh, envelopeOptions; Scale and Transform Each have `corners` and `strokes`, from the preferences, which OK updates; the envelope dialogs start from the selected envelope, Make opens as Reset (`reset: true`) while one is selected)",
|
||||
),
|
||||
(
|
||||
"ui.widthPointEdit",
|
||||
|
|
@ -2196,7 +2196,7 @@ pub fn menu_tree() -> Vec<(&'static str, Vec<Item>)> {
|
|||
"Artboards",
|
||||
vec![
|
||||
c("Convert to Artboards", "artboard.convertToArtboards"),
|
||||
cp("Rearrange All Artboards…", "artboard.rearrange", json!({"columns": 2, "spacing": 20, "moveArtwork": true})),
|
||||
c("Rearrange All Artboards…", "artboard.rearrange"),
|
||||
Sep,
|
||||
c("Fit to Artwork Bounds", "artboard.fitToArt"),
|
||||
c("Fit to Selected Art", "artboard.fitToSelection"),
|
||||
|
|
@ -2870,6 +2870,7 @@ fn menu_dialog(id: &str) -> Option<(&'static str, Value)> {
|
|||
"object.path.simplify" => ("simplify", json!({"tolerance": "1 pt"})),
|
||||
"object.path.splitIntoGrid" => ("splitIntoGrid", json!({"rows": 2, "columns": 2, "gutter": "12 pt"})),
|
||||
"object.vectorHalftone" => (crate::dialogs::halftone::KIND, crate::dialogs::halftone::fields()),
|
||||
"artboard.rearrange" => (crate::dialogs::rearrange_artboards::KIND, crate::dialogs::rearrange_artboards::fields()),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
|||
});
|
||||
let n = abs.len();
|
||||
widgets::bottom_bar(ui, |ui| {
|
||||
widgets::icon_button_enabled(ui, "dc-rearrange", tl!("Rearrange All Artboards (on the roadmap)"), false, false, 24.0);
|
||||
if widgets::icon_button_enabled(ui, "dc-rearrange", tl!("Rearrange All Artboards"), false, n > 1, 24.0).clicked() {
|
||||
open_rearrange(app);
|
||||
}
|
||||
ui.add_space((ui.available_width() - 4.0 * 28.0).max(0.0));
|
||||
if widgets::icon_button_enabled(ui, "dc-arrow-up", tl!("Move Up"), false, sel > 0, 24.0).clicked()
|
||||
&& app.run("artboard.reorder", json!({"index": sel, "to": sel - 1})).is_ok()
|
||||
|
|
@ -140,6 +142,11 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
|||
});
|
||||
}
|
||||
|
||||
/// Open Rearrange All Artboards (#681).
|
||||
pub(crate) fn open_rearrange(app: &mut VectorcraftApp) {
|
||||
app.run("ui.menuDialog", json!({ "command": "artboard.rearrange" })).ok();
|
||||
}
|
||||
|
||||
pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let n = app.session.active().map(|d| d.doc.artboards.len()).unwrap_or(0);
|
||||
let sel = selected(app, n);
|
||||
|
|
@ -161,7 +168,9 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
|||
if menu_item(ui, tl!("Artboard Options…"), n > 0, false) {
|
||||
app.select_tool("artboard");
|
||||
}
|
||||
menu_item(ui, tl!("Rearrange All Artboards…"), false, false);
|
||||
if menu_item(ui, tl!("Rearrange All Artboards…"), n > 1, false) {
|
||||
open_rearrange(app);
|
||||
}
|
||||
ui.separator();
|
||||
if menu_item(ui, tl!("Fit to Artwork Bounds"), n > 0, false) {
|
||||
app.run("artboard.fitToArt", json!({"index": sel})).ok();
|
||||
|
|
|
|||
|
|
@ -198,7 +198,8 @@ fn multi_color(app: &mut VectorcraftApp, ctx: &egui::Context) -> bool {
|
|||
}
|
||||
|
||||
/// Edit Artboards (the Artboard tool): the active artboard's name, position and size, its preset and
|
||||
/// orientation (#671), New Artboard and Delete Artboard, and Exit back to the Selection tool (#530).
|
||||
/// orientation (#671), New Artboard and Delete Artboard, and Quick Actions: Rearrange All (#681) and
|
||||
/// Exit back to the Selection tool (#530).
|
||||
fn artboard_sections(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let units = app.session.general_unit();
|
||||
let n = app.session.active().map_or(0, |d| d.doc.artboards.len());
|
||||
|
|
@ -235,8 +236,18 @@ fn artboard_sections(app: &mut VectorcraftApp, ui: &mut Ui) {
|
|||
});
|
||||
divider(ui);
|
||||
section_header(ui, tl!("Quick Actions"));
|
||||
if widgets::flat_button(ui, tl!("Exit"), w).clicked() {
|
||||
app.select_tool("selection");
|
||||
ui.horizontal(|ui| {
|
||||
rearrange_button(app, ui, n, w);
|
||||
if widgets::flat_button(ui, tl!("Exit"), w).clicked() {
|
||||
app.select_tool("selection");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Rearrange All (#681): opens Rearrange All Artboards; disabled with fewer than two artboards.
|
||||
fn rearrange_button(app: &mut VectorcraftApp, ui: &mut Ui, artboards: usize, w: f32) {
|
||||
if ui.add_enabled_ui(artboards > 1, |ui| widgets::flat_button(ui, tl!("Rearrange All"), w)).inner.clicked() {
|
||||
super::artboards::open_rearrange(app);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -308,6 +319,10 @@ fn document_sections(app: &mut VectorcraftApp, ui: &mut Ui) {
|
|||
app.select_tool("artboard");
|
||||
}
|
||||
});
|
||||
let artboards = app.session.active().map_or(0, |d| d.doc.artboards.len());
|
||||
if artboards > 1 {
|
||||
rearrange_button(app, ui, artboards, w);
|
||||
}
|
||||
divider(ui);
|
||||
section_header(ui, tl!("Appearance"));
|
||||
fill_stroke_rows(app, ui);
|
||||
|
|
@ -572,6 +587,26 @@ mod tests {
|
|||
assert_eq!(app.session.tool_id(), "selection");
|
||||
}
|
||||
|
||||
/// #681: Rearrange All opens Rearrange All Artboards in one click, from the Document section
|
||||
/// (only with several artboards) and from Edit Artboards' Quick Actions.
|
||||
#[test]
|
||||
fn rearrange_all_opens_the_dialog() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 200, "height": 100})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
assert!(!frame(&mut app, &ctx, vec![]).iter().any(|(t, _)| t == "Rearrange All"), "one artboard: nothing to rearrange");
|
||||
app.run("artboard.new", json!({})).unwrap();
|
||||
let texts = frame(&mut app, &ctx, vec![]);
|
||||
click(&mut app, &ctx, &texts, "Rearrange All");
|
||||
let kind = |app: &VectorcraftApp| app.ui.dialog.as_ref().map(|d| d.kind.clone());
|
||||
assert_eq!(kind(&app).as_deref(), Some(crate::dialogs::rearrange_artboards::KIND));
|
||||
app.ui.dialog = None;
|
||||
app.select_tool("artboard");
|
||||
let texts = frame(&mut app, &ctx, vec![]);
|
||||
click(&mut app, &ctx, &texts, "Rearrange All");
|
||||
assert_eq!(kind(&app).as_deref(), Some(crate::dialogs::rearrange_artboards::KIND));
|
||||
}
|
||||
|
||||
/// #671: Edit Artboards shows the artboard's preset (Custom when it matches none) and its
|
||||
/// orientation; the other orientation swaps its width and height, its corner staying put.
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -1694,6 +1694,51 @@ pub fn orientation_button(ui: &mut Ui, landscape: bool, selected: bool, tip: &st
|
|||
resp.on_hover_text(tl!(tip)).clicked()
|
||||
}
|
||||
|
||||
/// An artboard-layout toggle drawn in code (Rearrange All Artboards): small artboards in the grid
|
||||
/// cells `tiles` (column, row), tinted, and over them their order as a line from a dot on the first
|
||||
/// to an arrowhead on the last; accent-drawn in a pressed well when `selected`, greyed in a disabled
|
||||
/// `ui`. `tip` is shown as it is (the caller translates it). Returns clicked.
|
||||
pub fn layout_button(ui: &mut Ui, tiles: &[(u8, u8)], selected: bool, tip: &str) -> bool {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let (rect, resp) = ui.allocate_exact_size(Vec2::splat(26.0), Sense::click());
|
||||
let enabled = ui.is_enabled();
|
||||
let bg = if selected {
|
||||
t.tool_active
|
||||
} else if resp.hovered() && enabled {
|
||||
t.hover
|
||||
} else {
|
||||
Color32::TRANSPARENT
|
||||
};
|
||||
ui.painter().rect_filled(rect, CornerRadius::same(3), bg);
|
||||
let color = if !enabled {
|
||||
t.text_disabled
|
||||
} else if selected {
|
||||
t.accent
|
||||
} else {
|
||||
t.icon
|
||||
};
|
||||
// Cells at most 10 points a side in a 20-point square: a row's artboards stand tall, a
|
||||
// column's lie flat.
|
||||
let (cols, rows) = tiles.iter().fold((1u8, 1u8), |(c, r), &(x, y)| (c.max(x.saturating_add(1)), r.max(y.saturating_add(1))));
|
||||
let cell = vec2((20.0 / f32::from(cols)).min(10.0), (20.0 / f32::from(rows)).min(10.0));
|
||||
let origin = rect.center() - vec2(f32::from(cols) * cell.x, f32::from(rows) * cell.y) / 2.0;
|
||||
let centre = |&(x, y): &(u8, u8)| origin + vec2((f32::from(x) + 0.5) * cell.x, (f32::from(y) + 0.5) * cell.y);
|
||||
for tile in tiles {
|
||||
ui.painter().rect_filled(Rect::from_center_size(centre(tile), cell - vec2(2.0, 2.0)), 1.0, color.gamma_multiply(0.35));
|
||||
}
|
||||
let path: Vec<Pos2> = tiles.iter().map(centre).collect();
|
||||
if let ([first, ..], [.., from, to]) = (path.as_slice(), path.as_slice()) {
|
||||
let (first, from, to) = (*first, *from, *to);
|
||||
let dir = (to - from).normalized();
|
||||
let side = dir.rot90() * 2.5;
|
||||
let head = vec![to + dir * 1.5, to - dir * 2.5 + side, to - dir * 2.5 - side];
|
||||
ui.painter().circle_filled(first, 1.6, color);
|
||||
ui.painter().add(egui::Shape::line(path, Stroke::new(1.2, color)));
|
||||
ui.painter().add(egui::Shape::convex_polygon(head, color, Stroke::NONE));
|
||||
}
|
||||
resp.on_hover_text(tip).clicked()
|
||||
}
|
||||
|
||||
/// `region` of `doc` rendered on white, its longest side `px` pixels, as a texture named `name`
|
||||
/// (artboard and page thumbnails).
|
||||
pub fn region_texture(
|
||||
|
|
|
|||
Loading…
Reference in New Issue