mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 09:29:44 -06:00
#4919: Create Swatch Legend becomes an add-on under Object › Plug-ins › Color Effects with a dialog
addon.color.swatchLegend replaces swatch.legend. The dialog sets columns, chip size, gap, the values shown and whether to use only the swatches selected in the Swatches panel, with a preview; duplicate value kinds are refused. The Swatches panel menu item is removed. Catalog rows in all 26 languages.
This commit is contained in:
@@ -84,7 +84,6 @@ mod stroke;
|
||||
mod style;
|
||||
pub mod stylelib;
|
||||
mod swatch;
|
||||
pub mod swatchlegend;
|
||||
pub mod swatchlib;
|
||||
pub(crate) mod symboledit;
|
||||
pub mod symbollib;
|
||||
@@ -298,7 +297,6 @@ pub fn command_specs() -> &'static [CommandSpec] {
|
||||
v.extend(paint::proxy_specs());
|
||||
v.extend(overprint::specs());
|
||||
v.extend(swatchlib::specs());
|
||||
v.extend(swatchlegend::specs());
|
||||
v.extend(freeform::specs());
|
||||
v.extend(flatten::specs());
|
||||
v.extend(stylelib::specs());
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
//! Duotone and Invert Colors, in Effect › Color Effects) are the effects crate's; here, Apply as
|
||||
//! Filter writes any colour adjustment into the colours themselves, Select by Color Kind selects the
|
||||
//! objects that use spot colours, carry a live colour adjustment, or are images of a colour model,
|
||||
//! Print Ink Fixes ([`print_fixes`]) prepares a CMYK document's inks for print, and Find Nearest
|
||||
//! Swatch ([`nearest`]) matches colours to a swatch library's. Its panel and its submenu, Object ›
|
||||
//! Print Ink Fixes ([`print_fixes`]) prepares a CMYK document's inks for print, Find Nearest
|
||||
//! Swatch ([`nearest`]) matches colours to a swatch library's, and Create Swatch Legend
|
||||
//! ([`legend`]) draws labelled chips of the document's swatches. Its panel and its submenu, Object ›
|
||||
//! Plug-ins › Color Effects, are the UI's.
|
||||
|
||||
pub mod legend;
|
||||
mod nearest;
|
||||
mod print_fixes;
|
||||
#[cfg(test)]
|
||||
@@ -71,6 +73,9 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
nearest::apply
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(legend::specs())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn select(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
||||
+22
-12
@@ -1,26 +1,31 @@
|
||||
//! Create Swatch Legend (#4919): a grid of chips on the artboard, one per colour swatch, each
|
||||
//! filled with its swatch and labelled with the swatch's name and colour values, as one group made
|
||||
//! in one undo step.
|
||||
//! Create Swatch Legend (`addon.color.swatchLegend`; Object › Plug-ins › Color Effects › Create
|
||||
//! Swatch Legend…, #4919): a grid of chips on the artboard, one per colour swatch, each filled with
|
||||
//! its swatch and labelled with the swatch's name and colour values, as one group made in one undo
|
||||
//! step.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_color::{Color, Paint, cms::Model};
|
||||
use vectorcraft_doc::{Appearance, AreaFit, Node, NodeKind, TextKind, TextObject};
|
||||
use vectorcraft_geom::{Point, Rect, shapes};
|
||||
|
||||
use super::{CommandSpec, Result, Session, bad, cmd, has_doc};
|
||||
use super::super::super::{CommandSpec, bad, cmd, has_doc};
|
||||
use crate::{Result, Session};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![cmd!(
|
||||
"swatch.legend",
|
||||
"addon.color.swatchLegend",
|
||||
"Create Swatch Legend",
|
||||
["Window", "Swatches"],
|
||||
["Object", "Plug-ins", "Color Effects"],
|
||||
None,
|
||||
"{names?: [swatch name…] (default: every colour swatch of the document, in panel order), columns?: 1..50 (4), size?: chip side in pt 8..1000 (72), gap?: pt 0..1000 (12), values?: [hex|rgb|cmyk|lab|gray…] (default: each swatch's own colour model; spot swatches also say spot), x?, y? (the legend's top-left; default: the first artboard's, inset by `gap`)} draw a legend of colour swatches: one square chip per swatch filled with it (linked to it), labelled inside with the swatch name and the values asked for, in black on light chips and white on dark ones, as one group in one undo step; the group is selected → {id, count} (#4919)",
|
||||
"{names?: [swatch name…] (default: every colour swatch of the document, in panel order), columns?: 1..50 (4), size?: chip side in pt 8..1000 (72), gap?: pt 0..1000 (12), values?: [hex|rgb|cmyk|lab|gray…] (each at most once; default: each swatch's own colour model; spot swatches also say spot), x?, y? (the legend's top-left; default: the first artboard's, inset by `gap`)} draw a legend of colour swatches: one square chip per swatch filled with it (linked to it), labelled inside with the swatch name and the values asked for, in black on light chips and white on dark ones, as one group in one undo step; the group is selected → {id, count} (#4919)",
|
||||
has_doc,
|
||||
legend
|
||||
)]
|
||||
}
|
||||
|
||||
/// The command.
|
||||
pub const COMMAND: &str = "addon.color.swatchLegend";
|
||||
|
||||
/// Most swatches one legend draws.
|
||||
const MAX_CHIPS: usize = 2000;
|
||||
const VALUES: [&str; 5] = ["hex", "rgb", "cmyk", "lab", "gray"];
|
||||
@@ -78,7 +83,7 @@ fn value_lines(c: Color, values: &[String], spot: bool) -> Vec<String> {
|
||||
}
|
||||
|
||||
fn legend(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "swatch.legend";
|
||||
const C: &str = COMMAND;
|
||||
let columns = number(p, "columns", 4.0, 1.0..=50.0, C)?;
|
||||
if columns.fract() != 0.0 {
|
||||
return Err(bad(C, "`columns` must be a whole number 1..50"));
|
||||
@@ -96,6 +101,10 @@ fn legend(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
.collect::<Result<_>>()?,
|
||||
Some(_) => return Err(bad(C, "`values` must be a list")),
|
||||
};
|
||||
// Each kind once: every value line goes into every chip, so repeats would only multiply them.
|
||||
if values.iter().enumerate().any(|(i, v)| values[..i].contains(v)) {
|
||||
return Err(bad(C, "`values` names a kind twice"));
|
||||
}
|
||||
let st = s.doc()?;
|
||||
let doc = &st.doc;
|
||||
let colour = |w: &&vectorcraft_color::Swatch| w.paint.color().is_some();
|
||||
@@ -128,11 +137,12 @@ fn legend(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let parent = st.target_parent()?;
|
||||
let text_size = (size / 7.0).clamp(4.0, 12.0);
|
||||
let pad = (size / 12.0).clamp(1.0, 8.0);
|
||||
let style = super::create::new_type_style(s, &json!({ "size": text_size }));
|
||||
let style = super::super::super::create::new_type_style(s, &json!({ "size": text_size }));
|
||||
let count = swatches.len();
|
||||
// Each chip's fill links to its swatch, as paint.setFill {swatch} links art: editing a global or
|
||||
// spot swatch then recolours the legend too.
|
||||
let fills: Vec<Paint> = swatches.iter().map(|w| super::paint::swatch_solid(doc, &w.name, None).unwrap_or_else(|_| w.paint.clone())).collect();
|
||||
let fills: Vec<Paint> =
|
||||
swatches.iter().map(|w| super::super::super::paint::swatch_solid(doc, &w.name, None).unwrap_or_else(|_| w.paint.clone())).collect();
|
||||
let gid = s.edit("Create Swatch Legend", |d, sel| {
|
||||
let mut children = Vec::with_capacity(count * 2);
|
||||
for (i, (w, fill)) in swatches.iter().zip(fills).enumerate() {
|
||||
@@ -154,9 +164,9 @@ fn legend(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let inner = (size - 2.0 * pad).max(1.0);
|
||||
t.kind = TextKind::Area { frame: shapes::rectangle(Rect::new(0.0, 0.0, inner, inner)) };
|
||||
t.area.fit = AreaFit::ShrinkText { min_percent: AreaFit::MIN_PERCENT };
|
||||
t.para = super::create::new_type_para();
|
||||
t.para = super::super::super::create::new_type_para();
|
||||
t.system_layout = system_layout;
|
||||
super::typecmd::refresh_bounds(&mut t);
|
||||
super::super::super::typecmd::refresh_bounds(&mut t);
|
||||
children.push(std::sync::Arc::new(Node::new(d.alloc_id(), NodeKind::Text(Box::new(t)))));
|
||||
}
|
||||
let gid = d.alloc_id();
|
||||
+8
-4
@@ -33,7 +33,7 @@ fn a_legend_draws_a_labelled_chip_per_colour_swatch_in_one_undo_step() {
|
||||
s.execute("swatch.new", &json!({"name": "Paper", "color": "#f8f4e8"})).unwrap();
|
||||
let colours = s.doc().unwrap().doc.swatches_iter().filter(|w| w.paint.color().is_some()).count();
|
||||
let undo = s.doc().unwrap().history.undo.len();
|
||||
let r = s.execute("swatch.legend", &json!({"columns": 3, "size": 60, "gap": 10})).unwrap();
|
||||
let r = s.execute("addon.color.swatchLegend", &json!({"columns": 3, "size": 60, "gap": 10})).unwrap();
|
||||
assert_eq!(r["count"].as_u64().unwrap() as usize, colours);
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), undo + 1, "one undo step");
|
||||
assert_eq!(s.doc().unwrap().selection.objects, [NodeId(r["id"].as_u64().unwrap())], "the legend is selected");
|
||||
@@ -65,7 +65,8 @@ fn a_legend_draws_a_labelled_chip_per_colour_swatch_in_one_undo_step() {
|
||||
fn a_legend_takes_names_values_and_a_position() {
|
||||
let mut s = session();
|
||||
s.execute("swatch.new", &json!({"name": "Signal", "color": "cmyk 0 100 100 0", "spot": true})).unwrap();
|
||||
let r = s.execute("swatch.legend", &json!({"names": ["Signal"], "values": ["hex", "cmyk", "lab", "gray"], "x": 100, "y": 50})).unwrap();
|
||||
let r =
|
||||
s.execute("addon.color.swatchLegend", &json!({"names": ["Signal"], "values": ["hex", "cmyk", "lab", "gray"], "x": 100, "y": 50})).unwrap();
|
||||
assert_eq!(r["count"], 1);
|
||||
let g = group(&s, r["id"].as_u64().unwrap());
|
||||
let (text, _) = labels(&g).remove(0);
|
||||
@@ -87,8 +88,9 @@ fn a_legend_takes_names_values_and_a_position() {
|
||||
json!({"gap": -1}),
|
||||
json!({"x": f64::MAX}),
|
||||
json!({"names": []}),
|
||||
json!({"values": ["rgb", "rgb"]}),
|
||||
] {
|
||||
assert!(s.execute("swatch.legend", &bad).is_err(), "{bad}");
|
||||
assert!(s.execute("addon.color.swatchLegend", &bad).is_err(), "{bad}");
|
||||
}
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), before);
|
||||
}
|
||||
@@ -99,7 +101,9 @@ fn a_legend_takes_names_values_and_a_position() {
|
||||
fn chips_stay_linked_and_labels_stay_on_their_chips() {
|
||||
let mut s = session();
|
||||
s.execute("swatch.new", &json!({"name": "A Rather Long Brand Colour Name", "color": "#2050a0", "global": true})).unwrap();
|
||||
let r = s.execute("swatch.legend", &json!({"names": ["A Rather Long Brand Colour Name"], "size": 40, "values": ["hex", "rgb", "cmyk"]})).unwrap();
|
||||
let r = s
|
||||
.execute("addon.color.swatchLegend", &json!({"names": ["A Rather Long Brand Colour Name"], "size": 40, "values": ["hex", "rgb", "cmyk"]}))
|
||||
.unwrap();
|
||||
let id = r["id"].as_u64().unwrap();
|
||||
s.execute("swatch.edit", &json!({"name": "A Rather Long Brand Colour Name", "color": "#a02050"})).unwrap();
|
||||
let g = group(&s, id);
|
||||
@@ -3,8 +3,10 @@
|
||||
//! filter, into the colours themselves (`addon.color.filter`); below, under Inks, it opens Duotone's
|
||||
//! dialog and Print Ink Fixes' (`addon.color.printFixes`). The submenu applies the add-on's live
|
||||
//! effects, Duotone and Vector Halftone among them (Effect › Color Effects has them too), opens
|
||||
//! Print Ink Fixes and Find Nearest Swatch ([`nearest`]) and opens the panel.
|
||||
//! Print Ink Fixes, Find Nearest Swatch ([`nearest`]) and Create Swatch Legend ([`legend`]) and
|
||||
//! opens the panel.
|
||||
|
||||
pub mod legend;
|
||||
pub mod nearest;
|
||||
|
||||
use egui::Ui;
|
||||
@@ -100,6 +102,7 @@ pub fn menu() -> Vec<Item> {
|
||||
Item::Sep,
|
||||
cmd(print_fixes()),
|
||||
Item::Cmd("Find Nearest Swatch…", nearest::COMMAND, Value::Null),
|
||||
Item::Cmd("Create Swatch Legend…", legend::COMMAND, Value::Null),
|
||||
Item::Sep,
|
||||
Item::Sub(
|
||||
"Select by Color Kind",
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
//! Create Swatch Legend in the interface (Object › Plug-ins › Color Effects › Create Swatch
|
||||
//! Legend…, #4919): a dialog for the columns, the chip size, the gap and which colour values the
|
||||
//! labels show, and whether only the swatches selected in the Swatches panel are drawn. It
|
||||
//! previews on the canvas and runs `addon.color.swatchLegend` on OK.
|
||||
|
||||
use egui::Ui;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::VectorcraftApp;
|
||||
use crate::dialogs::{DialogSpec, form};
|
||||
use crate::state::Dialog;
|
||||
use crate::widgets;
|
||||
|
||||
/// The command, and its dialog's kind.
|
||||
pub const COMMAND: &str = "addon.color.swatchLegend";
|
||||
pub const KIND: &str = "swatchLegend";
|
||||
const LABEL_W: f32 = 80.0;
|
||||
const FIELD_W: f32 = 90.0;
|
||||
/// The values a label can show: (the command's name, the dialog's label).
|
||||
pub const VALUES: [(&str, &str); 5] = [("hex", "HEX"), ("rgb", "RGB"), ("cmyk", "CMYK"), ("lab", "Lab"), ("gray", "Grayscale")];
|
||||
|
||||
/// The dialog's fields to start with: the command's defaults, each swatch's own values.
|
||||
pub(crate) fn fields() -> Value {
|
||||
json!({ "columns": 4, "size": 72, "gap": 12, "selectedOnly": false })
|
||||
}
|
||||
|
||||
pub(crate) const SPEC: DialogSpec =
|
||||
DialogSpec { heading: |_| tl!("Create Swatch Legend").to_string(), body, confirm, preview: true, min_width: 300.0, ..DialogSpec::FORM };
|
||||
|
||||
/// The colour swatches selected in the Swatches panel.
|
||||
fn selected_swatches(app: &VectorcraftApp, ctx: &egui::Context) -> Vec<String> {
|
||||
let picked: Vec<String> = crate::panels::pstate(ctx, "swatch-selected");
|
||||
app.session
|
||||
.active()
|
||||
.map(|st| st.doc.swatches_iter().filter(|w| w.paint.color().is_some() && picked.contains(&w.name)).map(|w| w.name.clone()).collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The command's params from the dialog's fields.
|
||||
pub(crate) fn params(d: &Dialog) -> Value {
|
||||
let mut p = json!({ "columns": d.f64("columns", 4.0).round(), "size": d.f64("size", 72.0), "gap": d.f64("gap", 12.0) });
|
||||
let values: Vec<&str> = VALUES.iter().filter(|(v, _)| d.bool(v)).map(|(v, _)| *v).collect();
|
||||
if !values.is_empty() {
|
||||
p["values"] = json!(values);
|
||||
}
|
||||
if d.bool("selectedOnly")
|
||||
&& let Some(names) = d.fields.get("names").filter(|n| n.as_array().is_some_and(|a| !a.is_empty()))
|
||||
{
|
||||
p["names"] = names.clone();
|
||||
}
|
||||
p
|
||||
}
|
||||
|
||||
fn body(app: &mut VectorcraftApp, ui: &mut Ui, d: &mut Dialog) -> bool {
|
||||
let unit = app.session.general_unit();
|
||||
widgets::label_row(ui, tl!("Columns:"), LABEL_W, |ui| {
|
||||
if let Some(v) = widgets::range_field(ui, ("swatch-legend", "columns"), d.f64("columns", 4.0), 1.0..=50.0, "", 0, FIELD_W) {
|
||||
d.fields.insert("columns".into(), json!(v.round()));
|
||||
}
|
||||
});
|
||||
widgets::label_row(ui, tl!("Size:"), LABEL_W, |ui| {
|
||||
form::length_within(ui, d, "size", unit, FIELD_W, Some(8.0..=1000.0));
|
||||
});
|
||||
widgets::label_row(ui, tl!("Gap:"), LABEL_W, |ui| {
|
||||
form::length_within(ui, d, "gap", unit, FIELD_W, Some(0.0..=1000.0));
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
// None ticked: each swatch shows the values of its own colour model.
|
||||
ui.label(tl!("Show:"));
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for (v, label) in VALUES {
|
||||
form::check(ui, d, v, tl!(label));
|
||||
}
|
||||
});
|
||||
// Only the swatches selected in the Swatches panel, when some are.
|
||||
let names = selected_swatches(app, ui.ctx());
|
||||
if names.is_empty() {
|
||||
d.fields.insert("selectedOnly".into(), json!(false));
|
||||
} else {
|
||||
form::check(ui, d, "selectedOnly", tl!("Selected Swatches Only"));
|
||||
}
|
||||
d.fields.insert("names".into(), json!(names));
|
||||
form::preview(app, ui, d, "Create Swatch Legend", COMMAND, params(d));
|
||||
false
|
||||
}
|
||||
|
||||
/// OK: run the command (keep its preview) and close.
|
||||
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
form::commit_preview(app, COMMAND, params(d))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -0,0 +1,58 @@
|
||||
use serde_json::json;
|
||||
use vectorcraft_engine::Session;
|
||||
|
||||
use crate::tests_labels::painted_text;
|
||||
use crate::{VectorcraftApp, menus};
|
||||
|
||||
/// #4919: Object › Plug-ins › Color Effects › Create Swatch Legend… opens a dialog with the
|
||||
/// columns, chip size, gap and values; it previews, Cancel rolls it back, and OK draws the legend
|
||||
/// with the values picked as one undo step. With swatches selected in the Swatches panel, it can
|
||||
/// draw only those.
|
||||
#[test]
|
||||
fn create_swatch_legend_opens_its_dialog_previews_and_runs_on_ok() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 600, "height": 400})).unwrap();
|
||||
app.run("swatch.new", json!({"name": "Ink", "color": "#123456"})).unwrap();
|
||||
let entries = menus::menu_entries(&app);
|
||||
let item = entries.iter().find(|e| e.path == ["Object", "Plug-ins", "Color Effects"] && e.label == "Create Swatch Legend…").expect("menu item");
|
||||
let (cmd, p) = menus::click_target(&item.label, item.command.as_deref().unwrap(), &item.params);
|
||||
menus::invoke(&mut app, &cmd, p);
|
||||
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some(super::KIND));
|
||||
let shown = painted_text(&mut app, |app, ui| crate::dialogs::show(app, ui.ctx()));
|
||||
for s in ["Create Swatch Legend", "Columns:", "Size:", "Gap:", "HEX", "CMYK", "Grayscale", "Preview"] {
|
||||
assert!(shown.contains(s), "{s} in {shown}");
|
||||
}
|
||||
let count = |app: &VectorcraftApp| app.session.doc().unwrap().doc.layers[0].children().map_or(0, Vec::len);
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert("preview".into(), json!(true));
|
||||
painted_text(&mut app, |app, ui| crate::dialogs::show(app, ui.ctx()));
|
||||
assert_eq!(count(&app), 1, "previewed: one legend group");
|
||||
crate::dialogs::cancel(&mut app);
|
||||
assert_eq!(count(&app), 0, "rolled back");
|
||||
// OK with HEX and CMYK and 2 columns.
|
||||
menus::invoke(&mut app, &cmd, serde_json::Value::Null);
|
||||
let n = app.session.doc().unwrap().history.undo.len();
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
for (k, v) in [("hex", json!(true)), ("cmyk", json!(true)), ("columns", json!(2))] {
|
||||
d.fields.insert(k.into(), v);
|
||||
}
|
||||
crate::dialogs::confirm(&mut app).unwrap();
|
||||
assert!(app.ui.dialog.is_none());
|
||||
assert_eq!(app.session.doc().unwrap().history.undo.len(), n + 1);
|
||||
let st = app.session.doc().unwrap();
|
||||
let g = st.doc.layers[0].children().unwrap().last().unwrap();
|
||||
let label = g.children().unwrap().iter().find_map(|c| match &c.kind {
|
||||
vectorcraft_doc::NodeKind::Text(t) if t.plain_text().starts_with("Ink") => Some(t.plain_text()),
|
||||
_ => None,
|
||||
});
|
||||
let label = label.expect("Ink's label");
|
||||
assert!(label.contains("#123456") && label.contains("C="), "{label}");
|
||||
// Only the swatch selected in the Swatches panel.
|
||||
let ctx = egui::Context::default();
|
||||
crate::panels::set_pstate(&ctx, "swatch-selected", vec!["Ink".to_string()]);
|
||||
assert_eq!(super::selected_swatches(&app, &ctx), ["Ink"]);
|
||||
let mut d = crate::state::Dialog::new(super::KIND, super::fields());
|
||||
d.fields.insert("selectedOnly".into(), json!(true));
|
||||
d.fields.insert("names".into(), json!(["Ink"]));
|
||||
assert_eq!(super::params(&d)["names"], json!(["Ink"]));
|
||||
}
|
||||
@@ -407,6 +407,7 @@ registry! {
|
||||
PathToolOptions: [crate::addons::pathtools::KIND] => crate::addons::pathtools::OPTIONS_SPEC,
|
||||
DivideDashes: [crate::addons::pathtools::dashes::KIND] => crate::addons::pathtools::dashes::SPEC,
|
||||
NearestSwatch: [crate::addons::color::nearest::KIND] => crate::addons::color::nearest::SPEC,
|
||||
SwatchLegend: [crate::addons::color::legend::KIND] => crate::addons::color::legend::SPEC,
|
||||
ArrangeTools: [crate::addons::arrange::GRID, crate::addons::arrange::REPLACE, crate::addons::arrange::SIZE, crate::addons::arrange::OPACITY, crate::addons::arrange::SORT] => crate::addons::arrange::SPEC,
|
||||
ShortcutImport: [shortcut_import::KIND] => DialogSpec::window(shortcut_import::show, shortcut_import::confirm),
|
||||
}
|
||||
|
||||
@@ -6091,3 +6091,6 @@ breakText Glyphs Глифи
|
||||
Small to Large От малко към голямо
|
||||
Large to Small От голямо към малко
|
||||
Distribute Evenly Равномерно разпределение
|
||||
Create Swatch Legend Създаване на легенда на образците
|
||||
Create Swatch Legend… Създаване на легенда на образците…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6122,3 +6122,6 @@ shape Open Otvoren
|
||||
Small to Large Od manjeg prema većem
|
||||
Large to Small Od većeg prema manjem
|
||||
Distribute Evenly Rasporedi ravnomjerno
|
||||
Create Swatch Legend Kreiraj legendu uzoraka boja
|
||||
Create Swatch Legend… Kreiraj legendu uzoraka boja…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6216,3 +6216,6 @@ breakText Glyphs Glifs
|
||||
Small to Large De petit a gran
|
||||
Large to Small De gran a petit
|
||||
Distribute Evenly Distribueix uniformement
|
||||
Create Swatch Legend Crea una llegenda de mostres
|
||||
Create Swatch Legend… Crea una llegenda de mostres…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6228,3 +6228,6 @@ breakText Glyphs Glyfy
|
||||
Small to Large Od nejmenšího po největší
|
||||
Large to Small Od největšího po nejmenší
|
||||
Distribute Evenly Rozmístit rovnoměrně
|
||||
Create Swatch Legend Vytvořit legendu vzorníku
|
||||
Create Swatch Legend… Vytvořit legendu vzorníku…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6106,3 +6106,6 @@ breakText Glyphs Glyffer
|
||||
Small to Large Fra lille til stor
|
||||
Large to Small Fra stor til lille
|
||||
Distribute Evenly Fordel jævnt
|
||||
Create Swatch Legend Opret farveprøveforklaring
|
||||
Create Swatch Legend… Opret farveprøveforklaring…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5814,3 +5814,6 @@ breakText Glyphs Glyphen
|
||||
Small to Large Von klein nach groß
|
||||
Large to Small Von groß nach klein
|
||||
Distribute Evenly Gleichmäßig verteilen
|
||||
Create Swatch Legend Farbfeldlegende erstellen
|
||||
Create Swatch Legend… Farbfeldlegende erstellen…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6180,3 +6180,6 @@ breakText Glyphs Γλύφοι
|
||||
Small to Large Από μικρό σε μεγάλο
|
||||
Large to Small Από μεγάλο σε μικρό
|
||||
Distribute Evenly Ομοιόμορφη κατανομή
|
||||
Create Swatch Legend Δημιουργία υπομνήματος δειγμάτων χρωμάτων
|
||||
Create Swatch Legend… Δημιουργία υπομνήματος δειγμάτων χρωμάτων…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 163 and column 5.
|
@@ -6170,3 +6170,6 @@ breakText Glyphs Glifos
|
||||
Small to Large De menor a mayor
|
||||
Large to Small De mayor a menor
|
||||
Distribute Evenly Distribuir uniformemente
|
||||
Create Swatch Legend Crear leyenda de muestras
|
||||
Create Swatch Legend… Crear leyenda de muestras…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6258,3 +6258,6 @@ breakText Glyphs Merkit
|
||||
Small to Large Pienestä suureen
|
||||
Large to Small Suuresta pieneen
|
||||
Distribute Evenly Jaa tasaisesti
|
||||
Create Swatch Legend Luo värimallien selite
|
||||
Create Swatch Legend… Luo värimallien selite…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6223,3 +6223,6 @@ breakText Glyphs Glyphes
|
||||
Small to Large Du plus petit au plus grand
|
||||
Large to Small Du plus grand au plus petit
|
||||
Distribute Evenly Répartir uniformément
|
||||
Create Swatch Legend Créer une légende des nuances
|
||||
Create Swatch Legend… Créer une légende des nuances…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6212,3 +6212,6 @@ breakText Glyphs Glifák
|
||||
Small to Large Kicsitől a nagyig
|
||||
Large to Small Nagytól a kicsiig
|
||||
Distribute Evenly Egyenletes elosztás
|
||||
Create Swatch Legend Színmintajelmagyarázat létrehozása
|
||||
Create Swatch Legend… Színmintajelmagyarázat létrehozása…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6106,3 +6106,6 @@ breakText Glyphs Glif
|
||||
Small to Large Kecil ke besar
|
||||
Large to Small Besar ke kecil
|
||||
Distribute Evenly Distribusikan merata
|
||||
Create Swatch Legend Buat Legenda Swatch
|
||||
Create Swatch Legend… Buat Legenda Swatch…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2206 and column 5.
|
@@ -5848,3 +5848,6 @@ breakText Glyphs Glifi
|
||||
Small to Large Dal più piccolo al più grande
|
||||
Large to Small Dal più grande al più piccolo
|
||||
Distribute Evenly Distribuisci uniformemente
|
||||
Create Swatch Legend Crea legenda campioni
|
||||
Create Swatch Legend… Crea legenda campioni…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5913,3 +5913,6 @@ breakText Glyphs 字形
|
||||
Small to Large 小さい順
|
||||
Large to Small 大きい順
|
||||
Distribute Evenly 均等に分布
|
||||
Create Swatch Legend スウォッチの凡例を作成
|
||||
Create Swatch Legend… スウォッチの凡例を作成…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6216,3 +6216,6 @@ breakText Glyphs გლიფები
|
||||
Small to Large პატარიდან დიდისკენ
|
||||
Large to Small დიდიდან პატარისკენ
|
||||
Distribute Evenly თანაბრად განაწილება
|
||||
Create Swatch Legend ფერის ნიმუშების ლეგენდის შექმნა
|
||||
Create Swatch Legend… ფერის ნიმუშების ლეგენდის შექმნა…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -5809,3 +5809,6 @@ breakText Glyphs 글리프
|
||||
Small to Large 작은 것부터
|
||||
Large to Small 큰 것부터
|
||||
Distribute Evenly 균등 분포
|
||||
Create Swatch Legend 견본 범례 만들기
|
||||
Create Swatch Legend… 견본 범례 만들기…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6150,3 +6150,6 @@ breakText Glyphs Glyphs
|
||||
Small to Large Van klein naar groot
|
||||
Large to Small Van groot naar klein
|
||||
Distribute Evenly Gelijkmatig verdelen
|
||||
Create Swatch Legend Stalenlegenda maken
|
||||
Create Swatch Legend… Stalenlegenda maken…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6083,3 +6083,6 @@ breakText Glyphs Glify
|
||||
Small to Large Od najmniejszego do największego
|
||||
Large to Small Od największego do najmniejszego
|
||||
Distribute Evenly Rozmieść równomiernie
|
||||
Create Swatch Legend Utwórz legendę próbek
|
||||
Create Swatch Legend… Utwórz legendę próbek…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6301,3 +6301,6 @@ swatch Yellow Green Verde-amarelado
|
||||
Small to Large Do menor para o maior
|
||||
Large to Small Do maior para o menor
|
||||
Distribute Evenly Distribuir uniformemente
|
||||
Create Swatch Legend Criar Legenda de Amostras
|
||||
Create Swatch Legend… Criar Legenda de Amostras…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -6205,3 +6205,6 @@ breakText Glyphs Глифы
|
||||
Small to Large От меньшего к большему
|
||||
Large to Small От большего к меньшему
|
||||
Distribute Evenly Распределить равномерно
|
||||
Create Swatch Legend Создать легенду образцов
|
||||
Create Swatch Legend… Создать легенду образцов…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6280,3 +6280,6 @@ breakText Glyphs Glyfer
|
||||
Small to Large Från liten till stor
|
||||
Large to Small Från stor till liten
|
||||
Distribute Evenly Fördela jämnt
|
||||
Create Swatch Legend Skapa färgrutelegend
|
||||
Create Swatch Legend… Skapa färgrutelegend…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -6185,3 +6185,6 @@ breakText Glyphs Glifler
|
||||
Small to Large Küçükten Büyüğe
|
||||
Large to Small Büyükten Küçüğe
|
||||
Distribute Evenly Eşit Dağıt
|
||||
Create Swatch Legend Renk Örneği Açıklaması Oluştur
|
||||
Create Swatch Legend… Renk Örneği Açıklaması Oluştur…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -5828,3 +5828,6 @@ breakText Glyphs Гліфи
|
||||
Small to Large Від меншого до більшого
|
||||
Large to Small Від більшого до меншого
|
||||
Distribute Evenly Розподілити рівномірно
|
||||
Create Swatch Legend Створити легенду зразків
|
||||
Create Swatch Legend… Створити легенду зразків…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 177 and column 5.
|
@@ -5829,3 +5829,6 @@ breakText Glyphs Glyph
|
||||
Small to Large Từ nhỏ đến lớn
|
||||
Large to Small Từ lớn đến nhỏ
|
||||
Distribute Evenly Phân bố đều
|
||||
Create Swatch Legend Tạo chú giải mẫu màu
|
||||
Create Swatch Legend… Tạo chú giải mẫu màu…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 199 and column 5.
|
@@ -6069,3 +6069,6 @@ breakText Glyphs 字形
|
||||
Small to Large 从小到大
|
||||
Large to Small 从大到小
|
||||
Distribute Evenly 均匀分布
|
||||
Create Swatch Legend 创建色板图例
|
||||
Create Swatch Legend… 创建色板图例…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5866,3 +5866,6 @@ breakText Glyphs 字符
|
||||
Small to Large 從小到大
|
||||
Large to Small 從大到小
|
||||
Distribute Evenly 均勻分佈
|
||||
Create Swatch Legend 建立色票圖例
|
||||
Create Swatch Legend… 建立色票圖例…
|
||||
HEX HEX
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -70,6 +70,7 @@ pub(super) fn menu_dialog(app: &VectorcraftApp, id: &str) -> Option<(&'static st
|
||||
"object.vectorHalftone" => (crate::dialogs::halftone::KIND, crate::dialogs::halftone::fields()),
|
||||
crate::addons::pathtools::dashes::COMMAND => (crate::addons::pathtools::dashes::KIND, crate::addons::pathtools::dashes::fields()),
|
||||
crate::addons::color::nearest::COMMAND => (crate::addons::color::nearest::KIND, crate::addons::color::nearest::fields()),
|
||||
crate::addons::color::legend::COMMAND => (crate::addons::color::legend::KIND, crate::addons::color::legend::fields()),
|
||||
"artboard.rearrange" => (crate::dialogs::rearrange_artboards::KIND, crate::dialogs::rearrange_artboards::open_fields(app)),
|
||||
_ => return None,
|
||||
})
|
||||
|
||||
@@ -164,19 +164,6 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
if menu_item(ui, tl!("Add Used Colors"), true, false) {
|
||||
app.run("swatch.addUsedColors", json!({})).ok();
|
||||
}
|
||||
// Create Swatch Legend (#4919): the selected colour swatches, else every one.
|
||||
if menu_item(ui, tl!("Create Swatch Legend"), app.session.active().is_some(), false) {
|
||||
let picked = deletable(&selected);
|
||||
let colours: Vec<String> = app
|
||||
.session
|
||||
.active()
|
||||
.map(|st| st.doc.swatches_iter().filter(|w| w.paint.color().is_some() && picked.contains(&w.name)).map(|w| w.name.clone()).collect())
|
||||
.unwrap_or_default();
|
||||
let p = if colours.is_empty() { json!({}) } else { json!({ "names": colours }) };
|
||||
if let Err(e) = app.run("swatch.legend", p) {
|
||||
app.status(e);
|
||||
}
|
||||
}
|
||||
ui.separator();
|
||||
if menu_item(ui, tl!("Sort by Name"), true, false) {
|
||||
app.run("swatch.sortByName", json!({})).ok();
|
||||
|
||||
@@ -770,40 +770,3 @@ fn lab_spot_tiles_show_the_colour_the_spot_colors_options_pick() {
|
||||
assert_eq!(tile(&app), app.session.active().unwrap().doc.global_color("Ink").unwrap());
|
||||
assert!(matches!(tile(&app), Color::Cmyk { .. }));
|
||||
}
|
||||
|
||||
/// One frame of the panel menu at `time`; its painted labels.
|
||||
fn menu_labels(app: &mut VectorcraftApp, ctx: &egui::Context, events: Vec<Event>, time: f64) -> Vec<(String, Rect)> {
|
||||
let input =
|
||||
egui::RawInput { events, time: Some(time), screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(280.0, 900.0))), ..Default::default() };
|
||||
let mut out = ctx.run_ui(input, |ui| menu(app, ui));
|
||||
out.textures_delta.clear();
|
||||
crate::tests_removeanchors::shapes_text(&out.shapes.iter().map(|c| c.shape.clone()).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
/// #4919: the menu's Create Swatch Legend draws the selected colour swatches, or every one when
|
||||
/// none is selected.
|
||||
#[test]
|
||||
fn create_swatch_legend_draws_the_selected_swatches_or_all() {
|
||||
let mut app = app();
|
||||
app.run("swatch.new", json!({"name": "Ink 185", "color": "#e4002b", "spot": true})).unwrap();
|
||||
let ctx = context();
|
||||
let click = |app: &mut VectorcraftApp, ctx: &egui::Context, t: f64| {
|
||||
let labels = menu_labels(app, ctx, vec![], t);
|
||||
let at = labels.iter().find(|(l, _)| l.trim() == "Create Swatch Legend").map(|(_, r)| r.center()).expect("Create Swatch Legend");
|
||||
let button = |pressed| Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Default::default() };
|
||||
menu_labels(app, ctx, vec![Event::PointerMoved(at), button(true)], t + 0.1);
|
||||
menu_labels(app, ctx, vec![button(false)], t + 0.2);
|
||||
};
|
||||
let count = |app: &VectorcraftApp| {
|
||||
let st = app.session.active().unwrap();
|
||||
let id = *st.selection.objects.first().expect("the legend is selected");
|
||||
st.doc.node(id).unwrap().children().unwrap().len() / 2
|
||||
};
|
||||
set_pstate(&ctx, "swatch-selected", vec!["Ink 185".to_string(), "[None]".to_string()]);
|
||||
click(&mut app, &ctx, 0.0);
|
||||
assert_eq!(count(&app), 1, "the selected colour swatch alone");
|
||||
set_pstate(&ctx, "swatch-selected", Vec::<String>::new());
|
||||
click(&mut app, &ctx, 1.0);
|
||||
let colours = app.session.active().unwrap().doc.swatches_iter().filter(|w| w.paint.color().is_some()).count();
|
||||
assert_eq!(count(&app), colours, "every colour swatch");
|
||||
}
|
||||
|
||||
+5
-4
@@ -2108,16 +2108,17 @@ swatch in the library panel does). The pattern library Geometric Patterns (`geom
|
||||
{"name":"run_command","arguments":{"command":"swatch.library.get","params":{"library":"earth-tones"}}}
|
||||
{"name":"run_command","arguments":{"command":"swatch.library.add","params":{"library":"earth-tones","names":["Clay"]}}}
|
||||
```
|
||||
`swatch.legend {names?, columns?, size?, gap?, values?, x?, y?}` draws a legend of colour swatches on the page as one
|
||||
`addon.color.swatchLegend {names?, columns?, size?, gap?, values?, x?, y?}` draws a legend of colour swatches on the page as one
|
||||
group, in one undo step, and selects it: a square chip per swatch (`size` pt, 72 by default, `columns` to a row, 4 by
|
||||
default, `gap` pt apart, 12 by default) filled with the swatch and linked to it, labelled inside with the swatch's name
|
||||
and its values (`values`: any of `hex`, `rgb`, `cmyk`, `lab`, `gray`; by default the swatch's own colour model; spot
|
||||
swatches also say Spot), in black on light chips and white on dark ones. `names` picks swatches (default: every colour
|
||||
swatch, in panel order); `x`, `y` place the top-left (default: the first artboard's, inset by `gap`). The Swatches
|
||||
panel menu's Create Swatch Legend draws the selected colour swatches, else every one (#4919). → `{id, count}`
|
||||
swatch, in panel order); `x`, `y` place the top-left (default: the first artboard's, inset by `gap`). Object ›
|
||||
Plug-ins › Color Effects › Create Swatch Legend… sets these in a dialog with a preview, and can limit it to the
|
||||
swatches selected in the Swatches panel (#4919). → `{id, count}`
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"swatch.legend","params":{"columns":6,"values":["hex","cmyk"]}}}
|
||||
{"name":"run_command","arguments":{"command":"addon.color.swatchLegend","params":{"columns":6,"values":["hex","cmyk"]}}}
|
||||
```
|
||||
`swatch.library.save {path?, format?: "vcswatches"|"gpl"|"ase"|"css", names?, name?, user?}` writes the document's
|
||||
swatches as a library (`.vcswatches` keeps colour models, global, spot, gradients and colour
|
||||
|
||||
@@ -1501,14 +1501,14 @@ A VectorCraft addition, not a feature of the reference app, so no area score mov
|
||||
A VectorCraft addition, not a feature of the reference app, so no area score moves. Object › Plug-ins › Color Effects › Find Nearest Swatch… lists each colour of the selection (fills and strokes, as ticked; solid colours and gradient stops, inside groups and type) with the three swatches of the chosen library closest to it by CIEDE2000 difference, with that difference (`addon.color.nearestSwatch`, #4923). The library is the document's swatches, a built-in library, or a library or colour book the user saved or loaded (no colour book data ships with VectorCraft). Apply gives each colour its nearest swatch as one undo step (`addon.color.applyNearestSwatch`): global and spot swatches link, and a library's swatches the colours take are added to the document first, so a spot colour stays spot. Remaining: typing a colour to look up in the dialog (the command takes `color`), and applying a match other than the nearest. Tests: `cmd::addons::color::nearest::tests` (engine), `addons::color::nearest::tests::find_nearest_swatch_lists_the_matches_and_applies_the_nearest` (ui-egui).
|
||||
## Create Swatch Legend (2026-11-01)
|
||||
|
||||
A VectorCraft addition (it replaces a script some users ran), so no area score moves. The Swatches panel menu's Create Swatch Legend (`swatch.legend`) draws a chip per colour swatch (the selected ones, else all) filled with and linked to its swatch, labelled with its name and values (HEX, RGB, CMYK, Lab or Grayscale; by default its own model; spot swatches marked), black on light chips and white on dark ones, in a grid of chosen columns, chip size and spacing, as one selected group in one undo step (#4919). Tests: `cmd::swatchlegend::tests` (engine), `panels::swatches::tests::create_swatch_legend_draws_the_selected_swatches_or_all` (ui-egui).
|
||||
A VectorCraft addition (it replaces a script some users ran), so no area score moves. Object › Plug-ins › Color Effects › Create Swatch Legend… (`addon.color.swatchLegend`) opens a dialog (columns, chip size, gap, values, selected swatches only, with a preview) and draws a chip per colour swatch (the selected ones, else all) filled with and linked to its swatch, labelled with its name and values (HEX, RGB, CMYK, Lab or Grayscale; by default its own model; spot swatches marked), black on light chips and white on dark ones, in a grid of chosen columns, chip size and spacing, as one selected group in one undo step (#4919). Tests: `cmd::addons::color::legend::tests` (engine), `addons::color::legend::tests::create_swatch_legend_opens_its_dialog_previews_and_runs_on_ok` (ui-egui).
|
||||
|
||||
## Revision history
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-01 | minor | Color Effects add-on (a VectorCraft addition): Find Nearest Swatch (#4923); evidence added; scores unchanged. |
|
||||
| 2026-11-01 | minor | Create Swatch Legend in the Swatches panel menu (`swatch.legend`, #4919); evidence added; scores unchanged. |
|
||||
| 2026-11-01 | minor | Create Swatch Legend add-on under Object › Plug-ins › Color Effects (`addon.color.swatchLegend`, #4919); evidence added; scores unchanged. |
|
||||
| 2026-11-01 | minor | Document Tools add-on (a VectorCraft addition): Export All Open Documents as PDF or SVG (#4920); evidence added; scores unchanged. |
|
||||
| 2026-11-01 | minor | Path Precision Tools add-on (a VectorCraft addition): Expand Dashes into Paths (#4922); evidence added; scores unchanged. |
|
||||
| 2026-11-01 | minor | Type core: the Glyphs panel highlights the character selected with the Type tool and scrolls it into view (#4908); evidence added; scores unchanged. |
|
||||
|
||||
+1
-1
@@ -401,7 +401,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
|
||||
|---|---|---|
|
||||
| 2026-11-01 | minor | Color Effects add-on (a VectorCraft addition): Find Nearest Swatch (#4923); scores and estimates unchanged |
|
||||
| 2026-11-01 | minor | Artboards panel: a row dragged above or below another reorders the artboards (#4687); scores and estimates unchanged |
|
||||
| 2026-11-01 | minor | Swatches panel menu: Create Swatch Legend draws labelled chips of the selected (or all) colour swatches as one group (#4919); scores and estimates unchanged |
|
||||
| 2026-11-01 | minor | Object › Plug-ins › Color Effects › Create Swatch Legend…: a dialog that draws labelled chips of the colour swatches (all, or the ones selected in the Swatches panel) as one group (#4919); scores and estimates unchanged |
|
||||
| 2026-11-01 | minor | Document Tools add-on (a VectorCraft addition): Export All Open Documents as PDF or SVG (#4920); scores and estimates unchanged |
|
||||
| 2026-11-01 | minor | Path Precision Tools add-on (a VectorCraft addition): Expand Dashes into Paths (#4922); scores and estimates unchanged |
|
||||
| 2026-11-01 | minor | Arrange Tools add-on (a VectorCraft addition): Randomize Opacity (#4925); scores and estimates unchanged |
|
||||
|
||||
Reference in New Issue
Block a user