mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-27 11:17:01 -06:00
M3.10: swatch names follow the colour model (C=10 M=20 Y=30 K=0, R=.. G=.. B=.., Gray K=..) and are unique document-wide; New Swatch saves the active gradient or pattern, Ctrl/Cmd-click makes a spot colour; colour groups take solid colours only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
00a0874d4d
commit
2f21fc3a66
@@ -3,6 +3,7 @@
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_color::{Color, Paint, Swatch, SwatchGroup};
|
||||
use vectorcraft_doc::Document;
|
||||
use vectorcraft_doc::pattern::pattern_paint;
|
||||
|
||||
use super::paint::paint_from;
|
||||
use super::*;
|
||||
@@ -10,14 +11,22 @@ use crate::EngineError;
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
cmd!("swatch.new", "New Swatch", ["Window", "Swatches"], None, "{name?, color?, global?} (default: current fill)", has_doc, swatch_new),
|
||||
cmd!(
|
||||
"swatch.new",
|
||||
"New Swatch",
|
||||
["Window", "Swatches"],
|
||||
None,
|
||||
"{name?, color? | swatch? | gradient? | pattern?: name (default: the current fill), global?, spot? (a spot colour, always global)} save a colour, gradient or pattern as a swatch. Names are unique (\"Sky 2\"); a colour's default name is its values (\"C=10 M=20 Y=30 K=0\", \"R=255 G=128 B=0\", \"Gray K=40\") → {name}",
|
||||
has_doc,
|
||||
swatch_new
|
||||
),
|
||||
cmd!("swatch.delete", "Delete Swatch", ["Window", "Swatches"], None, "{name}", has_doc, swatch_delete),
|
||||
cmd!(
|
||||
"swatch.newGroup",
|
||||
"New Color Group",
|
||||
["Window", "Swatches"],
|
||||
None,
|
||||
"{name?, swatches?: [names] (moved into the group), colors?: [colour] (added as new swatches)}",
|
||||
"{name?, swatches?: [names] (solid colours move into the group; gradients, patterns and None stay), colors?: [colour] (added as new swatches)} → {name}",
|
||||
has_doc,
|
||||
swatch_new_group
|
||||
),
|
||||
@@ -27,21 +36,29 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
}
|
||||
|
||||
fn swatch_new(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let paint = match paint_from(s, p)? {
|
||||
Some(x) => x,
|
||||
None => s.paint.fill.clone(),
|
||||
const C: &str = "swatch.new";
|
||||
let paint = match str_param(p, "pattern") {
|
||||
Some(name) if s.doc()?.doc.pattern(name).is_some() => pattern_paint(name),
|
||||
Some(name) => return Err(bad(C, format!("no pattern `{name}`"))),
|
||||
None => paint_from(s, p)?.unwrap_or_else(|| s.paint.fill.clone()),
|
||||
};
|
||||
let name = match str_param(p, "name") {
|
||||
Some(n) => n.to_string(),
|
||||
None => match &paint {
|
||||
Paint::Solid { color, .. } => rgb_name(*color),
|
||||
_ => format!("Swatch {}", s.doc()?.doc.swatches.len() + 1),
|
||||
},
|
||||
let spot = bool_or(p, "spot", false);
|
||||
let global = spot || bool_or(p, "global", false);
|
||||
let paint = match paint {
|
||||
// The swatch holds the colour itself, not a link to the swatch it may have come from.
|
||||
Paint::Solid { color, .. } => Paint::solid(color),
|
||||
Paint::None => return Err(bad(C, "a swatch needs a colour, gradient or pattern")),
|
||||
_ if spot => return Err(bad(C, "only solid colours can be spot colours")),
|
||||
other => other,
|
||||
};
|
||||
let global = bool_or(p, "global", false);
|
||||
s.edit("New Swatch", |d, _| {
|
||||
d.swatches.push(Swatch { name: name.clone(), paint, global, spot: false });
|
||||
Ok(())
|
||||
let requested = str_param(p, "name").map(str::to_string);
|
||||
let name = s.edit("New Swatch", |d, _| {
|
||||
let name = match requested {
|
||||
Some(n) => free_name(d, &n),
|
||||
None => default_name(d, &paint),
|
||||
};
|
||||
d.swatches.push(Swatch { name: name.clone(), paint, global, spot });
|
||||
Ok(name)
|
||||
})?;
|
||||
Ok(json!({ "name": name }))
|
||||
}
|
||||
@@ -58,15 +75,30 @@ fn swatch_delete(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
ok()
|
||||
}
|
||||
|
||||
/// The default name of a new solid swatch: its 8-bit RGB values.
|
||||
fn rgb_name(c: Color) -> String {
|
||||
let [r, g, b] = c.to_rgb();
|
||||
format!("R={} G={} B={}", (r * 255.0).round(), (g * 255.0).round(), (b * 255.0).round())
|
||||
/// The default name of a new solid swatch: its values in its own colour model.
|
||||
fn color_name(c: Color) -> String {
|
||||
let pct = |v: f32| (v * 100.0).round();
|
||||
let byte = |v: f32| (v * 255.0).round();
|
||||
match c {
|
||||
Color::Cmyk { c, m, y, k } => format!("C={} M={} Y={} K={}", pct(c), pct(m), pct(y), pct(k)),
|
||||
Color::Rgb { r, g, b } => format!("R={} G={} B={}", byte(r), byte(g), byte(b)),
|
||||
Color::Gray { k } => format!("Gray K={}", pct(k)),
|
||||
}
|
||||
}
|
||||
|
||||
/// A free default name for a new swatch of `paint`: its colour values, or "New Gradient Swatch 1"…
|
||||
fn default_name(d: &Document, paint: &Paint) -> String {
|
||||
let base = match paint {
|
||||
Paint::Solid { color, .. } => return free_name(d, &color_name(*color)),
|
||||
Paint::Gradient(_) => "New Gradient Swatch",
|
||||
_ => "New Pattern Swatch",
|
||||
};
|
||||
(1..).map(|i| format!("{base} {i}")).find(|n| !d.swatch_name_taken(n)).unwrap_or_else(|| base.to_string())
|
||||
}
|
||||
|
||||
/// A swatch or colour-group name not used yet in `d`.
|
||||
fn free_name(d: &Document, base: &str) -> String {
|
||||
unique_name(base, |n| d.swatch(n).is_some() || d.swatch_groups.iter().any(|g| g.name == n))
|
||||
unique_name(base, |n| d.swatch_name_taken(n))
|
||||
}
|
||||
|
||||
fn swatch_new_group(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
@@ -77,13 +109,17 @@ fn swatch_new_group(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let name = s.edit("New Color Group", |d, _| {
|
||||
let name = free_name(d, requested.as_deref().unwrap_or("Color Group"));
|
||||
let mut group = SwatchGroup { name: name.clone(), swatches: vec![] };
|
||||
// Colour groups hold solid colours only.
|
||||
for n in &names {
|
||||
if let Some(pos) = d.swatches.iter().position(|sw| &sw.name == n) {
|
||||
group.swatches.push(d.swatches.remove(pos));
|
||||
if d.swatch(n).is_some_and(|sw| matches!(sw.paint, Paint::Solid { .. }))
|
||||
&& let Some(sw) = d.remove_swatch(n)
|
||||
{
|
||||
group.swatches.push(sw);
|
||||
}
|
||||
}
|
||||
for c in &colors {
|
||||
group.swatches.push(Swatch { name: rgb_name(*c), paint: Paint::solid(*c), global: false, spot: false });
|
||||
let nm = unique_name(&color_name(*c), |n| d.swatch_name_taken(n) || group.swatches.iter().any(|sw| sw.name == n));
|
||||
group.swatches.push(Swatch { name: nm, paint: Paint::solid(*c), global: false, spot: false });
|
||||
}
|
||||
d.swatch_groups.push(group);
|
||||
Ok(name)
|
||||
|
||||
Reference in New Issue
Block a user