C: Swatches panel colour groups (+ IDML ColorGroup)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9ab1f9f330
commit
902fd214c2
|
|
@ -43,6 +43,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
|
|||
- Preferences › Advanced Type (document `advancedType`: superscript/subscript size and position; IDML TextPreference); subscripts now drop 33.3% like InDesign.
|
||||
- Preferences › Composition › Highlight: keep violations, H&J violations (three shades), custom tracking/kerning, substituted fonts.
|
||||
- File ▸ Export HTML (`file.exportHtml`): one self-contained page in reading order, styles as CSS, images inline, alt text from Object Export Options (also in EPUB).
|
||||
- Swatches panel colour groups (`swatch.newColorGroup`, `moveToGroup`, `ungroupColorGroup`, `renameColorGroup`; folders and a row context menu; IDML ColorGroup).
|
||||
- Drag and drop text editing: drag selected text to move it (Alt copies) with its formatting (`text.release`).
|
||||
- Image Import Options: place any page of a multi-page PDF (`file.place {pdfPage}`; the app asks which page), shown on screen and embedded as that page in PDF export.
|
||||
- Define Lists: named numbered lists that continue across stories in page order (`list.define`, paragraph `listName` / `startAt`), round-tripped through IDML; `null` in `type.para` / `type.char` now removes an override.
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ impl Document {
|
|||
stories: BTreeMap::new(),
|
||||
styles: Arc::new(Styles::default()),
|
||||
swatches: designcraft_color::default_swatches(),
|
||||
color_groups: vec![],
|
||||
sections: vec![Section {
|
||||
start: 0,
|
||||
start_number: Some(nd.start_page.max(1)),
|
||||
|
|
|
|||
|
|
@ -131,6 +131,14 @@ impl Default for NumberedList {
|
|||
}
|
||||
}
|
||||
|
||||
/// A Swatches panel colour group: a named folder of swatches (by name).
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ColorGroup {
|
||||
pub name: String,
|
||||
pub swatches: Vec<String>,
|
||||
}
|
||||
|
||||
/// Preferences › Advanced Type (stored with the document, like InDesign).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", default)]
|
||||
|
|
@ -293,6 +301,10 @@ pub struct Document {
|
|||
pub stories: BTreeMap<StoryId, Arc<Story>>,
|
||||
pub styles: Arc<Styles>,
|
||||
pub swatches: Vec<Swatch>,
|
||||
/// Swatches panel colour groups (folders), in panel order; swatches not in a group sit at
|
||||
/// the top level.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub color_groups: Vec<ColorGroup>,
|
||||
pub sections: Vec<Section>,
|
||||
#[serde(default)]
|
||||
pub assets: BTreeMap<AssetId, Arc<Asset>>,
|
||||
|
|
|
|||
|
|
@ -126,6 +126,91 @@ pub fn specs() -> Vec<CommandSpec> {
|
|||
return Err(bad("swatch.delete", "special swatches can't be deleted"));
|
||||
}
|
||||
d.swatches.retain(|w| w.name != name);
|
||||
for g in &mut d.color_groups {
|
||||
g.swatches.retain(|w| *w != name);
|
||||
}
|
||||
ok()
|
||||
})
|
||||
}),
|
||||
cmd!(
|
||||
"swatch.newColorGroup",
|
||||
"New Color Group",
|
||||
[],
|
||||
None,
|
||||
"{name?, swatches?: [names]} — a Swatches panel folder (the swatches move into it) → {name}",
|
||||
has_doc,
|
||||
|s, p| {
|
||||
let names: Vec<String> = p
|
||||
.get("swatches")
|
||||
.and_then(Value::as_array)
|
||||
.map(|a| a.iter().filter_map(|v| v.as_str().map(str::to_string)).collect())
|
||||
.unwrap_or_default();
|
||||
let want = str_param(p, "name").map(str::to_string);
|
||||
s.edit(|d, _| {
|
||||
for n in &names {
|
||||
if d.swatch(n).is_none_or(|w| w.locked) {
|
||||
return Err(bad("swatch.newColorGroup", format!("`{n}` can't go in a group")));
|
||||
}
|
||||
}
|
||||
let taken = |n: &str| d.color_groups.iter().any(|g| g.name == n);
|
||||
let name = match want {
|
||||
Some(n) if taken(&n) => return Err(bad("swatch.newColorGroup", format!("a group named `{n}` exists"))),
|
||||
Some(n) => n,
|
||||
None => (1..).map(|i| format!("Color Group {i}")).find(|n| !taken(n)).expect("free name"),
|
||||
};
|
||||
for g in &mut d.color_groups {
|
||||
g.swatches.retain(|w| !names.contains(w));
|
||||
}
|
||||
d.color_groups.push(designcraft_doc::ColorGroup { name: name.clone(), swatches: names });
|
||||
Ok(json!({"name": name}))
|
||||
})
|
||||
}
|
||||
),
|
||||
cmd!("swatch.moveToGroup", "Move to Color Group", [], None, "{swatches: [names], group: name | null (top level)}", has_doc, |s, p| {
|
||||
let names: Vec<String> = p
|
||||
.get("swatches")
|
||||
.and_then(Value::as_array)
|
||||
.map(|a| a.iter().filter_map(|v| v.as_str().map(str::to_string)).collect())
|
||||
.unwrap_or_default();
|
||||
let group = str_param(p, "group").map(str::to_string);
|
||||
s.edit(|d, _| {
|
||||
if let Some(g) = &group
|
||||
&& !d.color_groups.iter().any(|x| x.name == *g)
|
||||
{
|
||||
return Err(bad("swatch.moveToGroup", format!("no color group `{g}`")));
|
||||
}
|
||||
for g in &mut d.color_groups {
|
||||
g.swatches.retain(|w| !names.contains(w));
|
||||
}
|
||||
if let Some(g) = group.and_then(|g| d.color_groups.iter_mut().find(|x| x.name == g)) {
|
||||
g.swatches.extend(names.iter().filter(|n| d.swatches.iter().any(|w| w.name == **n && !w.locked)).cloned());
|
||||
}
|
||||
ok()
|
||||
})
|
||||
}),
|
||||
cmd!("swatch.ungroupColorGroup", "Ungroup Color Group", [], None, "{name} — its swatches go back to the top level", has_doc, |s, p| {
|
||||
let name = str_param(p, "name").unwrap_or("").to_string();
|
||||
s.edit(|d, _| {
|
||||
let n = d.color_groups.len();
|
||||
d.color_groups.retain(|g| g.name != name);
|
||||
if d.color_groups.len() == n {
|
||||
return Err(bad("swatch.ungroupColorGroup", format!("no color group `{name}`")));
|
||||
}
|
||||
ok()
|
||||
})
|
||||
}),
|
||||
cmd!("swatch.renameColorGroup", "Color Group Options", [], None, "{name, to}", has_doc, |s, p| {
|
||||
let (name, to) = (str_param(p, "name").unwrap_or("").to_string(), str_param(p, "to").unwrap_or("").trim().to_string());
|
||||
s.edit(|d, _| {
|
||||
if to.is_empty() || d.color_groups.iter().any(|g| g.name == to) {
|
||||
return Err(bad("swatch.renameColorGroup", format!("can't rename to `{to}`")));
|
||||
}
|
||||
let g = d
|
||||
.color_groups
|
||||
.iter_mut()
|
||||
.find(|g| g.name == name)
|
||||
.ok_or_else(|| bad("swatch.renameColorGroup", format!("no color group `{name}`")))?;
|
||||
g.name = to;
|
||||
ok()
|
||||
})
|
||||
}),
|
||||
|
|
@ -696,6 +781,13 @@ fn add_to_swatches(s: &mut Session, p: &Value) -> Result<Value> {
|
|||
let w = d.swatches.iter_mut().find(|w| w.name == name).expect("found above");
|
||||
w.name = new.clone();
|
||||
w.named = true;
|
||||
for g in &mut d.color_groups {
|
||||
for w in &mut g.swatches {
|
||||
if *w == name {
|
||||
*w = new.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Everything using the colour follows the rename.
|
||||
for sp in d.spreads.iter_mut().chain(d.parents.iter_mut()) {
|
||||
let sp = std::sync::Arc::make_mut(sp);
|
||||
|
|
@ -808,3 +900,39 @@ mod group_sample_tests {
|
|||
assert_eq!(after["errors"].as_u64().unwrap(), before, "{after}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod color_group_tests {
|
||||
use serde_json::json;
|
||||
|
||||
use crate::Session;
|
||||
|
||||
#[test]
|
||||
fn color_groups_hold_swatches_and_round_trip() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
let a = s.execute("swatch.create", &json!({"name": "Brand Red", "color": {"c": 0, "m": 100, "y": 100, "k": 0}})).unwrap()["name"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let b = s.execute("swatch.create", &json!({"name": "Brand Blue", "color": {"c": 100, "m": 60, "y": 0, "k": 0}})).unwrap()["name"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let g = s.execute("swatch.newColorGroup", &json!({"name": "Brand", "swatches": [a]})).unwrap();
|
||||
assert_eq!(g["name"], "Brand");
|
||||
assert!(s.execute("swatch.newColorGroup", &json!({"swatches": ["[Black]"]})).is_err(), "special swatches stay at the top");
|
||||
s.execute("swatch.moveToGroup", &json!({"swatches": [b], "group": "Brand"})).unwrap();
|
||||
let groups = |s: &Session| s.doc().unwrap().doc.color_groups.clone();
|
||||
assert_eq!(groups(&s)[0].swatches, [a.clone(), b.clone()]);
|
||||
let bytes = designcraft_idml::export_idml(&s.doc().unwrap().doc);
|
||||
let back = designcraft_idml::import_idml(&bytes).unwrap();
|
||||
assert_eq!(back.color_groups, groups(&s), "IDML ColorGroup");
|
||||
s.execute("swatch.delete", &json!({"name": b})).unwrap();
|
||||
assert_eq!(groups(&s)[0].swatches, [a.as_str()]);
|
||||
s.execute("swatch.renameColorGroup", &json!({"name": "Brand", "to": "Identity"})).unwrap();
|
||||
s.execute("swatch.ungroupColorGroup", &json!({"name": "Identity"})).unwrap();
|
||||
assert!(groups(&s).is_empty());
|
||||
assert!(s.doc().unwrap().doc.swatch(&a).is_some(), "ungrouping keeps the swatches");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -560,6 +560,18 @@ impl<'a> Ex<'a> {
|
|||
for g in gradients {
|
||||
root.push(g);
|
||||
}
|
||||
for (gi, g) in d.color_groups.iter().enumerate() {
|
||||
let mut el = El::new("ColorGroup")
|
||||
.attr("Self", format!("ColorGroup/{}", escape_id(&g.name)))
|
||||
.attr("Name", &g.name)
|
||||
.attr("IsRootColorGroup", "false");
|
||||
for (k, n) in g.swatches.iter().enumerate() {
|
||||
if let Some(id) = self.swatch_ids.get(n) {
|
||||
el.push(El::new("ColorGroupSwatch").attr("Self", format!("u{gi}ColorGroupSwatch{k}")).attr("SwatchItemRef", id));
|
||||
}
|
||||
}
|
||||
root.push(el);
|
||||
}
|
||||
for s in STROKE_STYLES {
|
||||
root.push(El::new("StrokeStyle").attr("Self", format!("StrokeStyle/$ID/{s}")).attr("Name", format!("$ID/{s}")));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ struct Importer<'r> {
|
|||
hidden_colors: HashMap<String, Color>,
|
||||
/// Unnamed swatches (gradients) added on first use.
|
||||
hidden_swatches: HashMap<String, Swatch>,
|
||||
color_groups: Vec<designcraft_doc::ColorGroup>,
|
||||
styles: Styles,
|
||||
para_names: HashMap<String, String>,
|
||||
char_names: HashMap<String, String>,
|
||||
|
|
@ -175,6 +176,7 @@ impl<'r> Importer<'r> {
|
|||
swatch_names,
|
||||
hidden_colors: HashMap::new(),
|
||||
hidden_swatches: HashMap::new(),
|
||||
color_groups: Vec::new(),
|
||||
styles,
|
||||
para_names: HashMap::new(),
|
||||
char_names: HashMap::new(),
|
||||
|
|
@ -545,6 +547,12 @@ impl<'r> Importer<'r> {
|
|||
names.insert(s.name.clone());
|
||||
self.swatches.push(s);
|
||||
}
|
||||
// Colour groups other than the root one become Swatches panel folders.
|
||||
for g in top.iter().filter(|e| e.local() == "ColorGroup" && e.get("IsRootColorGroup") != Some("true")) {
|
||||
let swatches: Vec<String> =
|
||||
g.find_all("ColorGroupSwatch").filter_map(|s| s.get("SwatchItemRef")).filter_map(|r| self.swatch_names.get(r).cloned()).collect();
|
||||
self.color_groups.push(designcraft_doc::ColorGroup { name: g.get("Name").unwrap_or("Color Group").to_string(), swatches });
|
||||
}
|
||||
}
|
||||
|
||||
fn color_for_ref(&self, r: &str, pending: &[(String, Swatch)]) -> Color {
|
||||
|
|
@ -1947,6 +1955,7 @@ impl<'r> Importer<'r> {
|
|||
stories: std::mem::take(&mut self.stories).into_iter().map(|(k, v)| (k, Arc::new(v))).collect(),
|
||||
styles: Arc::new(std::mem::take(&mut self.styles)),
|
||||
swatches: std::mem::take(&mut self.swatches),
|
||||
color_groups: std::mem::take(&mut self.color_groups),
|
||||
sections: std::mem::take(&mut self.sections),
|
||||
assets: std::mem::take(&mut self.assets),
|
||||
hyperlinks: vec![],
|
||||
|
|
|
|||
|
|
@ -16,57 +16,60 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
|
|||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new(if text { "Applies to text" } else { "Applies to fill" }).size(11.0).color(t.text_dim));
|
||||
});
|
||||
// Unnamed colours (mixed in the Color panel) aren't listed until Add to Swatches.
|
||||
for sw in doc.swatches.iter().filter(|w| !w.hidden) {
|
||||
// Unnamed colours (mixed in the Color panel) aren't listed until Add to Swatches; swatches in
|
||||
// colour groups are listed under their folder.
|
||||
let grouped: Vec<&str> = doc.color_groups.iter().flat_map(|g| g.swatches.iter().map(String::as_str)).collect();
|
||||
for sw in doc.swatches.iter().filter(|w| !w.hidden && !grouped.contains(&w.name.as_str())) {
|
||||
swatch_row(app, ui, &doc, sw, 0.0, fill_cur.as_deref(), text);
|
||||
}
|
||||
for g in &doc.color_groups {
|
||||
let open_id = egui::Id::new(("color_group_open", &g.name));
|
||||
let mut open: bool = ui.data(|d| d.get_temp(open_id)).unwrap_or(true);
|
||||
let (row, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 22.0), Sense::click());
|
||||
if fill_cur.as_deref() == Some(sw.name.as_str()) {
|
||||
ui.painter().rect_filled(row, 0.0, t.row_selected);
|
||||
} else if resp.hovered() {
|
||||
if resp.hovered() {
|
||||
ui.painter().rect_filled(row, 0.0, t.hover);
|
||||
}
|
||||
let (c, g) = crate::widgets::swatch_colors(&doc, &sw.name, 1.0);
|
||||
let chip = egui::Rect::from_min_size(row.min + vec2(4.0, 3.0), vec2(16.0, 16.0));
|
||||
crate::widgets::paint_chip(ui.painter(), chip, c, g);
|
||||
ui.painter().text(row.min + vec2(28.0, 11.0), egui::Align2::LEFT_CENTER, &sw.name, egui::FontId::proportional(12.5), t.text);
|
||||
// Type indicators: spot dot / process square, colour mode.
|
||||
let kind = match &sw.value {
|
||||
SwatchValue::Color { color, color_type } => {
|
||||
let m = match color {
|
||||
designcraft_color::Color::Cmyk { .. } => "CMYK",
|
||||
designcraft_color::Color::Rgb { .. } => "RGB",
|
||||
designcraft_color::Color::Gray { .. } => "Gray",
|
||||
};
|
||||
format!("{}{}", if *color_type == ColorType::Spot { "● " } else { "" }, m)
|
||||
}
|
||||
SwatchValue::Gradient { .. } => "Gradient".into(),
|
||||
SwatchValue::Tint { tint, .. } => format!("{:.0}%", tint * 100.0),
|
||||
_ => String::new(),
|
||||
};
|
||||
if sw.locked {
|
||||
icons::paint(
|
||||
ui.painter(),
|
||||
egui::Rect::from_min_size(egui::pos2(row.max.x - 64.0, row.min.y + 4.0), vec2(13.0, 13.0)),
|
||||
"lock",
|
||||
t.text_dim,
|
||||
);
|
||||
}
|
||||
ui.painter().text(row.right_center() - vec2(6.0, 0.0), egui::Align2::RIGHT_CENTER, kind, egui::FontId::proportional(10.5), t.text_dim);
|
||||
icons::paint(
|
||||
ui.painter(),
|
||||
egui::Rect::from_min_size(row.min + vec2(2.0, 3.0), vec2(16.0, 16.0)),
|
||||
if open { "chevron-down" } else { "chevron-right" },
|
||||
t.text_dim,
|
||||
);
|
||||
// A folder.
|
||||
let f = egui::Rect::from_min_size(row.min + vec2(20.0, 5.0), vec2(16.0, 12.0));
|
||||
ui.painter().rect_stroke(f, 1.5, egui::Stroke::new(1.2, t.text_dim), egui::StrokeKind::Inside);
|
||||
ui.painter().line_segment([f.left_top() + vec2(1.0, 3.0), f.right_top() + vec2(-1.0, 3.0)], egui::Stroke::new(1.0, t.text_dim));
|
||||
ui.painter().text(row.min + vec2(42.0, 11.0), egui::Align2::LEFT_CENTER, &g.name, egui::FontId::proportional(12.5), t.text);
|
||||
if resp.clicked() {
|
||||
let r = if text { app.run("type.char", json!({"attrs": {"fill": sw.name}})) } else { app.run("object.fill", json!({"swatch": sw.name})) };
|
||||
let _ = r;
|
||||
open = !open;
|
||||
ui.data_mut(|d| d.insert_temp(open_id, open));
|
||||
}
|
||||
if resp.secondary_clicked() {
|
||||
let _ = app.run("object.stroke", json!({"swatch": sw.name}));
|
||||
resp.context_menu(|ui| {
|
||||
if ui.button("Ungroup Color Group").clicked() {
|
||||
let _ = app.run("swatch.ungroupColorGroup", json!({"name": g.name}));
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
if open {
|
||||
for n in &g.swatches {
|
||||
if let Some(sw) = doc.swatches.iter().find(|w| w.name == *n) {
|
||||
swatch_row(app, ui, &doc, sw, 16.0, fill_cur.as_deref(), text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new("Right-click applies to stroke").size(10.5).color(t.text_disabled));
|
||||
ui.label(egui::RichText::new("Right-click: stroke, groups").size(10.5).color(t.text_disabled));
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if icons::button(ui, "plus", 20.0, false, "New Swatch").clicked() {
|
||||
let _ = app.run("swatch.create", json!({"color": {"c": 0, "m": 50, "y": 100, "k": 0}}));
|
||||
}
|
||||
ui.menu_button("☰", |ui| {
|
||||
if ui.button("New Color Group").clicked() {
|
||||
let _ = app.run("swatch.newColorGroup", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Load Swatches…").clicked() {
|
||||
let _ = app.run("app.loadSwatches", json!({}));
|
||||
ui.close();
|
||||
|
|
@ -80,6 +83,81 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
|
|||
});
|
||||
}
|
||||
|
||||
/// One Swatches panel row: chip, name, type; click applies to fill (or text), the context menu
|
||||
/// to stroke, colour groups and delete.
|
||||
fn swatch_row(
|
||||
app: &mut DesignApp,
|
||||
ui: &mut egui::Ui,
|
||||
doc: &designcraft_doc::Document,
|
||||
sw: &designcraft_color::swatch::Swatch,
|
||||
indent: f32,
|
||||
fill_cur: Option<&str>,
|
||||
text: bool,
|
||||
) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let (row, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 22.0), Sense::click());
|
||||
if fill_cur == Some(sw.name.as_str()) {
|
||||
ui.painter().rect_filled(row, 0.0, t.row_selected);
|
||||
} else if resp.hovered() {
|
||||
ui.painter().rect_filled(row, 0.0, t.hover);
|
||||
}
|
||||
let (c, g) = crate::widgets::swatch_colors(doc, &sw.name, 1.0);
|
||||
let chip = egui::Rect::from_min_size(row.min + vec2(4.0 + indent, 3.0), vec2(16.0, 16.0));
|
||||
crate::widgets::paint_chip(ui.painter(), chip, c, g);
|
||||
ui.painter().text(row.min + vec2(28.0 + indent, 11.0), egui::Align2::LEFT_CENTER, &sw.name, egui::FontId::proportional(12.5), t.text);
|
||||
// Type indicators: spot dot / process square, colour mode.
|
||||
let kind = match &sw.value {
|
||||
SwatchValue::Color { color, color_type } => {
|
||||
let m = match color {
|
||||
designcraft_color::Color::Cmyk { .. } => "CMYK",
|
||||
designcraft_color::Color::Rgb { .. } => "RGB",
|
||||
designcraft_color::Color::Gray { .. } => "Gray",
|
||||
};
|
||||
format!("{}{}", if *color_type == ColorType::Spot { "● " } else { "" }, m)
|
||||
}
|
||||
SwatchValue::Gradient { .. } => "Gradient".into(),
|
||||
SwatchValue::Tint { tint, .. } => format!("{:.0}%", tint * 100.0),
|
||||
_ => String::new(),
|
||||
};
|
||||
if sw.locked {
|
||||
icons::paint(ui.painter(), egui::Rect::from_min_size(egui::pos2(row.max.x - 64.0, row.min.y + 4.0), vec2(13.0, 13.0)), "lock", t.text_dim);
|
||||
}
|
||||
ui.painter().text(row.right_center() - vec2(6.0, 0.0), egui::Align2::RIGHT_CENTER, kind, egui::FontId::proportional(10.5), t.text_dim);
|
||||
if resp.clicked() {
|
||||
let r = if text { app.run("type.char", json!({"attrs": {"fill": sw.name}})) } else { app.run("object.fill", json!({"swatch": sw.name})) };
|
||||
let _ = r;
|
||||
}
|
||||
resp.context_menu(|ui| {
|
||||
if ui.button("Apply to Stroke").clicked() {
|
||||
let _ = app.run("object.stroke", json!({"swatch": sw.name}));
|
||||
ui.close();
|
||||
}
|
||||
if !sw.locked {
|
||||
ui.menu_button("Move to Color Group", |ui| {
|
||||
if ui.button("(Top Level)").clicked() {
|
||||
let _ = app.run("swatch.moveToGroup", json!({"swatches": [sw.name], "group": null}));
|
||||
ui.close();
|
||||
}
|
||||
for g in &doc.color_groups {
|
||||
if ui.button(&g.name).clicked() {
|
||||
let _ = app.run("swatch.moveToGroup", json!({"swatches": [sw.name], "group": g.name}));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
if ui.button("New Color Group with Swatch").clicked() {
|
||||
let _ = app.run("swatch.newColorGroup", json!({"swatches": [sw.name]}));
|
||||
ui.close();
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button("Delete Swatch").clicked() {
|
||||
let _ = app.run("swatch.delete", json!({"name": sw.name}));
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Colour models of the Color panel (its panel menu: Lab, CMYK, RGB).
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum ColorMode {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
|||
|---|---|---|---|---|---|
|
||||
| P0 | 102 | 99 | 3 | 0 | 99% |
|
||||
| P1 | 97 | 74 | 21 | 2 | 87% |
|
||||
| P2 | 76 | 13 | 13 | 50 | 26% |
|
||||
| P2 | 76 | 14 | 13 | 49 | 27% |
|
||||
|
||||
| Area | Parity |
|
||||
|---|---|
|
||||
|
|
@ -20,7 +20,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
|||
| Layers | 95% |
|
||||
| Frames, shapes & paths | 91% |
|
||||
| Transform | 100% |
|
||||
| Fill, stroke, colour | 86% |
|
||||
| Fill, stroke, colour | 88% |
|
||||
| Effects & transparency | 62% |
|
||||
| Placing & links | 82% |
|
||||
| Type & text frames | 88% |
|
||||
|
|
@ -149,7 +149,7 @@ Open P0 items: Preferences, Vector/page formats (PDF/AI/SVG/EPS place), Export t
|
|||
| Fill, stroke, colour | Spot colours | P1 | D |
|
||||
| Fill, stroke, colour | Ink Manager | P2 | M |
|
||||
| Fill, stroke, colour | Load/Save Swatches (.ase) | P1 | D |
|
||||
| Fill, stroke, colour | Color groups | P2 | M |
|
||||
| Fill, stroke, colour | Color groups | P2 | D |
|
||||
| Fill, stroke, colour | Unnamed colour to swatch | P1 | D |
|
||||
| Fill, stroke, colour | Stroke panel | P0 | D |
|
||||
| Fill, stroke, colour | Stroke styles | P1 | P |
|
||||
|
|
|
|||
Loading…
Reference in New Issue