From 15a5e64134e650996821bdbff6d7e0856a26a9fc Mon Sep 17 00:00:00 2001 From: Don Omondi <282319807+mockydevs@users.noreply.github.com> Date: Sun, 1 Nov 2026 13:52:13 +0300 Subject: [PATCH] =?UTF-8?q?#4919:=20Create=20Swatch=20Legend=20becomes=20a?= =?UTF-8?q?n=20add-on=20under=20Object=20=E2=80=BA=20Plug-ins=20=E2=80=BA?= =?UTF-8?q?=20Color=20Effects=20with=20a=20dialog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- crates/engine/src/cmd.rs | 2 - crates/engine/src/cmd/addons/color.rs | 9 +- .../color/legend.rs} | 34 ++++--- .../color/legend}/tests.rs | 12 ++- crates/ui-egui/src/addons/color.rs | 5 +- crates/ui-egui/src/addons/color/legend.rs | 93 +++++++++++++++++++ .../ui-egui/src/addons/color/legend/tests.rs | 58 ++++++++++++ crates/ui-egui/src/dialogs.rs | 1 + crates/ui-egui/src/i18n/bg.tsv | 3 + crates/ui-egui/src/i18n/bs.tsv | 3 + crates/ui-egui/src/i18n/ca.tsv | 3 + crates/ui-egui/src/i18n/cs.tsv | 3 + crates/ui-egui/src/i18n/da.tsv | 3 + crates/ui-egui/src/i18n/de.tsv | 3 + crates/ui-egui/src/i18n/el.tsv | 3 + crates/ui-egui/src/i18n/es.tsv | 3 + crates/ui-egui/src/i18n/fi.tsv | 3 + crates/ui-egui/src/i18n/fr.tsv | 3 + crates/ui-egui/src/i18n/hu.tsv | 3 + crates/ui-egui/src/i18n/id.tsv | 3 + crates/ui-egui/src/i18n/it.tsv | 3 + crates/ui-egui/src/i18n/ja.tsv | 3 + crates/ui-egui/src/i18n/ka.tsv | 3 + crates/ui-egui/src/i18n/ko.tsv | 3 + crates/ui-egui/src/i18n/nl.tsv | 3 + crates/ui-egui/src/i18n/pl.tsv | 3 + crates/ui-egui/src/i18n/pt-br.tsv | 3 + crates/ui-egui/src/i18n/ru.tsv | 3 + crates/ui-egui/src/i18n/sv.tsv | 3 + crates/ui-egui/src/i18n/tr.tsv | 3 + crates/ui-egui/src/i18n/uk.tsv | 3 + crates/ui-egui/src/i18n/vi.tsv | 3 + crates/ui-egui/src/i18n/zh-hans.tsv | 3 + crates/ui-egui/src/i18n/zh-hant.tsv | 3 + crates/ui-egui/src/menus/invoke.rs | 1 + crates/ui-egui/src/panels/swatches/actions.rs | 13 --- crates/ui-egui/src/panels/swatches/tests.rs | 37 -------- docs/mcp.md | 9 +- docs/target-app-parity.md | 4 +- docs/ui-parity.md | 2 +- 40 files changed, 280 insertions(+), 78 deletions(-) rename crates/engine/src/cmd/{swatchlegend.rs => addons/color/legend.rs} (80%) rename crates/engine/src/cmd/{swatchlegend => addons/color/legend}/tests.rs (90%) create mode 100644 crates/ui-egui/src/addons/color/legend.rs create mode 100644 crates/ui-egui/src/addons/color/legend/tests.rs diff --git a/crates/engine/src/cmd.rs b/crates/engine/src/cmd.rs index 74a287128..285604af5 100644 --- a/crates/engine/src/cmd.rs +++ b/crates/engine/src/cmd.rs @@ -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()); diff --git a/crates/engine/src/cmd/addons/color.rs b/crates/engine/src/cmd/addons/color.rs index 3dc037a2f..d1ed6c84a 100644 --- a/crates/engine/src/cmd/addons/color.rs +++ b/crates/engine/src/cmd/addons/color.rs @@ -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 { nearest::apply ), ] + .into_iter() + .chain(legend::specs()) + .collect() } fn select(s: &mut Session, p: &Value) -> Result { diff --git a/crates/engine/src/cmd/swatchlegend.rs b/crates/engine/src/cmd/addons/color/legend.rs similarity index 80% rename from crates/engine/src/cmd/swatchlegend.rs rename to crates/engine/src/cmd/addons/color/legend.rs index 348c9f34a..e2f38a7a1 100644 --- a/crates/engine/src/cmd/swatchlegend.rs +++ b/crates/engine/src/cmd/addons/color/legend.rs @@ -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 { 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 { } fn legend(s: &mut Session, p: &Value) -> Result { - 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 { .collect::>()?, 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 { 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 = swatches.iter().map(|w| super::paint::swatch_solid(doc, &w.name, None).unwrap_or_else(|_| w.paint.clone())).collect(); + let fills: Vec = + 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 { 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(); diff --git a/crates/engine/src/cmd/swatchlegend/tests.rs b/crates/engine/src/cmd/addons/color/legend/tests.rs similarity index 90% rename from crates/engine/src/cmd/swatchlegend/tests.rs rename to crates/engine/src/cmd/addons/color/legend/tests.rs index 8f9a7846e..9a8fa57f2 100644 --- a/crates/engine/src/cmd/swatchlegend/tests.rs +++ b/crates/engine/src/cmd/addons/color/legend/tests.rs @@ -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); diff --git a/crates/ui-egui/src/addons/color.rs b/crates/ui-egui/src/addons/color.rs index af472edb9..9ea653047 100644 --- a/crates/ui-egui/src/addons/color.rs +++ b/crates/ui-egui/src/addons/color.rs @@ -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::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", diff --git a/crates/ui-egui/src/addons/color/legend.rs b/crates/ui-egui/src/addons/color/legend.rs new file mode 100644 index 000000000..c8a1b758c --- /dev/null +++ b/crates/ui-egui/src/addons/color/legend.rs @@ -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 { + let picked: Vec = 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 { + form::commit_preview(app, COMMAND, params(d)) +} + +#[cfg(test)] +mod tests; diff --git a/crates/ui-egui/src/addons/color/legend/tests.rs b/crates/ui-egui/src/addons/color/legend/tests.rs new file mode 100644 index 000000000..ee51fe577 --- /dev/null +++ b/crates/ui-egui/src/addons/color/legend/tests.rs @@ -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"])); +} diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index d2f673d61..6221e6d90 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -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), } diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index bf7d14589..ac49948e0 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -6091,3 +6091,6 @@ breakText Glyphs Глифи Small to Large От малко към голямо Large to Small От голямо към малко Distribute Evenly Равномерно разпределение + Create Swatch Legend Създаване на легенда на образците + Create Swatch Legend… Създаване на легенда на образците… + HEX HEX diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 775304ada..ec205cc39 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index d3ce3d673..ae8f98c82 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 45171f71d..89a55480b 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index a2308cf94..cb13377f9 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 7a369d12d..14989897e 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 7f147c557..441d27c2d 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -6180,3 +6180,6 @@ breakText Glyphs Γλύφοι Small to Large Από μικρό σε μεγάλο Large to Small Από μεγάλο σε μικρό Distribute Evenly Ομοιόμορφη κατανομή + Create Swatch Legend Δημιουργία υπομνήματος δειγμάτων χρωμάτων + Create Swatch Legend… Δημιουργία υπομνήματος δειγμάτων χρωμάτων… + HEX HEX diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index d31aa772e..e3e5d9951 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index b2e82d108..5343b27a5 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 154b515bc..b487a63df 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index c3b0b3ff4..38ece26a8 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 8ab3f8cee..54dd04dea 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 59cd3c2b9..be6b4b2b0 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 968eb7761..be4454f52 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -5913,3 +5913,6 @@ breakText Glyphs 字形 Small to Large 小さい順 Large to Small 大きい順 Distribute Evenly 均等に分布 + Create Swatch Legend スウォッチの凡例を作成 + Create Swatch Legend… スウォッチの凡例を作成… + HEX HEX diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 5a81fa7da..cab7995f2 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -6216,3 +6216,6 @@ breakText Glyphs გლიფები Small to Large პატარიდან დიდისკენ Large to Small დიდიდან პატარისკენ Distribute Evenly თანაბრად განაწილება + Create Swatch Legend ფერის ნიმუშების ლეგენდის შექმნა + Create Swatch Legend… ფერის ნიმუშების ლეგენდის შექმნა… + HEX HEX diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index dca862d1c..4a0cb8a6f 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -5809,3 +5809,6 @@ breakText Glyphs 글리프 Small to Large 작은 것부터 Large to Small 큰 것부터 Distribute Evenly 균등 분포 + Create Swatch Legend 견본 범례 만들기 + Create Swatch Legend… 견본 범례 만들기… + HEX HEX diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index dcad02e6f..a478f36de 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 04683fee1..fa7dbdcb9 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 8f5820bdb..73bdbc449 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 2c18474d4..fd3ab6a98 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -6205,3 +6205,6 @@ breakText Glyphs Глифы Small to Large От меньшего к большему Large to Small От большего к меньшему Distribute Evenly Распределить равномерно + Create Swatch Legend Создать легенду образцов + Create Swatch Legend… Создать легенду образцов… + HEX HEX diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index e938f5856..0e04bd715 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index f61da4bf7..3714912c6 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 6b38c67c9..7d5cdda42 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -5828,3 +5828,6 @@ breakText Glyphs Гліфи Small to Large Від меншого до більшого Large to Small Від більшого до меншого Distribute Evenly Розподілити рівномірно + Create Swatch Legend Створити легенду зразків + Create Swatch Legend… Створити легенду зразків… + HEX HEX diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 88cae3eb0..ccca61df4 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -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 diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index aa32a1895..7ef64bcee 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -6069,3 +6069,6 @@ breakText Glyphs 字形 Small to Large 从小到大 Large to Small 从大到小 Distribute Evenly 均匀分布 + Create Swatch Legend 创建色板图例 + Create Swatch Legend… 创建色板图例… + HEX HEX diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 07b65a9f2..e475d2b45 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -5866,3 +5866,6 @@ breakText Glyphs 字符 Small to Large 從小到大 Large to Small 從大到小 Distribute Evenly 均勻分佈 + Create Swatch Legend 建立色票圖例 + Create Swatch Legend… 建立色票圖例… + HEX HEX diff --git a/crates/ui-egui/src/menus/invoke.rs b/crates/ui-egui/src/menus/invoke.rs index 3057dbd9d..7a3213f78 100644 --- a/crates/ui-egui/src/menus/invoke.rs +++ b/crates/ui-egui/src/menus/invoke.rs @@ -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, }) diff --git a/crates/ui-egui/src/panels/swatches/actions.rs b/crates/ui-egui/src/panels/swatches/actions.rs index 8bc4b7aec..be275c108 100644 --- a/crates/ui-egui/src/panels/swatches/actions.rs +++ b/crates/ui-egui/src/panels/swatches/actions.rs @@ -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 = 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(); diff --git a/crates/ui-egui/src/panels/swatches/tests.rs b/crates/ui-egui/src/panels/swatches/tests.rs index 0070d50ee..c649e282b 100644 --- a/crates/ui-egui/src/panels/swatches/tests.rs +++ b/crates/ui-egui/src/panels/swatches/tests.rs @@ -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, 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::>()) -} - -/// #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::::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"); -} diff --git a/docs/mcp.md b/docs/mcp.md index e553b1bd2..101cbe381 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -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 diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 802f56bfb..49cce0029 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -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. | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index c11ecf1f4..2660f5713 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -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 |