From d80024eff6545f05495169842b0b425b53404260 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Tue, 20 Oct 2026 19:41:56 -0700 Subject: [PATCH] View: Show Live Paint Gaps highlights the gaps of the selected Live Paint groups The reference app's View menu has Show Live Paint Gaps, which highlights the gaps in the selected Live Paint group based on the gap options set for that group. VectorCraft had no such item. View > Show Live Paint Gaps (view.livePaintGaps) turns the highlight on or off, and the item reads Hide Live Paint Gaps while it is on. While it is on and edges show, the canvas draws the gaps of each selected Live Paint group up to its Paint Stops At size, the gaps that livePaint.info lists. It draws them as the Gap Options preview does, as lines in the group's Gap Preview Color. The setting works as View > Hide Text Threads does: one setting covers every document, it is saved with the interface state, and Hide Edges hides the gaps. It is off by default. Docs: docs/mcp.md, docs/target-app-parity.md. --- crates/engine/src/cmd/buildcmds.rs | 6 ++ crates/engine/src/cmd/mod.rs | 3 + crates/ui-egui/src/canvas.rs | 84 +++++++++++++++++++ crates/ui-egui/src/dialogs/live_paint_gaps.rs | 29 +++++-- crates/ui-egui/src/i18n/bg.tsv | 2 + crates/ui-egui/src/i18n/cs.tsv | 2 + crates/ui-egui/src/i18n/da.tsv | 2 + crates/ui-egui/src/i18n/de.tsv | 2 + crates/ui-egui/src/i18n/el.tsv | 2 + crates/ui-egui/src/i18n/es.tsv | 2 + crates/ui-egui/src/i18n/fi.tsv | 2 + crates/ui-egui/src/i18n/fr.tsv | 2 + crates/ui-egui/src/i18n/hu.tsv | 2 + crates/ui-egui/src/i18n/id.tsv | 2 + crates/ui-egui/src/i18n/it.tsv | 2 + crates/ui-egui/src/i18n/ja.tsv | 2 + crates/ui-egui/src/i18n/ka.tsv | 2 + crates/ui-egui/src/i18n/ko.tsv | 2 + crates/ui-egui/src/i18n/nl.tsv | 2 + crates/ui-egui/src/i18n/pl.tsv | 2 + crates/ui-egui/src/i18n/pt-br.tsv | 2 + crates/ui-egui/src/i18n/ru.tsv | 2 + crates/ui-egui/src/i18n/sv.tsv | 2 + crates/ui-egui/src/i18n/tests.rs | 1 + crates/ui-egui/src/i18n/uk.tsv | 2 + crates/ui-egui/src/i18n/vi.tsv | 2 + crates/ui-egui/src/i18n/zh-hans.tsv | 2 + crates/ui-egui/src/i18n/zh-hant.tsv | 2 + crates/ui-egui/src/lib.rs | 7 ++ crates/ui-egui/src/menus.rs | 12 +++ crates/ui-egui/src/state.rs | 5 ++ docs/mcp.md | 3 + docs/target-app-parity.md | 1 + 33 files changed, 192 insertions(+), 5 deletions(-) diff --git a/crates/engine/src/cmd/buildcmds.rs b/crates/engine/src/cmd/buildcmds.rs index 92d001244..3c8c5560e 100644 --- a/crates/engine/src/cmd/buildcmds.rs +++ b/crates/engine/src/cmd/buildcmds.rs @@ -662,6 +662,12 @@ fn open_gaps(group: &Node, options: GapOptions) -> Vec<[Point; 2]> { po::find_gaps(&source_shapes(&source_nodes(group)).0, options.size.points()) } +/// The gaps View › Show Live Paint Gaps shows on `group`: those up to its Gap Options' size wide, +/// as `livePaint.info` lists them. None when `group` is not a Live Paint group. +pub fn live_paint_gaps(group: &Node) -> Vec<[Point; 2]> { + if is_live_paint(group) { open_gaps(group, group.gap_options()) } else { vec![] } +} + fn lp_gap_options(s: &mut Session, p: &Value) -> Result { const C: &str = "livePaint.gapOptions"; let detect = match p.get("detect").filter(|v| !v.is_null()) { diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs index 5ec103e89..681c2bcc5 100644 --- a/crates/engine/src/cmd/mod.rs +++ b/crates/engine/src/cmd/mod.rs @@ -525,6 +525,9 @@ pub use flatten::FlattenerPreset; /// The shapes a group selected alone gives the Pathfinder panel. pub use pathops::shape_count; +/// The gaps View › Show Live Paint Gaps shows on a Live Paint group. +pub use buildcmds::live_paint_gaps; + /// Command ids kept only so older scripts keep working: `(alias, the command it duplicates)`. /// They run as before but the command palette leaves them out (it lists the command once). pub const ALIASES: &[(&str, &str)] = &[("object.convertDocumentColorMode", "file.documentColorMode")]; diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 8c989b2e9..ba15edab8 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -553,6 +553,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { if app.ui.view.text_threads { thread_overlay(app, &painter, &xf); } + if app.ui.view.live_paint_gaps { + overlays.extend(live_paint_gap_overlays(app)); + } if app.ui.view.hidden_chars { app.canvas.hidden_chars = hidden_chars; hidden_chars_overlay(app, &painter, &xf); @@ -2246,6 +2249,22 @@ fn thread_overlay(app: &VectorcraftApp, p: &egui::Painter, xf: &Xf) { } } +/// View → Show Live Paint Gaps: the gaps of the selected Live Paint groups in each group's Gap +/// Preview Color ([`crate::dialogs::live_paint_gaps::selected_gaps`]). Cached per revision. +fn live_paint_gap_overlays(app: &mut VectorcraftApp) -> Vec { + let Some(st) = app.session.active() else { return vec![] }; + let key = (st.uid, st.revision); + let gaps = match &app.canvas.live_paint_gaps { + Some((k, gaps)) if *k == key => gaps.clone(), + _ => { + let gaps = std::sync::Arc::new(crate::dialogs::live_paint_gaps::selected_gaps(app)); + app.canvas.live_paint_gaps = Some((key, gaps.clone())); + gaps + } + }; + gaps.to_vec() +} + /// Simplify's Show Original Path: the paths its preview ran on, as they were before it, in a band /// 3 screen points wide of their layer's color at 40%, under the selection's outlines. Nothing is drawn /// when they have more than [`OVERLAY_MAX_ANCHORS`] anchor points. @@ -5840,6 +5859,71 @@ mod tests { assert!(area(&app), "a double-click on the widget converts to area type"); } + /// View → Show Live Paint Gaps draws the gaps of each selected Live Paint group in the group's + /// Gap Preview Color, and nothing for a group or path that is not a selected Live Paint group; + /// Hide Live Paint Gaps and Hide Edges hide them. + #[test] + fn show_live_paint_gaps_draws_the_selected_groups_gaps_in_their_preview_color() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + // Open paths round a square, each ending 2 pt short of its start: one Small Gap each. + let square = |app: &mut VectorcraftApp, x: i64| { + let anchors = [(0, 0), (100, 0), (100, 100), (0, 100), (0, 2)].map(|(dx, y)| json!({"x": x + dx, "y": y})); + app.session.execute("path.create", &json!({ "anchors": anchors })).unwrap()["id"].as_u64().unwrap() + }; + let mut groups = vec![]; + for (x, color) in [(0, "#123456"), (150, "#654321")] { + let p = square(&mut app, x); + app.session.execute("select.set", &json!({"ids": [p]})).unwrap(); + let g = app.session.execute("livePaint.make", &json!({})).unwrap()["id"].as_u64().unwrap(); + app.session.execute("livePaint.gapOptions", &json!({"id": g, "color": color})).unwrap(); + groups.push(g); + } + let plain = square(&mut app, 300); + let ctx = egui::Context::default(); + let gaps = |app: &mut VectorcraftApp, rgb: [u8; 3]| { + let solid = egui::epaint::ColorMode::Solid(c32(rgb)); + shapes(app, &ctx).iter().filter(|s| matches!(s, Shape::Path(ps) if ps.stroke.color == solid && ps.stroke.width == 2.0)).count() + }; + let (first, second) = ([0x12, 0x34, 0x56], [0x65, 0x43, 0x21]); + // The color the plain path's gaps would be drawn in. + let plain_color = vectorcraft_doc::GapOptions::default().color; + app.session.execute("select.set", &json!({"ids": [groups[0], plain]})).unwrap(); + assert_eq!((gaps(&mut app, first), gaps(&mut app, second)), (0, 0), "off until the item is chosen"); + // View → Show Live Paint Gaps runs the command. + let tree = crate::menus::menu_tree(); + let (_, view) = tree.iter().find(|(t, _)| *t == "View").unwrap(); + assert!(view.iter().any(|i| matches!(i, crate::menus::Item::Cmd("Show Live Paint Gaps", "view.livePaintGaps", _))), "{view:?}"); + crate::menus::invoke(&mut app, "view.livePaintGaps", json!({})); + assert!(app.ui.view.live_paint_gaps); + assert_eq!( + (gaps(&mut app, first), gaps(&mut app, second), gaps(&mut app, plain_color)), + (1, 0, 0), + "the selected group only; the plain path shows none" + ); + app.session.execute("select.set", &json!({"ids": [groups[1]]})).unwrap(); + assert_eq!((gaps(&mut app, first), gaps(&mut app, second)), (0, 1)); + // The lines follow the group's Gap Preview Color and Paint Stops At size. + app.session.execute("livePaint.gapOptions", &json!({"id": groups[1], "color": "#123456"})).unwrap(); + assert_eq!((gaps(&mut app, first), gaps(&mut app, second)), (1, 0)); + app.session.execute("livePaint.gapOptions", &json!({"id": groups[1], "size": 1})).unwrap(); + assert_eq!(gaps(&mut app, first), 0, "a 2 pt gap is wider than 1 pt"); + app.session.execute("livePaint.gapOptions", &json!({"id": groups[1], "size": "small"})).unwrap(); + app.ui.view.edges = false; + assert_eq!(gaps(&mut app, first), 0, "Hide Edges hides them"); + app.ui.view.edges = true; + assert_eq!(gaps(&mut app, first), 1); + app.run("view.livePaintGaps", json!({})).unwrap(); + assert!(!app.ui.view.live_paint_gaps); + assert_eq!(gaps(&mut app, first), 0, "View → Hide Live Paint Gaps"); + // The setting is kept with the interface state; a state saved before it existed loads with it off. + app.ui.view.live_paint_gaps = true; + let mut saved = serde_json::to_value(&app.ui).unwrap(); + assert_eq!(saved["view"]["live_paint_gaps"], json!(true)); + saved["view"].as_object_mut().unwrap().remove("live_paint_gaps"); + assert!(!serde_json::from_value::(saved).unwrap().view.live_paint_gaps); + } + /// #511: a star shows a widget in each of its ten corners with Direct Selection (none with /// the Selection tool: it isn't a live shape); View → Hide Corner Widget hides them. #[test] diff --git a/crates/ui-egui/src/dialogs/live_paint_gaps.rs b/crates/ui-egui/src/dialogs/live_paint_gaps.rs index 3d99287f7..98c7abfba 100644 --- a/crates/ui-egui/src/dialogs/live_paint_gaps.rs +++ b/crates/ui-egui/src/dialogs/live_paint_gaps.rs @@ -2,7 +2,8 @@ //! or Custom with its size), Gap Preview Color (a layer color or a custom color) and Close Gaps //! With Paths for the selected Live Paint groups, opened on the first group's options. With Preview //! on, the options apply to the groups live and the gaps they find draw on the canvas in the preview -//! color; OK keeps them as one undo step (`livePaint.gapOptions`). +//! color; OK keeps them as one undo step (`livePaint.gapOptions`). View › Show Live Paint Gaps +//! draws the gaps of the selected groups the same way ([`selected_gaps`]). //! //! Fields: `detect`, `size` (`small` | `medium` | `large` | `custom`), `custom` (pt), `color` //! (`#rrggbb`, or a preset's name such as "Red"), `closeGaps`, `preview`, `__ids` (the groups) and @@ -139,14 +140,32 @@ pub(crate) fn overlays(app: &VectorcraftApp) -> Vec { .filter_map(|gap| { let v: Vec = gap.as_array()?.iter().filter_map(Value::as_f64).collect(); let [x0, y0, x1, y1] = v.as_slice() else { return None }; - let mut path = BezPath::new(); - path.move_to(Point::new(*x0, *y0)); - path.line_to(Point::new(*x1, *y1)); - Some(Overlay::Path { path, color: [r, g, b], width: 2.0, dashed: false }) + Some(gap_line(Point::new(*x0, *y0), Point::new(*x1, *y1), [r, g, b])) }) .collect() } +/// View › Show Live Paint Gaps: the gaps of the selected Live Paint groups, as lines in each +/// group's Gap Preview Color, drawn as the preview draws them. +pub(crate) fn selected_gaps(app: &VectorcraftApp) -> Vec { + let Some(st) = app.session.active() else { return vec![] }; + let groups = st.selection.objects.iter().filter_map(|id| st.doc.node(*id)).filter(|n| vectorcraft_tools::builder::is_live_paint(n)); + groups + .flat_map(|n| { + let color = n.gap_options().color; + vectorcraft_engine::cmd::live_paint_gaps(n).into_iter().map(move |[a, b]| gap_line(a, b, color)) + }) + .collect() +} + +/// A gap from `a` to `b` as a line in `color`, 2 screen points wide. +fn gap_line(a: Point, b: Point, color: [u8; 3]) -> Overlay { + let mut path = BezPath::new(); + path.move_to(a); + path.line_to(b); + Overlay::Path { path, color, width: 2.0, dashed: false } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index dfc8c61a8..9fba45fef 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -1175,6 +1175,8 @@ Large Gaps Големи пролуки Gap Preview Color: Цвят за преглед на пролуките: Close Gaps With Paths Затваряне на пролуките с пътища + Show Live Paint Gaps Показване на пролуките на динамичното оцветяване + Hide Live Paint Gaps Скриване на пролуките на динамичното оцветяване Select Fills Избиране на запълвания Select Strokes Избиране на щрихове Gaussian Blur… Гаусово размазване… diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index dda91b33c..e74c53ae6 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -265,6 +265,8 @@ Large Gaps Velké mezery Gap Preview Color: Barva náhledu mezer: Close Gaps With Paths Uzavřít mezery cestami + Show Live Paint Gaps Zobrazit mezery živé malby + Hide Live Paint Gaps Skrýt mezery živé malby Select Fills Vybírat výplně Select Strokes Vybírat tahy Image Trace Vektorizace obrazu diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 4d654a510..c970314d5 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -5093,6 +5093,8 @@ swatch Bright Violet Kraftig violet Gap Detection Registrering af huller Gap Options Indstillinger for huller Gap Preview Color: Eksempelfarve for huller: + Show Live Paint Gaps Vis huller i live-maling + Hide Live Paint Gaps Skjul huller i live-maling Graph Column Diagramsøjle JavaScript Files JavaScript-filer JavaScript: JavaScript: diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index dc3ce6217..9b1477b9d 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1193,6 +1193,8 @@ file-filter Templates Vorlagen Large Gaps Große Lücken Gap Preview Color: Vorschaufarbe für Lücken: Close Gaps With Paths Lücken mit Pfaden schließen + Show Live Paint Gaps Lücken beim interaktiven Malen einblenden + Hide Live Paint Gaps Lücken beim interaktiven Malen ausblenden Select Fills Flächen auswählen Select Strokes Konturen auswählen Gaussian Blur… Gaußscher Weichzeichner… diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 702c23571..ec60891be 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1140,6 +1140,8 @@ Large Gaps Μεγάλα διάκενα Gap Preview Color: Χρώμα προεπισκόπησης διακένων: Close Gaps With Paths Κλείσιμο διακένων με διαδρομές + Show Live Paint Gaps Εμφάνιση διακένων δυναμικής βαφής + Hide Live Paint Gaps Απόκρυψη διακένων δυναμικής βαφής Select Fills Επιλογή γεμισμάτων Select Strokes Επιλογή περιγραμμάτων Gaussian Blur… Θόλωση Γκάους… diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index fa88b89e8..fa35bc663 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1200,6 +1200,8 @@ Large Gaps Huecos grandes Gap Preview Color: Color de previsualización de huecos: Close Gaps With Paths Cerrar huecos con trazados + Show Live Paint Gaps Mostrar huecos de pintura interactiva + Hide Live Paint Gaps Ocultar huecos de pintura interactiva Select Fills Seleccionar rellenos Select Strokes Seleccionar trazos Gaussian Blur… Desenfoque gaussiano… diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 34cd67b59..7508c9366 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -272,6 +272,8 @@ Large Gaps Suuret aukot Gap Preview Color: Aukkojen esikatseluväri: Close Gaps With Paths Sulje aukot poluilla + Show Live Paint Gaps Näytä elävän maalauksen aukot + Hide Live Paint Gaps Piilota elävän maalauksen aukot Select Fills Valitse täytöt Select Strokes Valitse viivat Image Trace Kuvan jäljitys diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 1e4e5ceab..3f0a5bdd9 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1199,6 +1199,8 @@ Large Gaps Grands espaces Gap Preview Color: Couleur d’aperçu des espaces : Close Gaps With Paths Fermer les espaces avec des tracés + Show Live Paint Gaps Afficher les espaces de peinture dynamique + Hide Live Paint Gaps Masquer les espaces de peinture dynamique Select Fills Sélectionner les fonds Select Strokes Sélectionner les contours Gaussian Blur… Flou gaussien… diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index e0162c02f..68333461f 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1505,6 +1505,8 @@ file-filter Templates Sablonok Large Gaps Nagy hézagok Gap Preview Color: Hézagok előnézeti színe: Close Gaps With Paths Hézagok lezárása útvonalakkal + Show Live Paint Gaps Élő festés hézagainak megjelenítése + Hide Live Paint Gaps Élő festés hézagainak elrejtése Select Fills Kitöltések kijelölése Select Strokes Körvonalak kijelölése Gaussian Blur Gauss-elmosás diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 165ffc9b3..eb498472a 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -5093,6 +5093,8 @@ swatch Bright Violet Violet cerah Gap Detection Deteksi celah Gap Options Opsi celah Gap Preview Color: Warna pratinjau celah: + Show Live Paint Gaps Tampilkan celah Live Paint + Hide Live Paint Gaps Sembunyikan celah Live Paint Graph Column Kolom grafik JavaScript Files File JavaScript JavaScript: JavaScript: diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 743861528..dded3a698 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1209,6 +1209,8 @@ file-filter Templates Modelli Large Gaps Spazi vuoti grandi Gap Preview Color: Colore anteprima spazi vuoti: Close Gaps With Paths Chiudi spazi vuoti con tracciati + Show Live Paint Gaps Mostra spazi vuoti pittura dinamica + Hide Live Paint Gaps Nascondi spazi vuoti pittura dinamica Select Fills Seleziona riempimenti Select Strokes Seleziona tracce Gaussian Blur… Sfocatura gaussiana… diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 6ab4d5723..f80f2550f 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -263,6 +263,8 @@ Large Gaps 大きい間隔 Gap Preview Color: 間隔のプレビューカラー: Close Gaps With Paths パスで間隔を閉じる + Show Live Paint Gaps ライブペイントの間隔を表示 + Hide Live Paint Gaps ライブペイントの間隔を隠す Select Fills 塗りを選択 Select Strokes 線を選択 Image Trace 画像トレース diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 9fcfb230e..257e0b823 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -4980,6 +4980,8 @@ imageTrace Outline კონტური Gap Detection ღრეჩოების აღმოჩენა Gap Options ღრეჩოს პარამეტრები Gap Preview Color: ღრეჩოს ნახვის ფერი: + Show Live Paint Gaps ცოცხალი შეღებვის ღრეჩოების ჩვენება + Hide Live Paint Gaps ცოცხალი შეღებვის ღრეჩოების დამალვა Glow Brightness ნათების სიკაშკაშე Glow Size ნათების ზომა Graph Column დიაგრამის სვეტი diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index f38b119f9..c0a9b365c 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -259,6 +259,8 @@ Large Gaps 큰 간격 Gap Preview Color: 간격 미리 보기 색상: Close Gaps With Paths 패스로 간격 닫기 + Show Live Paint Gaps 라이브 페인트 간격 표시 + Hide Live Paint Gaps 라이브 페인트 간격 숨기기 Select Fills 칠 선택 Select Strokes 선 선택 Image Trace 이미지 추적 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index a8231da68..ea9953ac7 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -5105,6 +5105,8 @@ swatch Bright Violet Felviolet Gap Detection Openingen detecteren Gap Options Openingsopties Gap Preview Color: Voorbeeldkleur openingen: + Show Live Paint Gaps Live Paint-openingen tonen + Hide Live Paint Gaps Live Paint-openingen verbergen Graph Column Grafiekkolom JavaScript Files JavaScript-bestanden JavaScript: JavaScript: diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 49a1a527d..e49f17889 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -230,6 +230,8 @@ Large Gaps Duże przerwy Gap Preview Color: Kolor podglądu przerw: Close Gaps With Paths Zamknij przerwy ścieżkami + Show Live Paint Gaps Pokaż przerwy aktywnego malowania + Hide Live Paint Gaps Ukryj przerwy aktywnego malowania Select Fills Zaznaczaj wypełnienia Select Strokes Zaznaczaj obrysy Image Trace Obrys obrazu diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index b59785737..4a384f66a 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1057,6 +1057,8 @@ Large Gaps Lacunas Grandes Gap Preview Color: Cor de Visualização das Lacunas: Close Gaps With Paths Fechar Lacunas com Caminhos + Show Live Paint Gaps Mostrar Lacunas da Pintura ao Vivo + Hide Live Paint Gaps Ocultar Lacunas da Pintura ao Vivo Select Fills Selecionar Preenchimentos Select Strokes Selecionar Traçados Gaussian Blur… Desfoque Gaussiano… diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 08590829e..1ab197483 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -1207,6 +1207,8 @@ file-filter Templates Шаблоны Large Gaps Большие зазоры Gap Preview Color: Цвет просмотра зазоров: Close Gaps With Paths Закрыть зазоры контурами + Show Live Paint Gaps Показать зазоры быстрой заливки + Hide Live Paint Gaps Скрыть зазоры быстрой заливки Select Fills Выделять заливки Select Strokes Выделять обводки Gaussian Blur… Размытие по Гауссу… diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 5f66db08e..841da74c2 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -1177,6 +1177,8 @@ Large Gaps Stora mellanrum Gap Preview Color: Förhandsgranskningsfärg för mellanrum: Close Gaps With Paths Stäng mellanrum med banor + Show Live Paint Gaps Visa mellanrum i direktmålning + Hide Live Paint Gaps Dölj mellanrum i direktmålning Select Fills Markera fyllningar Select Strokes Markera konturer Gaussian Blur… Gaussisk oskärpa… diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index e6171cc24..80b8a657d 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -931,6 +931,7 @@ fn toggled_labels() -> Vec { "view.textThreads", "type.hiddenCharacters", "view.gradientAnnotator", + "view.livePaintGaps", "view.artboards", "view.template", "view.rulers", diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 0fc8c3252..777864cb0 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -1203,6 +1203,8 @@ file-filter Templates Шаблони Large Gaps Великі проміжки Gap Preview Color: Колір перегляду проміжків: Close Gaps With Paths Закрити проміжки контурами + Show Live Paint Gaps Показати проміжки швидкої заливки + Hide Live Paint Gaps Приховати проміжки швидкої заливки Select Fills Виділяти заливки Select Strokes Виділяти обведення Gaussian Blur… Розмиття за Гауссом… diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index d3cfb1e75..790eb946f 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1395,6 +1395,8 @@ file-filter Templates Mẫu Large Gaps Khoảng cách lớn Gap Preview Color: Màu xem trước khoảng cách: Close Gaps With Paths Đóng khoảng cách bằng đường dẫn + Show Live Paint Gaps Hiện khoảng cách tô trực tiếp + Hide Live Paint Gaps Ẩn khoảng cách tô trực tiếp Select Fills Chọn vùng tô Select Strokes Chọn nét Gaussian Blur Mờ Gaussian diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index bde4e7c06..a1bc00a65 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1156,6 +1156,8 @@ file-filter Templates 模板 Large Gaps 大间隙 Gap Preview Color: 间隙预览颜色: Close Gaps With Paths 用路径封闭间隙 + Show Live Paint Gaps 显示实时上色间隙 + Hide Live Paint Gaps 隐藏实时上色间隙 Select Fills 选择填色 Select Strokes 选择描边 Gaussian Blur… 高斯模糊… diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index fee76ee1c..9c25e9db6 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1180,6 +1180,8 @@ file-filter Templates 範本 Large Gaps 大間隙 Gap Preview Color: 間隙預覽顏色: Close Gaps With Paths 以路徑封閉間隙 + Show Live Paint Gaps 顯示即時上色間隙 + Hide Live Paint Gaps 隱藏即時上色間隙 Select Fills 選取填色 Select Strokes 選取筆畫 Gaussian Blur… 高斯模糊… diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 69577b95c..2d4970a12 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -321,6 +321,8 @@ pub struct CanvasCache { pub slices: Option, /// The print tiling's pages for (document uid, revision) (View → Show Print Tiling). pub print_tiling: Option, + /// The gap lines View → Show Live Paint Gaps draws for (document uid, revision). + pub live_paint_gaps: Option, /// The marks Type → Show Hidden Characters drew on each text object in the last frame. A frame /// that does not draw them drops them ([`canvas::show`]). pub hidden_chars: canvas::HiddenChars, @@ -337,6 +339,10 @@ pub struct CanvasCache { /// [`CanvasCache::slices`]: the layout of the slices of (document uid, revision). pub type SliceCache = ((u64, u64), std::sync::Arc>); +/// [`CanvasCache::live_paint_gaps`]: the gap lines of the selected Live Paint groups of (document +/// uid, revision). +pub type LivePaintGapsCache = ((u64, u64), std::sync::Arc>); + /// [`CanvasCache::print_tiling`]: the print tiling of (document uid, revision). pub type PrintTilingCache = ((u64, u64), std::sync::Arc>); @@ -539,6 +545,7 @@ impl VectorcraftApp { worker_started: false, slices: None, print_tiling: None, + live_paint_gaps: None, hidden_chars: Default::default(), selection_box: None, selection_bounds: None, diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 9a7739f8e..aaf3cd171 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -207,6 +207,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ ), ("view.textThreads", "Hide Text Threads", "Cmd+Shift+Y", "{} toggle the thread lines between threaded text frames"), ("view.gradientAnnotator", "Hide Gradient Annotator", "Cmd+Alt+G", "{} toggle the Gradient tool's annotator"), + ( + "view.livePaintGaps", + "Show Live Paint Gaps", + "", + "{} toggle drawing the gaps of the selected Live Paint groups (those livePaint.info lists) in each group's Gap Preview Color while edges show → true when on", + ), ("type.hiddenCharacters", "Show Hidden Characters", "Cmd+Alt+I", "{} toggle markers for spaces, paragraph ends and story ends"), ("type.alignLeft", "Align left", "Cmd+Shift+L", "{} align the selected type's paragraphs (or those the Type tool's selection touches) left"), ( @@ -1231,6 +1237,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option flag(&mut app.ui.view.gradient_annotator), + "view.livePaintGaps" => flag(&mut app.ui.view.live_paint_gaps), "effect.last" => match app.last_effect.clone() { Some((e, params)) => { let label = vectorcraft_effects::effect_info(&e).map(|i| i.label.trim_end_matches('…').to_string()).unwrap_or_else(|| e.clone()); @@ -1872,6 +1879,7 @@ pub fn dynamic_label(app: &VectorcraftApp, id: &str, label: &str) -> String { "view.textThreads" => if v.text_threads { "Hide Text Threads" } else { "Show Text Threads" }.into(), "type.hiddenCharacters" => if v.hidden_chars { "Hide Hidden Characters" } else { "Show Hidden Characters" }.into(), "view.gradientAnnotator" => if v.gradient_annotator { "Hide Gradient Annotator" } else { "Show Gradient Annotator" }.into(), + "view.livePaintGaps" => if v.live_paint_gaps { "Hide Live Paint Gaps" } else { "Show Live Paint Gaps" }.into(), "effect.last" => match &app.last_effect { Some((e, _)) => format!("Last Effect: {}", vectorcraft_effects::effect_info(e).map(|i| i.label).unwrap_or(e.as_str())), None => label.into(), @@ -2900,6 +2908,7 @@ pub fn menu_tree_named(english_names: bool) -> Vec<(&'static str, Vec)> { c("Hide Text Threads", "view.textThreads"), c("Hide Gradient Annotator", "view.gradientAnnotator"), c("Hide Corner Widget", "view.cornerWidget"), + c("Show Live Paint Gaps", "view.livePaintGaps"), Sep, sub( "Guides", @@ -3970,6 +3979,8 @@ pub const DYNAMIC_LABELS: &[&str] = &[ "Show Hidden Characters", "Hide Gradient Annotator", "Show Gradient Annotator", + "Hide Live Paint Gaps", + "Show Live Paint Gaps", "Hide Artboards", "Show Artboards", "Hide Rulers", @@ -4243,6 +4254,7 @@ mod tests { for (id, on, off) in [ ("view.textThreads", "Hide Text Threads", "Show Text Threads"), ("view.gradientAnnotator", "Hide Gradient Annotator", "Show Gradient Annotator"), + ("view.livePaintGaps", "Show Live Paint Gaps", "Hide Live Paint Gaps"), ("type.hiddenCharacters", "Show Hidden Characters", "Hide Hidden Characters"), ("view.template", "Hide Template", "Show Template"), ] { diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index d6e5a179e..2393d4b8a 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -231,6 +231,10 @@ pub struct ViewFlags { pub text_threads: bool, /// View → Show/Hide Gradient Annotator. pub gradient_annotator: bool, + /// View → Show/Hide Live Paint Gaps: draw the gaps of the selected Live Paint groups in their Gap + /// Preview Color. + #[serde(default)] + pub live_paint_gaps: bool, /// Type → Show Hidden Characters. pub hidden_chars: bool, /// View → Hide/Show Template: template layers drawn (dimmed) or left out of the canvas. @@ -258,6 +262,7 @@ impl Default for ViewFlags { snap_to_glyph: false, text_threads: true, gradient_annotator: true, + live_paint_gaps: false, hidden_chars: false, templates: true, } diff --git a/docs/mcp.md b/docs/mcp.md index fed198256..b046b39ae 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1270,6 +1270,9 @@ already walks Image Trace. Short agent entry points: and `livePaint.info` list the gaps up to `size` wide that no path closes, as `[x0, y0, x1, y1]`. `livePaint.merge` keeps the bottom group's options when its Gap Detection is on, and otherwise turns Gap Detection on with `small` when another merged group has it on. When Gap Detection is off in every merged group, it stays off. + `view.livePaintGaps` (View › Show Live Paint Gaps, or Hide Live Paint Gaps while it is on) turns the drawing of + those gaps on or off. While it is on and edges show, the canvas draws the gaps of each selected Live Paint group in + that group's Gap Preview Color. The reply is the new state (`true` when on). The Live Paint Selection tool's options (`tool.setOption {tool: "livePaintSelection", values}`; double-clicking the tool opens them, dialog `livePaintSelectionOptions`) are `selectFills` and `selectStrokes` (both on), `color` (the highlight color, `#rrggbb` or a layer color's name; `#ff2a2a`) and `width` (the highlight's width, 1 to 20 screen diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 4160b8aec..118bba4fc 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -669,6 +669,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Advanced art:** live Blends (steps/distance/smooth colour, editable spine, anchor-targeted Blend tool, faithful interpolation, knockout), Envelope Distort (warp/mesh/top object, Reset, full Envelope Options, mesh handles, Edit Contents; type, images, symbols, gradients, patterns and appearance distorted everywhere), Liquify tools with options and pen pressure, Puppet Warp with rotating pins, Perspective Grid with presets, movable planes and projected type (choosing a perspective tool shows the grid; Hide Grid hides it with the tool still chosen), Gradient Mesh, Shape Builder (stroke-only open paths and lines cut the regions they cross and enclose regions of their own, and regions they bound merge without seams where the lines cross at rounded coordinates (#893); filled open paths count as closed; erase mode while Alt/Option is held: a minus pointer, red highlights of what will go, a click or drag deletes the regions touched, the pieces of open lines and the edges (outline pieces between the points where paths meet, opening their paths there) it passes over, one undo step, `shapeBuilder.merge {erase}` and `shapeBuilder.regions` edges over MCP), Live Paint (every path an edge, open lines included), Image Trace (12 presets, colour layers traced in parallel; a trace past two million anchor points is refused with advice rather than running out of memory, an image over 64 megapixels is traced scaled down to that size (aspect kept, paths at the image's own size) and one over 192 megapixels is refused, and a selection too large to outline shows its bounds (#525); Object › Image Trace › Make and Make and Expand trace at once with the Default preset, #543; with an image selected the Control bar and the Properties panel show Image Trace, whose arrow traces with any preset, Mask (`object.maskImage`: a clipping path on the image's outline, selected so its handles crop), Crop Image (a crop box on the image, starting on its part over the artboard, dragged by its handles or inside it, the outside dimmed, with X/Y/W/H, Apply and Cancel in the Control bar, Enter and Escape, #734; `object.cropImage {rect}` for agents, `{trim: true}` cuts an image to its pixels that aren't fully transparent, pixel for pixel) and Embed or Unembed in place of Fill and Stroke; with an Image Trace object selected, its Preset (choosing another traces again), View, the Image Trace panel and Expand, plus Release in Properties; the five View modes (Tracing Result, with Outlines, Outlines, Outlines with Source Image, Source Image) redraw a trace on screen without tracing again, are saved with it and `imageTrace.setView` sets them, while exports and Expand keep the result; the panel's Advanced section is a header that stays open or closed as it was left (part of #641); Color mode's Palette: Limited, Full Tone (as many colours as the tones need, Less … More), Automatic (flat art with its own colours, else Full Tone; a heuristic) and Document Library (the most used colours of the document's swatches or a swatch library, filled with the swatch colours as they are), part of #641; Advanced › Create: Fills and/or Strokes, where lines up to the Stroke width trace as stroked centre lines (distance transform, thinning, a skeleton graph with spurs pruned, curve fitting; one width per line; without Fills, wider areas become stroked outlines), part of #641), pattern swatches with pattern editing mode, live Repeat (radial/grid/mirror). A single selected mirror repeat shows a prominent axis with a center move handle and two rotation handles. Drag the line or center to change spacing, or an end to change angle; Shift snaps rotation to 45°. The original artwork stays in place, the reflected copy previews live, Escape cancels and each drag takes one undo step through `object.repeat.options`. Controls follow zoom/view rotation and hide with edges or the bounding box. Engine and headless canvas regressions passed for gestures, history and visibility; native move, rotation and Escape screenshots were reviewed. [Public mirror-repeat reference](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/create-and-edit-patterns/set-mirror-repeat-options.html). Object › Expand turns radial, grid and mirror repeats into groups of their instances (#1765, #1756). - Create Strokes uses exact squared Euclidean distances and Guo–Hall thinning in bounded per-blob crops. Maximal-disc pruning, split-crossing merges and degree-two joins simplify its skeleton graph. The whole-blob width rule and ink fitting remain; 46 code-drawn inputs measure ends and junctions in [`image-trace-quality.md`](image-trace-quality.md#centre-lines). Junction degrees measure 44/45 correct in each input variant; remaining spurs are recorded. This is a measured skeleton improvement; the Image Trace score, missing per-feature selection and width controls, and estimates remain unchanged. Live Paint Gap Options (Object › Live Paint › Gap Options…, `livePaint.gapOptions`): with Gap Detection on, paint stops at the gaps that run from a loose end of a path to the nearest path, up to Small (3 pt), Medium (6 pt), Large (12 pt) or a custom size; Close Gaps With Paths adds an unpainted path across each gap; Preview applies the options live and draws the gaps in the Gap Preview Color. The options are saved, copied and undone with the group, and Merge keeps the bottom group's options when its Gap Detection is on, or else turns Gap Detection on with Small when another merged group has it on. When Gap Detection is off in every merged group, it stays off. + View › Show Live Paint Gaps (`view.livePaintGaps`) draws the gaps of the selected Live Paint groups up to their Paint Stops At size, each in its group's Gap Preview Color, until Hide Live Paint Gaps or Hide Edges hides them ([Live Paint gaps](https://web.archive.org/web/20260105111201/https://helpx.adobe.com/illustrator/desktop/paint-and-fill/learn-painting-basics/find-and-close-gaps-in-live-paint-groups.html)). Double-clicking the Live Paint Selection tool opens its Tool Options ([Live Paint Selection tool options](https://web.archive.org/web/20250904012025/https://helpx.adobe.com/illustrator/using/selecting-objects.html)): with Select Fills off, a click selects an edge instead of a face; with Select Strokes off, Alt-click selects a face instead of an edge; Color and Width set the color and width of the highlight on the face or edge under the pointer (red and 4 px by default). The options last across tool switches and are saved with the preferences (`tool.setOption {tool: "livePaintSelection"}`). Window › Blend opens the Blend panel: Make, the selected blend's Spacing and Orientation (with no blend selected, the options new blends start with), Reverse Front to Back, Reverse Spine, Replace Spine, Expand and Release, each running its Object › Blend command. Make is dimmed until two or more objects are selected. Spacing and Orientation are dimmed until a blend or two or more objects are selected, and the other buttons are dimmed until a blend is selected. Objects on Path (#2545): a click with the Objects on Path tool (in Edit Toolbar's Modify section and the Blend tool's flyout; Object › Objects on Path › Attach and the context menu with two or more objects selected choose it) attaches the selected objects to the path clicked, open or closed, in an Objects on Path group with the path at its bottom. The objects go along the path column by column from left to right, spread evenly from its first anchor (the last one sits on the end of an open path), each turned to the path at its attachment point; Pivot and Rotate set where each one attaches and how far it tilts (`object.objectsOnPath.attach`, `object.objectsOnPath.options`). Editing the path moves the objects, an object moved on its own goes back, objects moved into or out of the group in the Layers panel join or leave the path, and Object › Objects on Path › Expand leaves a plain group. The group saves and reopens live and exports as plain art. The Space, Move All, Rotate All and Select/Move widgets, Detach, the tool's options dialog and the Properties panel section are missing.