From 918105d945b4eb8232c881ee3ee10b491da7fd80 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 14 Nov 2026 21:44:43 -0800 Subject: [PATCH 1/5] Recolor Artwork: Find shows the selected rows' art and dims the rest The Assign tab's Find button finds the colors of the selected rows in the art. While it is on, their art shows as the preview shows it (its current colors with Preview off) and the rest of the selected art, preserved colors included, shows a 25% tint of its current colors. A second click shows the art as before. Find records no undo step, and OK applies every row as it does with Find off. `findColors` is the dialog field. --- crates/ui-egui/src/dialogs/recolor.rs | 216 +++++++++++++++++++++++++- crates/ui-egui/src/i18n/bg.tsv | 1 + crates/ui-egui/src/i18n/bs.tsv | 1 + crates/ui-egui/src/i18n/ca.tsv | 1 + crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/da.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/el.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fi.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/gu.tsv | 1 + crates/ui-egui/src/i18n/hi.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/id.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ka.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/nl.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sv.tsv | 1 + crates/ui-egui/src/i18n/tr.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + docs/mcp.md | 12 ++ docs/target-app-parity.md | 1 + 31 files changed, 253 insertions(+), 4 deletions(-) diff --git a/crates/ui-egui/src/dialogs/recolor.rs b/crates/ui-egui/src/dialogs/recolor.rs index d2bbeb3013..41049e6fbe 100644 --- a/crates/ui-egui/src/dialogs/recolor.rs +++ b/crates/ui-egui/src/dialogs/recolor.rs @@ -9,7 +9,9 @@ //! CMYK or Lab, and Tint when the new color is a spot color of the library), which edit the //! first selected row's new color (once they have, OK gives the art each new color in its own //! model), Merge and Separate, Randomize order or -//! saturation and brightness, Reset, the Method and Limit to Library (its folder button imports a +//! saturation and brightness, Find (with it on, the art of the selected rows shows as the preview +//! shows it, their current colors with Preview off, and the rest of the selected art shows its +//! current colors dimmed; a second click shows the art as before), Reset, the Method and Limit to Library (its folder button imports a //! library file and limits to it; a library's colours apply as its swatches: a spot colour stays a //! spot colour, and joins the document's swatches). Edit tab: a hue and //! saturation wheel with a marker per new colour (drag one; with linked handles they all turn and @@ -48,7 +50,8 @@ //! `globalAdjust` in every slider mode), `display` (the Edit tab's `smooth`, `segmented` or `bars`; //! unset: `smooth`), `themePicker` (the Color Theme Picker is on), `themePicks` (its picks, as //! `recolor.pickTheme`'s `picks`; a new list recolors the rows), `weights` (one per row, as -//! `recolor.weigh`'s; a new list moves the rows' current colors toward them). Changing `colors`, a +//! `recolor.weigh`'s; a new list moves the rows' current colors toward them), `findColors` (Find is +//! on; it shows the art so on the Assign tab, with art and rows selected). Changing `colors`, a //! preserve flag or `limitTo` reduces the rows again (and drops `weights`). use egui::{Rect, Sense, Ui, pos2, vec2}; @@ -161,7 +164,7 @@ pub fn open(app: &mut VectorcraftApp, p: &Value) -> Result { "preserveWhite": kept("preserveWhite", true), "preserveBlack": kept("preserveBlack", true), "preserveGrays": kept("preserveGrays", false), "limitTo": limit, "group": group, "groupName": if new_group { Some(NEW_GROUP) } else { group }, "tab": "assign", "rule": Harmony::Complementary.id(), "linked": true, - "preview": true, THEME_PICKER: false, THEME_PICKS: [], WEIGHTS: null, "__art": art, "__assign": assign, "__newGroup": new_group, + "preview": true, THEME_PICKER: false, THEME_PICKS: [], WEIGHTS: null, FIND: false, "__art": art, "__assign": assign, "__newGroup": new_group, }); let mut d = Dialog::new(KIND, fields); // Without art the rows are there from the start: limit them now (art's rows snap as they reduce). @@ -215,6 +218,8 @@ fn sync(app: &mut VectorcraftApp, d: &mut Dialog) { } snap(app, d, &mut rows); d.fields.insert("rows".into(), Value::Array(rows)); + // Find dims the preserved colors with the rows that aren't selected. + d.fields.insert(PRESERVED.into(), r["preserved"].clone()); d.fields.insert("__sel".into(), json!([])); // The new rows hold no slider edit. d.fields.remove(COMPS); @@ -586,6 +591,76 @@ fn apply_params(d: &Dialog) -> Value { p } +/// The Find button's field, and the tint of its current color that the art of the rows that +/// aren't selected shows while it is on (VectorCraft's own amount: [`Color::tinted`]). +const FIND: &str = "findColors"; +const FIND_DIM: f32 = 0.25; +/// The lighter tints, a percent apart, tried when a dimmed color is one a selected row lists. +const FIND_TRIES: usize = 20; +/// The colors of the art that the rows leave out (Preserve White, Black and Grays), as +/// `recolor.reduce` returned them. +const PRESERVED: &str = "__preserved"; + +/// While Find is on, on the Assign tab with art and rows selected: what the art previews instead of +/// [`apply_params`], as `command.batch` parameters. A first `recolor.apply` gives each current color +/// of the rows that aren't selected, and each preserved color, its [`FIND_DIM`] tint, or the nearest lighter one when that is a +/// color a selected row lists (a color a selected row also lists stays); +/// with Preview on, a second recolors the selected rows as [`apply_params`] would (the other rows +/// excluded, no color group rewritten). None otherwise, and when there is nothing to dim with +/// Preview off. OK still applies [`apply_params`]. +fn find_params(d: &Dialog) -> Option { + if !d.bool(FIND) || !d.bool("__art") || d.str("tab") == "edit" { + return None; + } + let rs = rows(d); + let sel: Vec = selection(d).into_iter().filter(|i| *i < rs.len()).collect(); + if sel.is_empty() { + return None; + } + let keys = |r: &Value| r["from"].as_array().cloned().unwrap_or_default(); + let found: Vec = sel.iter().flat_map(|&i| keys(&rs[i])).collect(); + // A dimmed color must not be one the second step recolors (by its key, or by its hex as a + // "#rrggbb" entry matches): the nearest lighter tint that is none of them is taken instead. + let found_hex: Vec = found.iter().filter_map(color_value).map(|c| c.to_hex()).collect(); + let dimmed = |c: Color| { + (0..FIND_TRIES) + .map(|n| c.tinted(FIND_DIM - n as f32 * 0.01)) + .find(|t| !found.contains(&key(t)) && !found_hex.contains(&t.to_hex())) + .map(|t| key(&t)) + }; + // The current colors of the rows that aren't selected, and the preserved colors. + let preserved = d.fields.get(PRESERVED).and_then(Value::as_array).cloned().unwrap_or_default(); + let others = rs.iter().enumerate().filter(|(i, _)| !sel.contains(i)).flat_map(|(_, r)| keys(r)).chain(preserved); + let mut dim = serde_json::Map::new(); + // A color shown as the same hex as one a selected row lists stays: a "#rrggbb" entry on either + // side matches both. + let listed = |k: &Value| found.contains(k) || color_value(k).is_some_and(|c| found_hex.contains(&c.to_hex())); + for k in others.filter(|k| !listed(k)) { + if let (Some(to), Some(name)) = (color_value(&k).and_then(dimmed), k.as_str()) { + dim.insert(name.to_string(), to); + } + } + let mut commands = vec![]; + if !dim.is_empty() { + commands.push(json!({"command": CMD, "params": {"map": dim}})); + } + if d.bool("preview") { + let mut p = apply_params(d); + let mut only = rs; + for (_, r) in only.iter_mut().enumerate().filter(|(i, _)| !sel.contains(i)) { + r["exclude"] = json!(true); + } + p["map"] = Value::Array(only); + if let Some(p) = p.as_object_mut() { + for k in ["group", "groupColors", "rename"] { + p.remove(k); + } + } + commands.push(json!({"command": CMD, "params": p})); + } + (!commands.is_empty()).then(|| json!({"commands": commands})) +} + fn body(app: &mut VectorcraftApp, ui: &mut Ui, d: &mut Dialog) -> bool { sync(app, d); sync_theme(app, d); @@ -646,7 +721,15 @@ fn body(app: &mut VectorcraftApp, ui: &mut Ui, d: &mut Dialog) -> bool { } sync(app, d); if d.bool("__art") { - form::preview(app, ui, d, "Recolor Artwork", CMD, apply_params(d)); + match find_params(d) { + // Find shows the art so with Preview off too. + Some(p) => { + ui.add_space(8.0); + form::preview_check(ui, d); + form::preview_now(app, d, "Recolor Artwork", "command.batch", p); + } + None => form::preview(app, ui, d, "Recolor Artwork", CMD, apply_params(d)), + } } if let Some(c) = pick.and_then(|i| Some((i, to_color(rows(d).get(i)?)?))) { *d = super::color_picker::over_recolor(d, c.1, c.0); @@ -858,6 +941,10 @@ fn assign_tab(app: &mut VectorcraftApp, ui: &mut Ui, d: &mut Dialog, rows: &mut } } } + let finding = d.bool(FIND); + if widgets::icon_button_enabled(ui, "search", tl!("Find the selected colors in the artwork"), finding, d.bool("__art"), 24.0).clicked() { + d.fields.insert(FIND.into(), json!(!finding)); + } if widgets::icon_button(ui, "rotate-ccw", tl!("Reset the rows to the artwork's colors"), false, 24.0).clicked() { d.fields.remove("__reduced"); d.fields.remove(COMPS); @@ -2020,6 +2107,127 @@ mod tests { assert_eq!(new_colors(&app), [json!("rgb 0 255 0"), json!("rgb 255 255 0")]); } + /// Find previews nothing of its own while it is off, on the Edit tab, without art or without a + /// selected row. On, a first `recolor.apply` dims each current color of the other rows (a color + /// a selected row also lists stays), and with Preview on a second recolors the selected rows + /// alone, without rewriting the color group. + #[test] + fn find_dims_the_colors_of_the_rows_not_selected() { + let mut d = Dialog::new( + KIND, + json!({"rows": rows(), "__art": true, "preview": true, "method": "exact", "limitTo": "", "group": "G", "groupName": "H", "__sel": [0]}), + ); + assert_eq!(find_params(&d), None, "off"); + d.fields.insert(FIND.into(), json!(true)); + let dim = |k: &str| key(&color_value(&json!(k)).unwrap().tinted(FIND_DIM)); + let p = find_params(&d).expect("on"); + let cmds = p["commands"].as_array().unwrap(); + assert_eq!(cmds.len(), 2); + assert_eq!(cmds[0]["command"], CMD); + assert_eq!( + cmds[0]["params"]["map"], + json!({"rgb 0 0 255": dim("rgb 0 0 255"), "rgb 0 0 128": dim("rgb 0 0 128"), "rgb 0 255 0": dim("rgb 0 255 0")}) + ); + assert_eq!(dim("rgb 0 0 255"), json!("rgb 191.25 191.25 255"), "a quarter of the color, the rest white"); + let second = &cmds[1]["params"]; + assert_eq!(second["map"][0], rows()[0], "the selected row recolors as it would"); + assert_eq!((second["map"][1]["exclude"].clone(), second["map"][2]["exclude"].clone()), (json!(true), json!(true))); + assert!(second.get("group").is_none() && second.get("rename").is_none(), "{second}"); + // A color that a selected row lists as a hex (or with another key of the same hex) stays. + let mut e = d.clone(); + let mut rs = rows(); + rs[0]["from"] = json!(["#0000ff"]); + e.fields.insert("rows".into(), json!(rs)); + let map = find_params(&e).unwrap()["commands"][0]["params"]["map"].clone(); + assert!(map.get("rgb 0 0 255").is_none() && map.get("rgb 0 0 128").is_some(), "{map}"); + // The preserved colors dim too. + let mut e = d.clone(); + e.fields.insert(PRESERVED.into(), json!(["rgb 0 0 0"])); + assert_eq!(find_params(&e).unwrap()["commands"][0]["params"]["map"]["rgb 0 0 0"], dim("rgb 0 0 0")); + // A color a selected row lists is not dimmed; with Preview off only the dimming runs. + let mut rs = rows(); + rs[2]["from"] = json!(["rgb 0 255 0", "rgb 255 0 0"]); + d.fields.insert("rows".into(), json!(rs)); + d.fields.insert("preview".into(), json!(false)); + let p = find_params(&d).unwrap(); + assert_eq!(p["commands"].as_array().map(Vec::len), Some(1)); + assert!(p["commands"][0]["params"]["map"].get("rgb 255 0 0").is_none(), "{p}"); + // A dimmed color that a selected row lists would be recolored with it: a lighter tint is taken. + let mut rs = rows(); + rs[0]["from"] = json!(["rgb 255 0 0", "rgb 191 191 255"]); + d.fields.insert("rows".into(), json!(rs)); + let p = find_params(&d).unwrap(); + let to = color_value(&p["commands"][0]["params"]["map"]["rgb 0 0 255"]).unwrap(); + assert_eq!(to.to_hex(), "#c2c2ff", "{p}"); + // Every row selected with Preview off: nothing to show. + d.fields.insert("__sel".into(), json!([0, 1, 2])); + assert_eq!(find_params(&d), None); + for (k, v) in [("__sel", json!([])), ("__sel", json!([7])), ("tab", json!("edit")), ("__art", json!(false))] { + let mut e = d.clone(); + e.fields.insert("preview".into(), json!(true)); + e.fields.insert("__sel".into(), json!([0])); + e.fields.insert(k.into(), v.clone()); + assert_eq!(find_params(&e), None, "{k}: {v}"); + } + } + + /// In the dialog, Find with the red art's row selected shows the red art's new color and the + /// blue art dimmed, with Preview off the red art's own color; off again, the art shows as the + /// preview has it. Cancel leaves the art as it was with no undo step, and OK with Find on + /// applies the rows to all the art, as one undo step. + #[test] + fn find_shows_the_selected_rows_art_and_dims_the_rest() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); + let ctx = egui::Context::default(); + let [red, blue] = theme_setup(&mut app, &ctx); + let undo = app.session.doc().unwrap().history.undo.len(); + let set = |app: &mut VectorcraftApp, k: &str, v: Value| { + app.ui.dialog.as_mut().unwrap().fields.insert(k.into(), v); + }; + let rs = json!([{"from": ["rgb 255 0 0"], "to": "rgb 0 255 0"}, {"from": ["rgb 0 0 255"], "to": "rgb 255 255 0"}]); + set(&mut app, "rows", rs.clone()); + set(&mut app, "__sel", json!([0])); + frame(&mut app, &ctx, 0.1, vec![], 0); + let fills = |app: &VectorcraftApp| (fill_hex(app, red), fill_hex(app, blue)); + assert_eq!(fills(&app), ("#00ff00".into(), "#ffff00".into()), "off: the preview"); + set(&mut app, FIND, json!(true)); + frame(&mut app, &ctx, 0.2, vec![], 0); + assert_eq!(fills(&app), ("#00ff00".into(), "#bfbfff".into()), "on: the other art dimmed"); + set(&mut app, "preview", json!(false)); + frame(&mut app, &ctx, 0.3, vec![], 0); + assert_eq!(fills(&app), ("#ff0000".into(), "#bfbfff".into()), "Preview off: the current colors"); + set(&mut app, "__sel", json!([1])); + frame(&mut app, &ctx, 0.4, vec![], 0); + assert_eq!(fills(&app), ("#ffbfbf".into(), "#0000ff".into()), "the other row selected"); + set(&mut app, FIND, json!(false)); + frame(&mut app, &ctx, 0.5, vec![], 0); + assert_eq!(fills(&app), ("#ff0000".into(), "#0000ff".into()), "off with Preview off: the art as it is"); + assert_eq!(app.session.doc().unwrap().history.undo.len(), undo, "Find records no undo step"); + set(&mut app, FIND, json!(true)); + frame(&mut app, &ctx, 0.6, vec![], 0); + crate::dialogs::cancel(&mut app); + assert!(app.ui.dialog.is_none()); + assert_eq!(fills(&app), ("#ff0000".into(), "#0000ff".into()), "Cancel: the art as it was"); + assert_eq!(app.session.doc().unwrap().history.undo.len(), undo); + // A black square joins the selection: Preserve Black leaves it out of the rows. + let black = square(&mut app, 70.0, "#000000"); + let undo = app.session.doc().unwrap().history.undo.len(); + app.run("select.set", json!({"ids": [red, blue, black]})).unwrap(); + app.run("ui.recolorDialog", json!({})).unwrap(); + set(&mut app, "method", json!(Method::Exact.id())); + frame(&mut app, &ctx, 1.0, vec![], 0); + set(&mut app, "rows", rs); + set(&mut app, "__sel", json!([0])); + set(&mut app, FIND, json!(true)); + frame(&mut app, &ctx, 1.1, vec![], 0); + assert_eq!(fills(&app), ("#00ff00".into(), "#bfbfff".into())); + assert_eq!(fill_hex(&app, black), "#bfbfbf", "the preserved black dims too"); + crate::dialogs::confirm(&mut app).unwrap(); + assert_eq!(fills(&app), ("#00ff00".into(), "#ffff00".into()), "OK applies the rows to all the art"); + assert_eq!(fill_hex(&app, black), "#000000", "and keeps the preserved black"); + assert_eq!(app.session.doc().unwrap().history.undo.len(), undo + 1, "one undo step"); + } + /// In a CMYK document colors opened alone are saved as a new color group in their own model. #[test] fn a_new_group_from_colors_keeps_their_model_in_a_cmyk_document() { diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 948b295e17..1ff12356a6 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -1446,6 +1446,7 @@ font-technology Variable Променливи Show smooth color wheel Показване на плавно цветово колело Show segmented color wheel Показване на сегментирано цветово колело Show color bars Показване на цветни ленти + Find the selected colors in the artwork Намиране на избраните цветове в илюстрацията Harmony Sets Набори от хармонии Height Височина Height: Височина: diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index f8d6fa263b..d0c4c61ba7 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -2881,6 +2881,7 @@ digits Malayalam Malajalam Show smooth color wheel Prikaži glatki kotač boja Show segmented color wheel Prikaži segmentirani kotač boja Show color bars Prikaži trake boja + Find the selected colors in the artwork Pronađi odabrane boje u crtežu Harmony Rules Pravila harmonije Keep this row's colors (exclude) Zadrži boje ovog reda (isključi) Limit to Library: Ograniči na biblioteku: diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 3ed3ca8331..3918a384e9 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -1977,6 +1977,7 @@ digits Malayalam Malaiàlam Show smooth color wheel Mostra la roda de color contínua Show segmented color wheel Mostra la roda de color segmentada Show color bars Mostra les barres de color + Find the selected colors in the artwork Troba els colors seleccionats a l’obra Harmony Sets Conjunts d’harmonia HDTV 1080p HDTV 1080p HDTV 720p HDTV 720p diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 161e235c56..117dec883c 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2917,6 +2917,7 @@ digits Malayalam Malajálamské Show smooth color wheel Zobrazit plynulé barevné kolo Show segmented color wheel Zobrazit segmentované barevné kolo Show color bars Zobrazit barevné pruhy + Find the selected colors in the artwork Najít vybrané barvy v kresbě Harmony Rules Pravidla harmonie Keep this row's colors (exclude) Ponechat barvy tohoto řádku (vyloučit) Limit to Library: Omezit na knihovnu: diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index fd0b7c042e..71bbea2027 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -3308,6 +3308,7 @@ digits Malayalam Malayalam Show smooth color wheel Vis glat farvehjul Show segmented color wheel Vis segmenteret farvehjul Show color bars Vis farvebjælker + Find the selected colors in the artwork Find de valgte farver i grafikken Harmony Rules Harmoniregler Keep this row's colors (exclude) Behold farverne i denne række (ekskludér) Limit to Library: Begræns til bibliotek: diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 2a5f32a5d9..58dbb1d623 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1491,6 +1491,7 @@ file-filter Templates Vorlagen Show smooth color wheel Stufenlosen Farbkreis anzeigen Show segmented color wheel Segmentierten Farbkreis anzeigen Show color bars Farbbalken anzeigen + Find the selected colors in the artwork Ausgewählte Farben in der Grafik finden Harmony Sets Harmoniesätze Height Höhe Height: Höhe: diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index b1b7b4d039..63bd2dfc4d 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1421,6 +1421,7 @@ font-technology Variable Μεταβλητές Show smooth color wheel Εμφάνιση ομαλού χρωματικού τροχού Show segmented color wheel Εμφάνιση τμηματοποιημένου χρωματικού τροχού Show color bars Εμφάνιση χρωματικών ράβδων + Find the selected colors in the artwork Εύρεση των επιλεγμένων χρωμάτων στο σχέδιο Harmony Sets Σύνολα χρωματικής αρμονίας Height Ύψος Height: Ύψος: diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 5b3c675f7b..d7994eb6fc 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1500,6 +1500,7 @@ font-technology Variable Variables Show smooth color wheel Mostrar rueda de color continua Show segmented color wheel Mostrar rueda de color segmentada Show color bars Mostrar barras de color + Find the selected colors in the artwork Buscar los colores seleccionados en la ilustración Harmony Sets Conjuntos de armonía Height Altura Height: Altura: diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index e77a617c52..824ad48ee0 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -2861,6 +2861,7 @@ digits Malayalam Malajalam Show smooth color wheel Näytä liukuva väriympyrä Show segmented color wheel Näytä segmentoitu väriympyrä Show color bars Näytä väripalkit + Find the selected colors in the artwork Etsi valitut värit kuvituksesta Harmony Rules Harmoniasäännöt Keep this row's colors (exclude) Säilytä tämän rivin värit (jätä pois) Limit to Library: Rajoita kirjastoon: diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 2762d2a954..598185ed18 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1493,6 +1493,7 @@ font-technology Variable Variables Show smooth color wheel Afficher la roue chromatique continue Show segmented color wheel Afficher la roue chromatique segmentée Show color bars Afficher les barres de couleur + Find the selected colors in the artwork Rechercher les couleurs sélectionnées dans l’illustration Harmony Rules: Règles d’harmonie : Harmony Sets Ensembles d’harmonies Height Hauteur diff --git a/crates/ui-egui/src/i18n/gu.tsv b/crates/ui-egui/src/i18n/gu.tsv index 1c96900b2b..2296f70318 100644 --- a/crates/ui-egui/src/i18n/gu.tsv +++ b/crates/ui-egui/src/i18n/gu.tsv @@ -3504,6 +3504,7 @@ Show all plates બધી પ્લેટ બતાવો Show axis અક્ષ બતાવો Show color bars રંગ બાર બતાવો + Find the selected colors in the artwork પસંદ કરેલા રંગો આર્ટવર્કમાં શોધો Show handles when multiple anchors are selected બહુવિધ એન્કર પસંદ હોય ત્યારે હૅન્ડલ બતાવો Show in Folder ફોલ્ડરમાં બતાવો Show its progress, see Cancel and write log lines તેની પ્રગતિ બતાવવી, રદ કરવાની વિનંતી જોવી અને લૉગ લાઇનો લખવી diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 548f961f51..401f1fe1f8 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -4590,6 +4590,7 @@ antiAlias Sharp शार्प Show Center Mark शो सेंटर मार्क Show Code कोड दिखाएं Show color bars रंग सलाखों दिखाएँ + Find the selected colors in the artwork चयनित रंगों को आर्टवर्क में खोजें Show Color Groups रंग समूह दिखाएँ Show Color Swatches रंग स्वॉचेस दिखाएँ Show Corner Widget शो कॉर्नर विजेट diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 200b45d1b1..f20f65f424 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1899,6 +1899,7 @@ file-filter Templates Sablonok Show smooth color wheel Folytonos színkör megjelenítése Show segmented color wheel Szegmentált színkör megjelenítése Show color bars Színsávok megjelenítése + Find the selected colors in the artwork A kijelölt színek keresése a grafikában Harmony Sets Harmóniakészletek HDTV 1080p HDTV 1080p HDTV 720p HDTV 720p diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index f3dcca3874..235a98c4e5 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -2699,6 +2699,7 @@ digits Malayalam Malayalam Show smooth color wheel Tampilkan roda warna halus Show segmented color wheel Tampilkan roda warna bersegmen Show color bars Tampilkan bilah warna + Find the selected colors in the artwork Temukan warna yang dipilih di karya seni Harmony Rules Aturan harmoni Keep this row's colors (exclude) Pertahankan warna baris ini (kecualikan) Limit to Library: Batasi ke pustaka: diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 90ec79404c..ea2007eb43 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1521,6 +1521,7 @@ file-filter Templates Modelli Show smooth color wheel Mostra ruota dei colori continua Show segmented color wheel Mostra ruota dei colori segmentata Show color bars Mostra barre di colore + Find the selected colors in the artwork Trova i colori selezionati nella grafica Harmony Sets Gruppi di armonia Height Altezza Height: Altezza: diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index d98acfa22d..1b41557a24 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1925,6 +1925,7 @@ antiAlias Sharp シャープ Show smooth color wheel 滑らかなカラーホイールを表示 Show segmented color wheel 分割カラーホイールを表示 Show color bars カラーバーを表示 + Find the selected colors in the artwork 選択したカラーをアートワーク内で検索 Height 高さ Height: 高さ: Help › Search Commands… ヘルプ › コマンドを検索… diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 794e5454a9..32cf861c37 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -2616,6 +2616,7 @@ digits Malayalam მალაიალამური Show smooth color wheel გლუვი ფერთა წრის ჩვენება Show segmented color wheel სეგმენტირებული ფერთა წრის ჩვენება Show color bars ფერთა ზოლების ჩვენება + Find the selected colors in the artwork არჩეული ფერების პოვნა ნახატში Keep this row's colors (exclude) ამ სტრიქონის ფერების შენარჩუნება (გამორიცხვა) Limit to Library: ბიბლიოთეკით შეზღუდვა: Link harmony colors ჰარმონიის ფერების დაკავშირება diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index bb8676df71..98a996179d 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -1915,6 +1915,7 @@ antiAlias Sharp 날카롭게 Show smooth color wheel 부드러운 색상환 표시 Show segmented color wheel 분할 색상환 표시 Show color bars 색상 막대 표시 + Find the selected colors in the artwork 선택한 색상을 아트웍에서 찾기 Height 높이 Height: 높이: Help › Search Commands… 도움말 › 명령 검색… diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index a597a03fd0..0d8d9be9b5 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -3217,6 +3217,7 @@ digits Malayalam Malayalam Show smooth color wheel Vloeiend kleurenwiel tonen Show segmented color wheel Gesegmenteerd kleurenwiel tonen Show color bars Kleurbalken tonen + Find the selected colors in the artwork De geselecteerde kleuren zoeken in de artwork Harmony Rules Harmonieregels Keep this row's colors (exclude) De kleuren van deze rij behouden (uitsluiten) Limit to Library: Beperken tot bibliotheek: diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 99ee2080b8..0d964a5026 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2827,6 +2827,7 @@ digits Malayalam Malajalam Show smooth color wheel Pokaż płynne koło kolorów Show segmented color wheel Pokaż segmentowe koło kolorów Show color bars Pokaż paski kolorów + Find the selected colors in the artwork Znajdź zaznaczone kolory w kompozycji Keep this row's colors (exclude) Zachowaj kolory tego wiersza (wyklucz) Limit to Library: Ogranicz do biblioteki: Link harmony colors Połącz kolory harmonii diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 677c8e460f..5181b0eb13 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1323,6 +1323,7 @@ antiAlias Sharp Nítido Show smooth color wheel Mostrar roda de cores contínua Show segmented color wheel Mostrar roda de cores segmentada Show color bars Mostrar barras de cores + Find the selected colors in the artwork Localizar as cores selecionadas na arte Harmony Sets Conjuntos de Harmonia Height Altura Height: Altura: diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 106343cddd..f2da3f7c42 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -1510,6 +1510,7 @@ file-filter Templates Шаблоны Show smooth color wheel Показать плавный цветовой круг Show segmented color wheel Показать сегментированный цветовой круг Show color bars Показать цветовые полосы + Find the selected colors in the artwork Найти выбранные цвета в рисунке Harmony Sets Наборы гармонии Height Высота Height: Высота: diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index cc854aa362..a739583ab1 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -1459,6 +1459,7 @@ font-technology Variable Variabla Show smooth color wheel Visa steglöst färghjul Show segmented color wheel Visa segmenterat färghjul Show color bars Visa färgstaplar + Find the selected colors in the artwork Hitta de markerade färgerna i teckningen Harmony Sets Harmoniuppsättningar Height Höjd Height: Höjd: diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index f2888a30f0..9db264343f 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -1422,6 +1422,7 @@ font-technology Variable Değişken Show smooth color wheel Kesintisiz renk tekerleğini göster Show segmented color wheel Bölümlü renk tekerleğini göster Show color bars Renk çubuklarını göster + Find the selected colors in the artwork Seçili renkleri resimde bul Harmony Sets Armoni Setleri Height Yükseklik Height: Yükseklik: diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 411b2c3baf..2e09a8f0c8 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -1515,6 +1515,7 @@ file-filter Templates Шаблони Show smooth color wheel Показати плавне колірне коло Show segmented color wheel Показати сегментоване колірне коло Show color bars Показати колірні смуги + Find the selected colors in the artwork Знайти вибрані кольори в малюнку Harmony Sets Набори гармонії Height Висота Height: Висота: diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 1460910197..5be6d9174c 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1746,6 +1746,7 @@ file-filter Templates Mẫu Show smooth color wheel Hiện bánh xe màu liền mạch Show segmented color wheel Hiện bánh xe màu phân đoạn Show color bars Hiện các thanh màu + Find the selected colors in the artwork Tìm các màu đã chọn trong tác phẩm Harmony Sets Bộ hài hòa màu Height Chiều cao Height: Chiều cao: diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 3fe2f9e009..e7408947c8 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1449,6 +1449,7 @@ file-filter Templates 模板 Show smooth color wheel 显示平滑色轮 Show segmented color wheel 显示分段色轮 Show color bars 显示颜色条 + Find the selected colors in the artwork 在图稿中查找所选颜色 Harmony Sets 色彩调和组 Height 高度 Height: 高度: diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index ab7f9d9dd2..167777d864 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1486,6 +1486,7 @@ file-filter Templates 範本 Show smooth color wheel 顯示平滑色輪 Show segmented color wheel 顯示分段色輪 Show color bars 顯示顏色條 + Find the selected colors in the artwork 在圖稿中尋找所選顏色 Harmony Sets 色彩調和組 Height 高度 Height: 高度: diff --git a/docs/mcp.md b/docs/mcp.md index 8ca4121032..0d3bd96e65 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -4408,6 +4408,18 @@ of the rows that are not excluded, in row order, and Save Prominent Colors saves {"name":"run_command","arguments":{"command":"recolor.weigh","params":{"map":[{"from":["rgb 255 0 0","rgb 128 0 204"],"to":"#00ff00"},{"from":["rgb 0 0 255"],"to":"#ffff00"}],"weights":[1,1]}}} ``` +The Assign tab's Find button (the dialog field `findColors`) finds the colors of the selected rows in the art +([Advanced recolor options overview](https://helpx.adobe.com/illustrator/desktop/manage-colors/learn-color-basics/edit-colors-dialog-overview.html)). +While it is on and rows are selected, the art of those rows shows as the preview shows it (its current colors with +Preview off), and the rest of the selected art shows dimmed; a second click shows the art as before +([archived help, Recolor Artwork](https://help.adobe.com/archive/en_US/illustrator/cs5/illustrator_cs5_help.pdf#page=149)). +As VectorCraft's own rules, a dimmed color is a 25% tint of its current color, or the nearest lighter tint (in steps +of 1%) when that tint is a color a selected row lists (`recolor.apply` gives each current color of the other rows +and the preserved colors that tint, then recolors the selected rows), a color that a selected row also lists is not +dimmed, so art that mixes colors dims only its other colors, and nothing is dimmed on the Edit tab or with no row +selected. Find changes only what the canvas shows: it records no undo step, Cancel leaves the art as it was, and OK +applies every row to the art as it does with Find off. + The `recolor.*` commands refuse parameters of the wrong form. A `limitTo` or `method` that is not a string gets the error for an unknown library or method. Other wrong-form parameters get an error that names them. `includeImages`, `includePatterns`, a row's `exclude`, `order`, `saturationBrightness` and the `white`, `black` and diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index f4424700be..ccb1198959 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1591,6 +1591,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Document Color Mode for effects and graphs:** File › Document Color Mode and `document.open {colorMode}` convert the colors of effects (Drop Shadow, Inner Glow, Outer Glow and the other effects with a color, on objects, their fills and strokes and in graphic styles) and the series paints and label styles that a graph keeps, so a graph drawn again from new data keeps the converted colors; File › Document Color Mode also converts the fill and stroke that new art takes. Undo restores the original new-art fill and stroke and Redo applies the conversion again. - **Recolor Artwork layout (#3416):** the Edit chip list uses the modal scroll helper and its actual remaining height; the tab's minimum-height space scrolls with the chips. `tests_recolor` covers 5/20/200/400 colors, window resizing, 100/125/150/200% UI scaling, scrolling to and editing the final chip, Preview, Cancel, Confirm and Undo, alongside the existing #1733 drag regressions. Scores and estimates unchanged. - **Recolor Artwork Edit displays:** buttons beside the Edit tab's wheel show the colors on a smooth color wheel, a segmented color wheel or as color bars ([Edit colors using Recolor Artwork](https://helpx.adobe.com/illustrator/desktop/manage-colors/modify-colors/edit-colors-using-edit-colors-dialog.html)). The segmented wheel draws 24 hues by 5 saturations as solid patches, and its markers move freely as on the smooth wheel. The bars show one solid bar per recolored color in row order: a click selects a bar for the color sliders, and a drag moves its row without changing a color ([Advanced recolor options overview](https://helpx.adobe.com/illustrator/desktop/manage-colors/learn-color-basics/edit-colors-dialog-overview.html)). The dialog opens on the smooth wheel; `display` (`smooth`, `segmented`, `bars`) is the dialog field. `tests_recolor` covers switching, the dialog height, segmented dragging and Brightness, and bar selection, editing, reordering, scrolling, Preview, Cancel and OK. Scores and estimates unchanged. + - **Recolor Artwork Find:** the Assign tab's Find button finds the colors of the selected rows in the art ([Advanced recolor options overview](https://helpx.adobe.com/illustrator/desktop/manage-colors/learn-color-basics/edit-colors-dialog-overview.html)): while it is on, their art shows as the preview shows it and the rest of the selected art shows dimmed, until a second click ([archived help, Recolor Artwork](https://help.adobe.com/archive/en_US/illustrator/cs5/illustrator_cs5_help.pdf#page=149)). VectorCraft's own rules: a dimmed color is a 25% tint of its current color (or the nearest lighter tint when that one is a color a selected row lists), a color a selected row also lists is not dimmed, and Find records no undo step. `findColors` is the dialog field. Tests: `find_dims_the_colors_of_the_rows_not_selected` and `find_shows_the_selected_rows_art_and_dims_the_rest` in `crates/ui-egui/src/dialogs/recolor.rs`. Scores and estimates unchanged. - **Edit Colors parameters:** `edit.colors.saturate` needs a number `intensity`, the value from -100% to 100% that Saturate takes ([Adjusting colors](https://web.archive.org/web/20230128232631/https://helpx.adobe.com/illustrator/using/adjusting-colors.html)), and answers "missing number `intensity`" without one; a number outside the range counts as its nearest end. A value that is not a number, such as `"40"`, is refused with `` `intensity` must be a number, not "40" ``, as in every command that needs a number (`tests::params::a_malformed_required_number_is_not_reported_as_missing`). `edit.colors.adjustBalance` refuses a channel and `color.harmony` refuses `steps` or `amount` that is not a number; both read `null` as a value left out. `tests::editcolors` and `tests::colorguide` cover them. - **Convert to Indexed Color:** Edit › Edit Colors › Convert to Indexed Color… (`edit.colors.toIndexed`) stores the embedded images in the selection as indexed-color PNGs of at most 256 colors. The palette is chosen as for a PNG-8 export, as evenly spaced levels of red, green and blue, or from the document's swatches, with diffusion, pattern or noise dithering; pixels under half opacity become transparent. The dialog previews live, and OK is one undo step. This is VectorCraft's own command and counts toward no score. `tests::editcolors` and `encode::tests_palette` cover it. Scores and estimates unchanged. - **Working RGB numeric controls:** the Color panel and Color Picker read RGB, HSB, hex and Web Safe RGB values in the active working RGB space. Editing one RGB channel preserves the other native channels, including colors outside display sRGB. Regressions cover sRGB, Display P3, Wide Gamut RGB and ProPhoto RGB, picker initialization, native numeric round trips, and a slider click with one Undo step. Scores and estimates unchanged. From 0c1ddbd90b8ea72c02fc9bf8aeaf2aae57f296b1 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 14 Nov 2026 22:46:16 -0800 Subject: [PATCH 2/5] Recolor Artwork: Find dims a color that only shares a selected key's hex Find kept every color shown as the same hex as a color a selected row lists, so art in another row whose key differs only past the hex (for example rgb 191.25 191.25 255 next to a selected rgb 191 191 255) stayed undimmed, although recolor.apply recolors only the exact key. A color now stays only when its key matches, or when either entry is a "#rrggbb" one. Keys are compared as recolor.apply parses them, whatever their case or spacing. --- crates/ui-egui/src/dialogs/recolor.rs | 49 +++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/crates/ui-egui/src/dialogs/recolor.rs b/crates/ui-egui/src/dialogs/recolor.rs index 41049e6fbe..4438d59454 100644 --- a/crates/ui-egui/src/dialogs/recolor.rs +++ b/crates/ui-egui/src/dialogs/recolor.rs @@ -601,6 +601,18 @@ const FIND_TRIES: usize = 20; /// `recolor.reduce` returned them. const PRESERVED: &str = "__preserved"; +/// A row's current color as `recolor.apply` matches it: an exact color (its key, parsed whatever +/// its case or spacing), or `Err` with the hex of a "#rrggbb" entry, which matches every color +/// shown as that hex. +type FindEntry = Result; + +fn find_entry(k: &Value) -> Option { + match k.as_str() { + Some(s) => ColorKey::parse(s).map(Ok).or_else(|| color_value(k).map(|c| Err(c.to_hex()))), + None => color_value(k).map(|c| Ok(ColorKey::of(&c))), + } +} + /// While Find is on, on the Assign tab with art and rows selected: what the art previews instead of /// [`apply_params`], as `command.batch` parameters. A first `recolor.apply` gives each current color /// of the rows that aren't selected, and each preserved color, its [`FIND_DIM`] tint, or the nearest lighter one when that is a @@ -619,22 +631,30 @@ fn find_params(d: &Dialog) -> Option { } let keys = |r: &Value| r["from"].as_array().cloned().unwrap_or_default(); let found: Vec = sel.iter().flat_map(|&i| keys(&rs[i])).collect(); + // The selected rows' entries as `recolor.apply` reads them: exact colors and "#rrggbb" ones. + let found: Vec = found.iter().filter_map(find_entry).collect(); + let found_keys: Vec = found.iter().filter_map(|e| e.as_ref().ok().copied()).collect(); + let found_hex_entries: Vec = found.iter().filter_map(|e| e.as_ref().err().cloned()).collect(); + let found_hex: Vec = found.iter().map(|e| e.as_ref().map_or_else(Clone::clone, |k| k.color().to_hex())).collect(); // A dimmed color must not be one the second step recolors (by its key, or by its hex as a // "#rrggbb" entry matches): the nearest lighter tint that is none of them is taken instead. - let found_hex: Vec = found.iter().filter_map(color_value).map(|c| c.to_hex()).collect(); let dimmed = |c: Color| { (0..FIND_TRIES) .map(|n| c.tinted(FIND_DIM - n as f32 * 0.01)) - .find(|t| !found.contains(&key(t)) && !found_hex.contains(&t.to_hex())) + .find(|t| !found_keys.contains(&ColorKey::of(t)) && !found_hex.contains(&t.to_hex())) .map(|t| key(&t)) }; // The current colors of the rows that aren't selected, and the preserved colors. let preserved = d.fields.get(PRESERVED).and_then(Value::as_array).cloned().unwrap_or_default(); let others = rs.iter().enumerate().filter(|(i, _)| !sel.contains(i)).flat_map(|(_, r)| keys(r)).chain(preserved); let mut dim = serde_json::Map::new(); - // A color shown as the same hex as one a selected row lists stays: a "#rrggbb" entry on either - // side matches both. - let listed = |k: &Value| found.contains(k) || color_value(k).is_some_and(|c| found_hex.contains(&c.to_hex())); + // A color a selected row lists stays, and so does one shown as the same hex when either entry is + // "#rrggbb" (it matches both). Two keys of the same hex are separate colors. + let listed = |k: &Value| match find_entry(k) { + Some(Ok(key)) => found_keys.contains(&key) || found_hex_entries.contains(&key.color().to_hex()), + Some(Err(hex)) => found_hex.contains(&hex), + None => false, + }; for k in others.filter(|k| !listed(k)) { if let (Some(to), Some(name)) = (color_value(&k).and_then(dimmed), k.as_str()) { dim.insert(name.to_string(), to); @@ -2133,13 +2153,30 @@ mod tests { assert_eq!(second["map"][0], rows()[0], "the selected row recolors as it would"); assert_eq!((second["map"][1]["exclude"].clone(), second["map"][2]["exclude"].clone()), (json!(true), json!(true))); assert!(second.get("group").is_none() && second.get("rename").is_none(), "{second}"); - // A color that a selected row lists as a hex (or with another key of the same hex) stays. + // A color that a selected row lists as a hex stays. let mut e = d.clone(); let mut rs = rows(); rs[0]["from"] = json!(["#0000ff"]); e.fields.insert("rows".into(), json!(rs)); let map = find_params(&e).unwrap()["commands"][0]["params"]["map"].clone(); assert!(map.get("rgb 0 0 255").is_none() && map.get("rgb 0 0 128").is_some(), "{map}"); + // A color a selected row lists by its key keeps only that color: another key of the same hex + // dims, and a hex entry of it stays (it would dim the selected color too). + let mut e = d.clone(); + let mut rs = rows(); + rs[0]["from"] = json!(["rgb 191 191 255"]); + rs[1]["from"] = json!(["rgb 191.25 191.25 255", "#bfbfff"]); + e.fields.insert("rows".into(), json!(rs)); + let map = find_params(&e).unwrap()["commands"][0]["params"]["map"].clone(); + assert!(map.get("rgb 191.25 191.25 255").is_some() && map.get("#bfbfff").is_none(), "{map}"); + // A key is read whatever its case or spacing, as `recolor.apply` reads it. + let mut e = d.clone(); + let mut rs = rows(); + rs[0]["from"] = json!(["RGB 191 191 255"]); + rs[1]["from"] = json!(["rgb 191 191 255", "rgb 0 0 128"]); + e.fields.insert("rows".into(), json!(rs)); + let map = find_params(&e).unwrap()["commands"][0]["params"]["map"].clone(); + assert!(map.get("rgb 191 191 255").is_none() && map.get("rgb 0 0 128").is_some(), "{map}"); // The preserved colors dim too. let mut e = d.clone(); e.fields.insert(PRESERVED.into(), json!(["rgb 0 0 0"])); From 216e14a6a72f0572f2975b93830261142190b967 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 15 Nov 2026 07:06:15 -0800 Subject: [PATCH 3/5] Recolor Artwork: Find keeps the plain tint when only an exact key shares its hex The tint Find gives a row that isn't selected was skipped when its hex matched the hex of a selected row's exact key, though `recolor.apply` matches an exact key only by its key. The lighter tint was taken, and when every candidate collided the color was left undimmed. Only a "#rrggbb" entry is now compared by hex. --- crates/ui-egui/src/dialogs/recolor.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/dialogs/recolor.rs b/crates/ui-egui/src/dialogs/recolor.rs index a11bbd0aeb..13ca97d1aa 100644 --- a/crates/ui-egui/src/dialogs/recolor.rs +++ b/crates/ui-egui/src/dialogs/recolor.rs @@ -645,7 +645,7 @@ fn find_params(d: &Dialog) -> Option { let dimmed = |c: Color| { (0..FIND_TRIES) .map(|n| c.tinted(FIND_DIM - n as f32 * 0.01)) - .find(|t| !found_keys.contains(&ColorKey::of(t)) && !found_hex.contains(&t.to_hex())) + .find(|t| !found_keys.contains(&ColorKey::of(t)) && !found_hex_entries.contains(&t.to_hex())) .map(|t| key(&t)) }; // The current colors of the rows that aren't selected, and the preserved colors. @@ -2275,11 +2275,22 @@ mod tests { assert!(p["commands"][0]["params"]["map"].get("rgb 255 0 0").is_none(), "{p}"); // A dimmed color that a selected row lists would be recolored with it: a lighter tint is taken. let mut rs = rows(); - rs[0]["from"] = json!(["rgb 255 0 0", "rgb 191 191 255"]); + rs[0]["from"] = json!(["rgb 255 0 0", "rgb 191.25 191.25 255"]); d.fields.insert("rows".into(), json!(rs)); let p = find_params(&d).unwrap(); let to = color_value(&p["commands"][0]["params"]["map"]["rgb 0 0 255"]).unwrap(); assert_eq!(to.to_hex(), "#c2c2ff", "{p}"); + // So is one that a selected "#rrggbb" entry matches by its hex. + rs[0]["from"] = json!(["rgb 255 0 0", "#bfbfff"]); + d.fields.insert("rows".into(), json!(rs)); + let p = find_params(&d).unwrap(); + let to = color_value(&p["commands"][0]["params"]["map"]["rgb 0 0 255"]).unwrap(); + assert_eq!(to.to_hex(), "#c2c2ff", "{p}"); + // Another key of the same hex matches only itself, so the tint is kept. + rs[0]["from"] = json!(["rgb 255 0 0", "rgb 191 191 255"]); + d.fields.insert("rows".into(), json!(rs)); + let p = find_params(&d).unwrap(); + assert_eq!(p["commands"][0]["params"]["map"]["rgb 0 0 255"], dim("rgb 0 0 255"), "{p}"); // Every row selected with Preview off: nothing to show. d.fields.insert("__sel".into(), json!([0, 1, 2])); assert_eq!(find_params(&d), None); From 8e602cb980999801c13844d6bb1afff66c50e97a Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 15 Nov 2026 07:21:00 -0800 Subject: [PATCH 4/5] Recolor Artwork: Find dims a row color given as a value, not a string A row of the rows that aren't selected whose current color was given as a color value (`[r,g,b]` or a CMYK, gray or Lab object) was left undimmed, because only string entries went into the dimming map. Such a color now dims under its color key, the form `recolor.apply` matches it by. --- crates/ui-egui/src/dialogs/recolor.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/dialogs/recolor.rs b/crates/ui-egui/src/dialogs/recolor.rs index 13ca97d1aa..e15256b693 100644 --- a/crates/ui-egui/src/dialogs/recolor.rs +++ b/crates/ui-egui/src/dialogs/recolor.rs @@ -660,8 +660,13 @@ fn find_params(d: &Dialog) -> Option { None => false, }; for k in others.filter(|k| !listed(k)) { - if let (Some(to), Some(name)) = (color_value(&k).and_then(dimmed), k.as_str()) { - dim.insert(name.to_string(), to); + // A string entry keeps its own form ("#rrggbb" matches by hex); a color value goes by its key. + let name = match k.as_str() { + Some(s) => Some(s.to_string()), + None => color_value(&k).map(|c| ColorKey::of(&c).to_string()), + }; + if let (Some(to), Some(name)) = (color_value(&k).and_then(dimmed), name) { + dim.insert(name, to); } } let mut commands = vec![]; @@ -2265,6 +2270,14 @@ mod tests { let mut e = d.clone(); e.fields.insert(PRESERVED.into(), json!(["rgb 0 0 0"])); assert_eq!(find_params(&e).unwrap()["commands"][0]["params"]["map"]["rgb 0 0 0"], dim("rgb 0 0 0")); + // A color given as a value instead of a string dims under its key, as `recolor.apply` reads it. + let mut e = d.clone(); + let mut rs = rows(); + rs[1]["from"] = json!([[0.0, 0.0, 1.0], {"c": 0, "m": 100, "y": 100, "k": 0}]); + e.fields.insert("rows".into(), json!(rs)); + let map = find_params(&e).unwrap()["commands"][0]["params"]["map"].clone(); + assert_eq!(map["rgb 0 0 255"], dim("rgb 0 0 255"), "{map}"); + assert_eq!(map["cmyk 0 100 100 0"], dim("cmyk 0 100 100 0"), "{map}"); // A color a selected row lists is not dimmed; with Preview off only the dimming runs. let mut rs = rows(); rs[2]["from"] = json!(["rgb 0 255 0", "rgb 255 0 0"]); From d089df0ab51719ce5dd9f78702b1f03cb432394e Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 15 Nov 2026 08:28:08 -0800 Subject: [PATCH 5/5] Recolor Artwork: Find tries every lighter tint down to 1% When a selected row listed the twenty tints from 25% through 6% of a color of an unselected row, Find left that color undimmed although its 5% tint was free. The search now tries each tint a percent apart from 25% down to 1% before leaving a color as it is. --- crates/ui-egui/src/dialogs/recolor.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/dialogs/recolor.rs b/crates/ui-egui/src/dialogs/recolor.rs index e15256b693..238205216e 100644 --- a/crates/ui-egui/src/dialogs/recolor.rs +++ b/crates/ui-egui/src/dialogs/recolor.rs @@ -599,8 +599,9 @@ fn apply_params(d: &Dialog) -> Value { /// aren't selected shows while it is on (VectorCraft's own amount: [`Color::tinted`]). const FIND: &str = "findColors"; const FIND_DIM: f32 = 0.25; -/// The lighter tints, a percent apart, tried when a dimmed color is one a selected row lists. -const FIND_TRIES: usize = 20; +/// The tints tried when a dimmed color is one a selected row lists: [`FIND_DIM`] and each lighter +/// one a percent apart, down to 1%. +const FIND_TRIES: usize = 25; /// The colors of the art that the rows leave out (Preserve White, Black and Grays), as /// `recolor.reduce` returned them. const PRESERVED: &str = "__preserved"; @@ -2299,6 +2300,15 @@ mod tests { let p = find_params(&d).unwrap(); let to = color_value(&p["commands"][0]["params"]["map"]["rgb 0 0 255"]).unwrap(); assert_eq!(to.to_hex(), "#c2c2ff", "{p}"); + // When the twenty tints from 25% through 6% are all listed, the 5% tint is taken. + let blue = color_value(&json!("rgb 0 0 255")).unwrap(); + let tint = |n: usize| key(&blue.tinted(FIND_DIM - n as f32 * 0.01)); + let mut from = vec![json!("rgb 255 0 0")]; + from.extend((0..20).map(tint)); + rs[0]["from"] = json!(from); + d.fields.insert("rows".into(), json!(rs)); + let p = find_params(&d).unwrap(); + assert_eq!(p["commands"][0]["params"]["map"]["rgb 0 0 255"], tint(20), "{p}"); // Another key of the same hex matches only itself, so the tint is kept. rs[0]["from"] = json!(["rgb 255 0 0", "rgb 191 191 255"]); d.fields.insert("rows".into(), json!(rs));