mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-30 12:47:13 -06:00
M3.79: Edit or Apply Color Group opens Recolor Artwork on a colour group
Double-clicking a colour group's folder in the Swatches panel, or the options
button (Edit or Apply Color Group) while a group is selected, opens Recolor
Artwork on the group: without art the rows are the group's colours and OK
rewrites the group; with art selected the art is reduced to the group's size,
takes its colours, and art and group change in one undo step. New
swatch.editGroup {group, colors, rename?} rewrites a group in order (art linked
to its global swatches follows); recolor.apply takes group, groupColors and
rename.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
fca068a0a7
commit
fcc0f4ca0f
@@ -30,8 +30,8 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Recolor Artwork",
|
||||
["Edit", "Edit Colors"],
|
||||
None,
|
||||
"{map: rows [{from: [colour keys (recolor.colors) or \"#rrggbb\" (every colour shown as that hex)], to: colour (hex, key, {c,m,y,k} or {l,a,b}) | null, exclude?: false (the row's colours are kept)}] | {\"key or #rrggbb\": colour, …} (one row per colour), method?: \"exact\" (default)|\"preserveTints\"|\"scaleTints\"|\"tintsShades\"|\"hueShift\" (how a row's colours take its new colour, relative to the row's darkest, average or most saturated colour), limitTo?: swatch library (new colours snap to its nearest colour), includeImages?: true, includePatterns?: true} recolour the selection (gradients, text, meshes, image pixels and pattern tiles included; each new colour keeps the model of the one it replaces) as one undo step → {changed}",
|
||||
has_selection,
|
||||
"{map: rows [{from: [colour keys (recolor.colors) or \"#rrggbb\" (every colour shown as that hex)], to: colour (hex, key, {c,m,y,k} or {l,a,b}) | null, exclude?: false (the row's colours are kept)}] | {\"key or #rrggbb\": colour, …} (one row per colour), method?: \"exact\" (default)|\"preserveTints\"|\"scaleTints\"|\"tintsShades\"|\"hueShift\" (how a row's colours take its new colour, relative to the row's darkest, average or most saturated colour), limitTo?: swatch library (new colours snap to its nearest colour), group?: colour group rewritten with groupColors?: [colour] (default: the rows' new colours, in order, excluded rows left out), rename?: the group's new name, includeImages?: true, includePatterns?: true} recolour the selection (gradients, text, meshes, image pixels and pattern tiles included; each new colour keeps the model of the one it replaces) and the group, as one undo step → {changed}",
|
||||
has_doc,
|
||||
apply
|
||||
),
|
||||
cmd!(
|
||||
@@ -305,10 +305,32 @@ fn apply(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
}
|
||||
let ids = s.doc()?.selection.objects.clone();
|
||||
let group = str_param(p, "group");
|
||||
if ids.is_empty() && group.is_none() {
|
||||
return Err(bad(C, "select artwork to recolour, or give a colour `group`"));
|
||||
}
|
||||
let map = ColorMap::new(&rows, method);
|
||||
let f = |c: Color| map.map(c);
|
||||
let scope = Scope { fill: true, stroke: true, ..Scope::of(p) };
|
||||
let changed = s.edit("Recolor Artwork", |d, _| Ok(Recolor::new(scope, &f).run(d, &ids)))?;
|
||||
let label = if ids.is_empty() { "Edit Color Group" } else { "Recolor Artwork" };
|
||||
let new: Vec<Color> = match p.get("groupColors").and_then(Value::as_array) {
|
||||
Some(cs) => cs
|
||||
.iter()
|
||||
.map(|v| {
|
||||
color_value(v).map(|c| palette.as_ref().map_or(c, |pl| pl.nearest(c))).ok_or_else(|| bad(C, format!("bad colour {v} in groupColors")))
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
None => rows.iter().filter_map(Row::new_color).collect(),
|
||||
};
|
||||
let rename = str_param(p, "rename");
|
||||
let (changed, edit) = s.edit(label, |d, _| {
|
||||
let changed = Recolor::new(scope, &f).run(d, &ids);
|
||||
let edit = group.map(|g| super::swatch::rewrite_group(d, g, &new, rename, C)).transpose()?;
|
||||
Ok((changed, edit))
|
||||
})?;
|
||||
if let Some(e) = edit {
|
||||
e.defaults(s);
|
||||
}
|
||||
Ok(json!({ "changed": changed }))
|
||||
}
|
||||
|
||||
|
||||
@@ -122,6 +122,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_doc,
|
||||
swatch_spot_options
|
||||
),
|
||||
cmd!(
|
||||
"swatch.editGroup",
|
||||
"Edit Color Group",
|
||||
["Window", "Swatches"],
|
||||
None,
|
||||
"{group: colour group, colors: [colour] (hex, key, {c,m,y,k} or {l,a,b}; the group's swatches take them in order, extra colours become new swatches, swatches past the end are removed; built-in swatches can't change), rename?: new group name} rewrite a colour group as one undo step; art linked to its global swatches takes their new colours (and keeps its colour, unlinked, when its swatch goes) → {name, swatches: [names], relinked}",
|
||||
has_doc,
|
||||
swatch_edit_group
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -770,3 +779,78 @@ fn follow_bases(bases: &[(String, Color)]) -> impl FnMut(&mut Color, &mut Option
|
||||
std::mem::replace(c, new) != new
|
||||
}
|
||||
}
|
||||
|
||||
/// A colour group rewrite: its name afterwards and the swatch links it changed.
|
||||
pub(super) struct GroupEdit {
|
||||
name: String,
|
||||
swatches: Vec<String>,
|
||||
relinks: Vec<Relink>,
|
||||
relinked: usize,
|
||||
}
|
||||
|
||||
impl GroupEdit {
|
||||
/// Apply the link changes to the default fill and stroke for new art.
|
||||
pub(super) fn defaults(&self, s: &mut Session) {
|
||||
self.relinks.iter().for_each(|r| r.defaults(s));
|
||||
}
|
||||
}
|
||||
|
||||
/// Rewrite colour group `group` with `colors`: its swatches take them in order (keeping their
|
||||
/// names, kinds and global or spot flags), extra colours are added as new swatches and swatches
|
||||
/// past the end are removed. Paints linked to its global swatches follow (a removed swatch's are
|
||||
/// unlinked); `rename` renames the group. A built-in swatch (None, Registration) it would change or
|
||||
/// remove is refused.
|
||||
pub(super) fn rewrite_group(d: &mut Document, group: &str, colors: &[Color], rename: Option<&str>, cmd: &str) -> Result<GroupEdit> {
|
||||
let gi = d.swatch_groups.iter().position(|g| g.name == group).ok_or_else(|| bad(cmd, format!("no colour group `{group}`")))?;
|
||||
let name = match rename.map(str::trim).filter(|n| !n.is_empty() && *n != group) {
|
||||
Some(n) => d.free_swatch_name(n),
|
||||
None => group.to_string(),
|
||||
};
|
||||
let old = &d.swatch_groups[gi].swatches;
|
||||
if let Some(w) = old.iter().enumerate().find(|(i, w)| w.is_reserved() && colors.get(*i).is_none_or(|c| w.paint.color() != Some(*c))).map(|x| x.1)
|
||||
{
|
||||
return Err(bad(cmd, format!("`{}` can't be edited or removed", w.name)));
|
||||
}
|
||||
let mut relinks = vec![];
|
||||
let mut swatches = std::mem::take(&mut d.swatch_groups[gi].swatches);
|
||||
for gone in swatches.split_off(colors.len().min(swatches.len())) {
|
||||
if gone.global {
|
||||
relinks.push(Relink { from: gone.name.clone(), to: gone.name, color: None, keep: false });
|
||||
}
|
||||
}
|
||||
for (w, c) in swatches.iter_mut().zip(colors) {
|
||||
if w.paint.color() != Some(*c) {
|
||||
// A tint swatch becomes a colour of its own.
|
||||
w.paint = Paint::solid(*c);
|
||||
if w.global {
|
||||
// Art linked to a Lab spot shows it as the Spot Colors options say.
|
||||
relinks.push(Relink { from: w.name.clone(), to: w.name.clone(), color: Some(d.linked_color(*c, w.spot)), keep: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
for c in colors.iter().skip(swatches.len()) {
|
||||
let nm = unique_name(&color_name(*c), |n| n == name || d.swatch_name_taken(n) || swatches.iter().any(|w| w.name == n));
|
||||
swatches.push(Swatch { name: nm, paint: Paint::solid(*c), global: false, spot: false });
|
||||
}
|
||||
let g = &mut d.swatch_groups[gi];
|
||||
(g.name, g.swatches) = (name.clone(), swatches);
|
||||
let names = g.swatches.iter().map(|w| w.name.clone()).collect();
|
||||
let relinked = if relinks.is_empty() { 0 } else { d.map_solid_paints(&mut |c, l, t| relinks.iter().any(|r| r.apply(c, l, t))) };
|
||||
Ok(GroupEdit { name, swatches: names, relinks, relinked })
|
||||
}
|
||||
|
||||
fn swatch_edit_group(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "swatch.editGroup";
|
||||
let group = str_param(p, "group").ok_or_else(|| bad(C, "missing `group`"))?;
|
||||
let colors: Vec<Color> = p
|
||||
.get("colors")
|
||||
.and_then(Value::as_array)
|
||||
.ok_or_else(|| bad(C, "missing `colors`"))?
|
||||
.iter()
|
||||
.map(|v| color_value(v).ok_or_else(|| bad(C, format!("bad colour {v}"))))
|
||||
.collect::<Result<_>>()?;
|
||||
let rename = str_param(p, "rename");
|
||||
let e = s.edit("Edit Color Group", |d, _| rewrite_group(d, group, &colors, rename, C))?;
|
||||
e.defaults(s);
|
||||
Ok(json!({"name": e.name, "swatches": e.swatches, "relinked": e.relinked}))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user