diff --git a/crates/tools/src/builder.rs b/crates/tools/src/builder.rs index 5903ac5b3..2e6164a4c 100644 --- a/crates/tools/src/builder.rs +++ b/crates/tools/src/builder.rs @@ -36,7 +36,7 @@ use cache::RegionCache; #[cfg(test)] use live_paint::LpHover; use live_paint::lp_color; -pub use live_paint::{LP_SELECT_WIDTH, LP_SELECT_WIDTH_RANGE, LivePaintBucketTool, LivePaintSelectionTool}; +pub use live_paint::{LP_BUCKET_WIDTH, LP_BUCKET_WIDTH_RANGE, LP_SELECT_WIDTH, LP_SELECT_WIDTH_RANGE, LivePaintBucketTool, LivePaintSelectionTool}; pub use lookup::{ EDGE_NAME, FACE_NAME, Face, LIVE_PAINT_NAME, LIVE_PAINT_PREFIX, SOURCES_NAME, builder_members, drawn_outlines, edge_near, edges, face_at, faces, is_live_paint, leaf_shapes, live_paint_at, live_paint_takes, node_outline, outline_snap, path_contains, shape_builder_shapes, shapes_for, diff --git a/crates/tools/src/builder/live_paint.rs b/crates/tools/src/builder/live_paint.rs index 7265122c3..4d201acc9 100644 --- a/crates/tools/src/builder/live_paint.rs +++ b/crates/tools/src/builder/live_paint.rs @@ -86,20 +86,91 @@ fn lp_overlays(h: &LpHover, cache: &RegionCache, color: [u8; 3], width: f32) -> } } -/// Live Paint Bucket: click fills the face under the cursor with the current fill; Shift-click -/// paints the nearest edge with the current stroke. Alt-click samples a fill or stroke into the -/// paint defaults; Alt+Shift-click samples only the color under the pointer. Clicking selected -/// ordinary paths without Alt makes them a Live Paint group first. +/// The Live Paint Bucket's highlight width (screen pixels) by default. +pub const LP_BUCKET_WIDTH: f64 = 4.0; + +/// The least and greatest highlight width (screen pixels) the Live Paint Bucket takes. +pub const LP_BUCKET_WIDTH_RANGE: (f64, f64) = (1.0, 20.0); + +/// What a Live Paint Bucket click paints. +#[derive(Clone, Copy, Debug, PartialEq)] +enum LpPaint { + Nothing, + /// The face under the pointer. + Fills, + /// The edge nearest the pointer. + Strokes, + /// The edge nearest the pointer when one is in reach, else the face under the pointer. + Both, +} + +/// Live Paint Bucket: by default, a click fills the face under the cursor with the current fill, +/// and Shift-click paints the nearest edge with the current stroke. Alt-click samples a fill or +/// stroke into the paint defaults; Alt+Shift-click samples only the color under the pointer. +/// Clicking selected ordinary paths without Alt makes them a Live Paint group first. Its Tool +/// Options (double-click the tool) are `paintFills` (on) and `paintStrokes` (off), `highlight` +/// (on), the highlight `color` (`#rrggbb`, red by default) and its `width` ([`LP_BUCKET_WIDTH`] +/// pixels). With Paint Strokes alone on, a click paints the edge nearest the pointer and +/// Shift-click fills the face. With both on, a click paints the edge nearest the pointer when one +/// is within 4 pixels, else it fills the face, and Shift-click fills the face. With both off, a +/// click paints nothing. With Highlight off, the face or edge under the pointer is not +/// highlighted. pub struct LivePaintBucketTool { pub(super) cache: RegionCache, pub(super) hover: LpHover, /// Where the pointer last moved, and whether Shift and Alt were held. at: Option<(Point, bool, bool)>, + fills: bool, + strokes: bool, + highlight: bool, + color: [u8; 3], + width: f64, } impl Default for LivePaintBucketTool { fn default() -> Self { - Self { cache: RegionCache::live_paint(), hover: LpHover::None, at: None } + Self { + cache: RegionCache::live_paint(), + hover: LpHover::None, + at: None, + fills: true, + strokes: false, + highlight: true, + color: HIGHLIGHT_RED, + width: LP_BUCKET_WIDTH, + } + } +} + +impl LivePaintBucketTool { + /// What a click paints, Shift held or not. Shift switches Paint Fills alone to Paint Strokes + /// alone and back, and switches both to Paint Fills alone. + fn paints(&self, shift: bool) -> LpPaint { + match (self.fills, self.strokes, shift) { + (false, false, _) => LpPaint::Nothing, + (true, true, false) => LpPaint::Both, + (true, false, false) | (_, true, true) => LpPaint::Fills, + (true, false, true) | (false, true, false) => LpPaint::Strokes, + } + } + + /// The highlight at `p` for a pointer with Shift and Alt held as given: none with Alt or with + /// Highlight off, else the face or edge a click there paints. + fn hover_at(&mut self, cx: &ToolContext, p: Point, shift: bool, alt: bool) -> LpHover { + if alt || !self.highlight { + return LpHover::None; + } + let takes = |id| lp_visible(cx, id); + match self.paints(shift) { + LpPaint::Nothing => LpHover::None, + LpPaint::Fills => lp_hover(cx, Some(&mut self.cache), p, false, &takes), + // A click paints no region of the selection, so none is highlighted. + LpPaint::Strokes => lp_hover(cx, None, p, true, &takes), + LpPaint::Both => match lp_part_at(cx.doc, p, cx.tol(4.0), true, &takes) { + Some((_, Some(e))) => e.path_data().map(|pd| LpHover::Part(pd.to_bezpath())).unwrap_or_default(), + _ => lp_hover(cx, Some(&mut self.cache), p, false, &takes), + }, + } } } @@ -113,8 +184,7 @@ impl Tool for LivePaintBucketTool { match ev.kind { PointerKind::Move => { self.at = Some((p, ev.mods.shift, ev.mods.alt)); - self.hover = - if ev.mods.alt { LpHover::None } else { lp_hover(cx, Some(&mut self.cache), p, ev.mods.shift, &|id| lp_visible(cx, id)) }; + self.hover = self.hover_at(cx, p, ev.mods.shift, ev.mods.alt); vec![] } PointerKind::Down => { @@ -142,13 +212,14 @@ impl Tool for LivePaintBucketTool { }); } let point = json!([p.x, p.y]); - if ev.mods.shift { - return match lp_part_at(cx.doc, p, cx.tol(4.0), true, &|id| lp_visible(cx, id)) { - Some((g, Some(_))) => { - vec![Action::Exec("livePaint.strokeEdge".into(), json!({ "group": g.0, "point": point, "tolerance": cx.tol(4.0) }))] - } - _ => vec![], - }; + let paint = self.paints(ev.mods.shift); + if matches!(paint, LpPaint::Strokes | LpPaint::Both) + && let Some((g, Some(_))) = lp_part_at(cx.doc, p, cx.tol(4.0), true, &|id| lp_visible(cx, id)) + { + return vec![Action::Exec("livePaint.strokeEdge".into(), json!({ "group": g.0, "point": point, "tolerance": cx.tol(4.0) }))]; + } + if !matches!(paint, LpPaint::Fills | LpPaint::Both) { + return vec![]; } if let Some(g) = live_paint_at(cx.doc, p) { return vec![Action::Exec("livePaint.fill".into(), json!({ "group": g.0, "point": point }))]; @@ -164,13 +235,39 @@ impl Tool for LivePaintBucketTool { } fn overlays(&self, _cx: &ToolContext) -> Vec { - lp_overlays(&self.hover, &self.cache, HIGHLIGHT_RED, 4.0) + lp_overlays(&self.hover, &self.cache, self.color, self.width as f32) } fn cursor(&self, _cx: &ToolContext, _p: Point, m: Mods) -> Cursor { if m.alt { Cursor::Eyedropper } else { Cursor::LivePaintBucket } } + fn options(&self) -> Value { + let [r, g, b] = self.color; + json!({ + "paintFills": self.fills, + "paintStrokes": self.strokes, + "highlight": self.highlight, + "color": format!("#{r:02x}{g:02x}{b:02x}"), + "width": self.width, + }) + } + + fn set_option(&mut self, key: &str, v: &Value) { + match key { + "paintFills" => self.fills = v.as_bool().unwrap_or(self.fills), + "paintStrokes" => self.strokes = v.as_bool().unwrap_or(self.strokes), + "highlight" => self.highlight = v.as_bool().unwrap_or(self.highlight), + "color" => self.color = lp_color(v).unwrap_or(self.color), + "width" => { + let (min, max) = LP_BUCKET_WIDTH_RANGE; + self.width = v.as_f64().filter(|w| w.is_finite()).map_or(self.width, |w| w.clamp(min, max)); + } + _ => return, + } + self.hover = LpHover::None; + } + fn waiting(&self) -> bool { self.cache.waiting() } @@ -184,7 +281,7 @@ impl Tool for LivePaintBucketTool { loop { self.cache.settle(wait); if let Some((p, shift, alt)) = self.at { - self.hover = if alt { LpHover::None } else { lp_hover(cx, Some(&mut self.cache), p, shift, &|id| lp_visible(cx, id)) }; + self.hover = self.hover_at(cx, p, shift, alt); } if !wait || !self.cache.waiting() { break; diff --git a/crates/tools/src/builder/tests.rs b/crates/tools/src/builder/tests.rs index f4a8b771f..9bfc411bf 100644 --- a/crates/tools/src/builder/tests.rs +++ b/crates/tools/src/builder/tests.rs @@ -765,6 +765,106 @@ fn bucket_click_fills_face_and_shift_click_strokes_edge() { assert_eq!(id, "livePaint.strokeEdge"); } +#[test] +fn bucket_options_choose_what_a_click_paints() { + let (d, gid, ..) = live_group_doc(); + let s = Selection::default(); + let p = paint(); + let cx = cx(&d, &s, &p); + let shift = Mods { shift: true, ..Default::default() }; + let fill = |x: f64| vec![Action::Exec("livePaint.fill".into(), json!({ "group": gid.0, "point": [x, 25.0] }))]; + let stroke = |x: f64| vec![Action::Exec("livePaint.strokeEdge".into(), json!({ "group": gid.0, "point": [x, 25.0], "tolerance": 4.0 }))]; + let click = |t: &mut LivePaintBucketTool, x: f64, m: Mods| t.pointer(&cx, &ev(PointerKind::Down, x, 25.0).with_mods(m)); + let hovered = |t: &mut LivePaintBucketTool, x: f64, m: Mods| { + t.pointer(&cx, &ev(PointerKind::Move, x, 25.0).with_mods(m)); + match t.overlays(&cx).as_slice() { + [] => None, + [Overlay::Path { path, .. }] => Some(path.bounding_box()), + ov => panic!("{ov:?}"), + } + }; + let (face, edge) = (Some(Rect::new(50.0, 0.0, 100.0, 50.0)), Some(Rect::new(50.0, 0.0, 50.0, 50.0))); + let mut t = LivePaintBucketTool::default(); + assert_eq!(t.options(), json!({"paintFills": true, "paintStrokes": false, "highlight": true, "color": "#ff2a2a", "width": 4.0})); + // Paint Fills alone: 1 pt from the edge, a click fills the face and Shift-click strokes the edge. + assert_eq!((click(&mut t, 51.0, Mods::default()), hovered(&mut t, 51.0, Mods::default())), (fill(51.0), face)); + assert_eq!((click(&mut t, 51.0, shift), hovered(&mut t, 51.0, shift)), (stroke(51.0), edge)); + // Paint Strokes alone: a click strokes the edge in reach and paints nothing farther away; + // Shift-click fills the face. + t.set_option("paintFills", &json!(false)); + t.set_option("paintStrokes", &json!(true)); + assert_eq!((click(&mut t, 51.0, Mods::default()), hovered(&mut t, 51.0, Mods::default())), (stroke(51.0), edge)); + assert_eq!((click(&mut t, 75.0, Mods::default()), hovered(&mut t, 75.0, Mods::default())), (vec![], None)); + assert_eq!((click(&mut t, 51.0, shift), hovered(&mut t, 51.0, shift)), (fill(51.0), face)); + // Both: a click strokes the edge in reach, else fills the face; Shift-click fills the face. + t.set_option("paintFills", &json!(true)); + assert_eq!((click(&mut t, 51.0, Mods::default()), hovered(&mut t, 51.0, Mods::default())), (stroke(51.0), edge)); + assert_eq!((click(&mut t, 75.0, Mods::default()), hovered(&mut t, 75.0, Mods::default())), (fill(75.0), face)); + assert_eq!((click(&mut t, 51.0, shift), hovered(&mut t, 51.0, shift)), (fill(51.0), face)); + // Both off: nothing is painted or highlighted. + t.set_option("paintFills", &json!(false)); + t.set_option("paintStrokes", &json!(false)); + for m in [Mods::default(), shift] { + assert_eq!((click(&mut t, 51.0, m), hovered(&mut t, 51.0, m)), (vec![], None)); + } + // A value that is not true or false is ignored. + t.set_option("paintFills", &json!("yes")); + assert_eq!(t.options()["paintFills"], false); +} + +#[test] +fn bucket_strokes_alone_paints_and_highlights_no_region_of_the_selection() { + let (d, a, b) = two_rects(); + let mut s = Selection::default(); + s.set([a, b]); + let p = paint(); + let cx = cx(&d, &s, &p); + let mut t = LivePaintBucketTool::default(); + t.pointer(&cx, &ev(PointerKind::Move, 75.0, 50.0)); + t.poll(&cx, true); + assert!(!t.overlays(&cx).is_empty(), "Paint Fills highlights the region"); + t.set_option("paintFills", &json!(false)); + t.set_option("paintStrokes", &json!(true)); + t.pointer(&cx, &ev(PointerKind::Move, 75.0, 50.0)); + t.poll(&cx, true); + assert!(t.overlays(&cx).is_empty()); + assert!(t.pointer(&cx, &ev(PointerKind::Down, 75.0, 50.0)).is_empty()); +} + +#[test] +fn bucket_options_set_the_highlight() { + let (d, ..) = live_group_doc(); + let s = Selection::default(); + let p = paint(); + let cx = cx(&d, &s, &p); + let mut t = LivePaintBucketTool::default(); + let highlight = |t: &mut LivePaintBucketTool| { + t.pointer(&cx, &ev(PointerKind::Move, 75.0, 25.0)); + match t.overlays(&cx).as_slice() { + [] => None, + [Overlay::Path { color, width, .. }] => Some((*color, *width)), + ov => panic!("{ov:?}"), + } + }; + assert_eq!(highlight(&mut t), Some((HIGHLIGHT_RED, 4.0))); + t.set_option("color", &json!("#00FF00")); + t.set_option("width", &json!(9)); + assert_eq!(highlight(&mut t), Some(([0, 0xff, 0], 9.0))); + t.set_option("color", &json!("light blue")); + t.set_option("width", &json!("wide")); + assert_eq!(highlight(&mut t), Some(([0x4f, 0x80, 0xff], 9.0))); + t.set_option("width", &json!(100)); + assert_eq!(t.options()["width"], json!(LP_BUCKET_WIDTH_RANGE.1)); + t.set_option("width", &json!(0)); + assert_eq!(t.options()["width"], json!(LP_BUCKET_WIDTH_RANGE.0)); + // Highlight off: nothing is highlighted, and a click still paints. + t.set_option("highlight", &json!(false)); + assert_eq!(highlight(&mut t), None); + assert_eq!(t.pointer(&cx, &ev(PointerKind::Down, 75.0, 25.0)).len(), 1); + t.set_option("highlight", &json!(true)); + assert!(highlight(&mut t).is_some()); +} + #[test] fn bucket_alt_samples_face_edge_and_selected_region_without_painting() { let (mut d, _gid, face, edge) = live_group_doc(); diff --git a/crates/tools/src/settings.rs b/crates/tools/src/settings.rs index 1e94c6d2c..e7b9f9a5c 100644 --- a/crates/tools/src/settings.rs +++ b/crates/tools/src/settings.rs @@ -45,6 +45,8 @@ const ROWS: &[Row] = &[ (&["blobBrush", "eraser"], None, &["angle", "roundness"]), // Live Paint Selection Tool Options (`builder::LivePaintSelectionTool`). (&["livePaintSelection"], None, &["selectFills", "selectStrokes", "color", "width"]), + // Live Paint Bucket Options (`builder::LivePaintBucketTool`). + (&["livePaintBucket"], None, &["paintFills", "paintStrokes", "highlight", "color", "width"]), (&["polygon"], None, &["sides"]), (&["star"], None, &["points"]), (&["freeTransform"], None, &["constrain"]), diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index b0210026a..0e9f7b75f 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -56,6 +56,7 @@ pub mod justification; pub mod layer_options; pub mod layers_panel_options; pub mod liquify; +pub mod live_paint_bucket; pub mod live_paint_gaps; pub mod live_paint_selection; pub mod missing_fonts; @@ -389,6 +390,7 @@ registry! { BlendOptions: [blend_options::KIND] => blend_options::SPEC, LivePaintGaps: [live_paint_gaps::KIND] => live_paint_gaps::SPEC, LivePaintSelectionOptions: [live_paint_selection::KIND] => live_paint_selection::SPEC, + LivePaintBucketOptions: [live_paint_bucket::KIND] => live_paint_bucket::SPEC, BrushOptions: [brush_options::KIND] => brush_options::SPEC, NewBrush: [new_brush::KIND] => new_brush::SPEC, EditSelection: [edit_selection::KIND] => edit_selection::SPEC, diff --git a/crates/ui-egui/src/dialogs/live_paint_bucket.rs b/crates/ui-egui/src/dialogs/live_paint_bucket.rs new file mode 100644 index 000000000..550b23534 --- /dev/null +++ b/crates/ui-egui/src/dialogs/live_paint_bucket.rs @@ -0,0 +1,151 @@ +//! Live Paint Bucket Options (double-click the Live Paint Bucket tool): what a click with the tool +//! paints and how the face or edge under the pointer highlights. The options last across tool +//! switches and are saved with the preferences. OK sets them with +//! `tool.setOption {tool: "livePaintBucket", values}`. +//! +//! Fields: `paintFills` and `paintStrokes` (the faces and the edges of Live Paint groups), +//! `highlight` (whether the face or edge under the pointer is outlined), `color` (the highlight +//! color: `#rrggbb`, or a layer color's name such as "Red") and `width` (the highlight's width in +//! screen pixels). + +use serde_json::{Map, Value, json}; +use vectorcraft_color::Color; +use vectorcraft_doc::LAYER_COLORS; +use vectorcraft_tools::builder::LP_BUCKET_WIDTH_RANGE; + +use super::tile_edge_color::resolve; +use super::{DialogSpec, form, run_and_close}; +use crate::state::Dialog; +use crate::{VectorcraftApp, widgets}; + +/// The dialog kind of the Live Paint Bucket Options. +pub const KIND: &str = "livePaintBucketOptions"; + +/// The tool the dialog serves. +const TOOL: &str = "livePaintBucket"; + +/// The options the dialog edits. +const KEYS: [&str; 5] = ["paintFills", "paintStrokes", "highlight", "color", "width"]; + +/// Width of the label column. +const LABEL_W: f32 = 60.0; + +pub(super) const SPEC: DialogSpec = DialogSpec { heading, body, confirm, min_width: 300.0, ..DialogSpec::FORM }; + +fn heading(_: &Dialog) -> String { + let label = vectorcraft_tools::tool_info(TOOL).map_or("", |t| t.label); + crate::i18n::fmt(tl!("{tool} Options"), &[("tool", tl!(label))]) +} + +/// Open the Live Paint Bucket Options with the tool's current values. +pub fn open(app: &mut VectorcraftApp) -> Result { + let o = app.session.tool_options_of(TOOL); + let fields: Map = KEYS.iter().filter_map(|key| Some((key.to_string(), o.get(*key)?.clone()))).collect(); + app.ui.dialog = Some(Dialog { kind: KIND.into(), fields }); + Ok(json!({ "dialog": KIND })) +} + +fn body(_app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { + form::check(ui, d, "paintFills", tl!("Paint Fills")); + form::check(ui, d, "paintStrokes", tl!("Paint Strokes")); + ui.add_space(6.0); + form::check(ui, d, "highlight", tl!("Highlight")); + let color = resolve(&d.str("color")).unwrap_or(Color::BLACK); + let [r, g, b, _] = color.to_rgba8(1.0); + let preset = LAYER_COLORS.iter().position(|(_, c)| *c == [r, g, b]); + let names: Vec<&str> = LAYER_COLORS.iter().map(|(n, _)| *n).chain(["Custom"]).collect(); + ui.add_enabled_ui(d.bool("highlight"), |ui| { + widgets::label_row(ui, tl!("Color:"), LABEL_W, |ui| { + if let Some((_, [r, g, b])) = + widgets::dropdown(ui, "lp-bucket-color", names[preset.unwrap_or(LAYER_COLORS.len())], &names, 130.0).and_then(|i| LAYER_COLORS.get(i)) + { + d.fields.insert("color".into(), json!(Color::rgb8(*r, *g, *b).to_hex())); + } + // The color button sets any other color; the dropdown then shows Custom. + let mut rgb = [r, g, b]; + if ui.color_edit_button_srgb(&mut rgb).changed() { + d.fields.insert("color".into(), json!(Color::rgb8(rgb[0], rgb[1], rgb[2]).to_hex())); + } + }); + let (min, max) = LP_BUCKET_WIDTH_RANGE; + let v = d.f64("width", min); + widgets::label_row(ui, tl!("Width:"), LABEL_W, |ui| { + if let Some(x) = widgets::range_field(ui, "lp-bucket-width", v, min..=max, "px", 0, 72.0) { + d.fields.insert("width".into(), json!(x)); + } + }); + }); + false +} + +/// OK: set the tool's options. +fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { + let values: Map = KEYS.iter().filter_map(|k| Some((k.to_string(), d.fields.get(*k)?.clone()))).collect(); + run_and_close(app, "tool.setOption", json!({ "tool": TOOL, "values": values })) +} + +#[cfg(test)] +mod tests { + use super::*; + use vectorcraft_engine::Session; + use vectorcraft_tools::{Overlay, PointerEvent, PointerKind}; + + /// The labels the open dialog paints. + fn painted(app: &mut VectorcraftApp) -> String { + crate::tests_labels::painted_text(app, |app, ui| super::super::show(app, ui.ctx())) + } + + #[test] + fn double_clicking_the_tool_opens_its_options_and_ok_sets_them() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 400, "height": 300})).unwrap(); + // Two squares side by side, made a Live Paint group: two faces and the edge between them. + for x in [0, 100] { + app.run("shape.rectangle", json!({"x": 50 + x, "y": 50, "width": 100, "height": 100})).unwrap(); + } + app.run("select.all", json!({})).unwrap(); + app.run("livePaint.make", json!({})).unwrap(); + app.run("select.set", json!({"ids": []})).unwrap(); + app.select_tool(TOOL); + let hover = |app: &mut VectorcraftApp, x: f64| { + let view = app.view_info(); + crate::canvas::dispatch(app, &PointerEvent::new(PointerKind::Move, x, 100.0), view); + app.session.overlays(view) + }; + let ov = hover(&mut app, 151.0); + assert!(ov.iter().any(|o| matches!(o, Overlay::Path { color: [0xff, 0x2a, 0x2a], width: 4.0, .. })), "{ov:?}"); + + crate::toolbar::open_options(&mut app, TOOL).unwrap(); + let text = painted(&mut app); + for label in ["Live Paint Bucket Options", "Paint Fills", "Paint Strokes", "Highlight", "Color:", "Width:"] { + assert!(text.contains(label), "{label} in {text}"); + } + let d = app.ui.dialog.as_mut().unwrap(); + assert_eq!(d.kind, KIND); + assert_eq!( + (d.bool("paintFills"), d.bool("paintStrokes"), d.bool("highlight"), d.str("color"), d.f64("width", 0.0)), + (true, false, true, "#ff2a2a".to_string(), 4.0) + ); + d.fields.insert("paintFills".into(), json!(false)); + d.fields.insert("paintStrokes".into(), json!(true)); + d.fields.insert("color".into(), json!("Green")); + d.fields.insert("width".into(), json!(8)); + super::super::confirm(&mut app).unwrap(); + assert!(app.ui.dialog.is_none()); + // The tool takes them: by the edge, the edge highlights in the new color and width. + let ov = hover(&mut app, 151.0); + assert!(ov.iter().any(|o| matches!(o, Overlay::Path { color: [0x4f, 0xff, 0x4f], width: 8.0, .. })), "{ov:?}"); + // They last while another tool is chosen, and the dialog opens on them again. + app.select_tool("selection"); + let o = app.session.tool_options_of(TOOL); + assert_eq!((o["paintFills"].as_bool(), o["paintStrokes"].as_bool(), o["color"].as_str()), (Some(false), Some(true), Some("#4fff4f"))); + app.run("tool.options", json!({"tool": TOOL})).unwrap(); + let d = app.ui.dialog.as_mut().unwrap(); + assert_eq!((d.bool("paintStrokes"), d.str("color")), (true, "#4fff4f".to_string())); + // Highlight off: the tool outlines nothing under the pointer. + d.fields.insert("highlight".into(), json!(false)); + super::super::confirm(&mut app).unwrap(); + app.select_tool(TOOL); + assert!(!hover(&mut app, 151.0).iter().any(|o| matches!(o, Overlay::Path { .. }))); + } +} diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index f2389f4b8..b3bc7e860 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -1230,6 +1230,9 @@ font-technology Variable Променливи Hide Live Paint Gaps Скриване на пролуките на динамичното оцветяване Select Fills Избиране на запълвания Select Strokes Избиране на щрихове + Paint Fills Боядисване на запълвания + Paint Strokes Боядисване на щрихове + Highlight Маркиране Gaussian Blur… Гаусово размазване… General Общи General › Placement › X Общи › Разположение › X diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 2e9b5fd55..aaa5ec00f 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -267,6 +267,9 @@ Hide Live Paint Gaps Sakrij praznine živog bojenja Select Fills Odaberi ispune Select Strokes Odaberi poteze + Paint Fills Boji ispune + Paint Strokes Boji poteze + Highlight Isticanje Image Trace Praćenje slike Image Trace (Make and Expand) Praćenje slike (napravi i proširi) Release Image Trace Oslobodi praćenje slike diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 0c2238de1..5e309464e 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -1859,6 +1859,7 @@ digits Malayalam Malaiàlam High Quality Print Impressió d’alta qualitat High Resolution Alta resolució High resolution Alta resolució + Highlight Ressaltat Highlight Alternate Glyphs Ressalta els glifs alternatius Highlight anchors on mouse over Ressalta els punts d’ancoratge en passar el ratolí Highlight Area Ressalta l’àrea @@ -2844,8 +2845,10 @@ digits Malayalam Malaiàlam Pages Pàgines Paint Daubs Taques de pintura Paint Daubs… Taques de pintura… + Paint Fills Pinta els farciments Paint Opacity: Opacitat de pintura: Paint Stops At: La pintura s’atura a: + Paint Strokes Pinta els traços Paintbrush Pinzell Paintbrush Tool Eina de pinzell Palette Knife Espàtula diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index d8d77feef..700dcf26d 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -277,6 +277,9 @@ Hide Live Paint Gaps Skrýt mezery živé malby Select Fills Vybírat výplně Select Strokes Vybírat tahy + Paint Fills Malovat výplně + Paint Strokes Malovat tahy + Highlight Zvýraznění Image Trace Vektorizace obrazu Image Trace (Make and Expand) Vektorizace obrazu (vytvořit a rozložit) Release Image Trace Uvolnit vektorizaci obrazu diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 7a1740223..b4cb18ce0 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -5312,6 +5312,9 @@ swatch Bright Violet Kraftig violet Close Gaps With Paths Luk huller med stier Select Fills Markér udfyldninger Select Strokes Markér konturer + Paint Fills Mal udfyldninger + Paint Strokes Mal konturer + Highlight Fremhævning Column Type: Søjletype: Each Design Represents: Hvert design repræsenterer: Event: Hændelse: diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 48e587d88..0f9001fa4 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1254,6 +1254,9 @@ file-filter Templates Vorlagen Hide Live Paint Gaps Lücken beim interaktiven Malen ausblenden Select Fills Flächen auswählen Select Strokes Konturen auswählen + Paint Fills Flächen malen + Paint Strokes Konturen malen + Highlight Hervorheben Gaussian Blur… Gaußscher Weichzeichner… General Allgemein General › Placement › X Allgemein › Platzierung › X diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index bec866697..931624d4d 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1196,6 +1196,9 @@ font-technology Variable Μεταβλητές Hide Live Paint Gaps Απόκρυψη διακένων δυναμικής βαφής Select Fills Επιλογή γεμισμάτων Select Strokes Επιλογή περιγραμμάτων + Paint Fills Βαφή γεμισμάτων + Paint Strokes Βαφή περιγραμμάτων + Highlight Επισήμανση Gaussian Blur… Θόλωση Γκάους… General Γενικά General › Placement › X Γενικά › Τοποθέτηση › X diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index d5a664029..7bb969e1b 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1267,6 +1267,9 @@ font-technology Variable Variables Hide Live Paint Gaps Ocultar huecos de pintura interactiva Select Fills Seleccionar rellenos Select Strokes Seleccionar trazos + Paint Fills Pintar rellenos + Paint Strokes Pintar trazos + Highlight Resaltado Gaussian Blur… Desenfoque gaussiano… General General General › Placement › X General › Colocación › X diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index f1badd485..a0017ed4d 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -284,6 +284,9 @@ Hide Live Paint Gaps Piilota elävän maalauksen aukot Select Fills Valitse täytöt Select Strokes Valitse viivat + Paint Fills Maalaa täytöt + Paint Strokes Maalaa viivat + Highlight Korostus Image Trace Kuvan jäljitys Image Trace (Make and Expand) Kuvan jäljitys (luo ja laajenna) Release Image Trace Vapauta kuvan jäljitys diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index be6e4d1ae..a4c3483b7 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1267,6 +1267,9 @@ font-technology Variable Variables Hide Live Paint Gaps Masquer les espaces de peinture dynamique Select Fills Sélectionner les fonds Select Strokes Sélectionner les contours + Paint Fills Peindre les fonds + Paint Strokes Peindre les contours + Highlight Mise en évidence Gaussian Blur… Flou gaussien… General Général General › Placement › X Général › Placement › X diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 7243f3dee..c44637ba9 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1584,6 +1584,9 @@ file-filter Templates Sablonok 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 + Paint Fills Kitöltések festése + Paint Strokes Körvonalak festése + Highlight Kiemelés Gaussian Blur Gauss-elmosás Gaussian Blur… Gauss-elmosás… General Általános diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 84e58c69b..e1b620277 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -5312,6 +5312,9 @@ swatch Bright Violet Violet cerah Close Gaps With Paths Tutup celah dengan jalur Select Fills Pilih isian Select Strokes Pilih stroke + Paint Fills Cat isian + Paint Strokes Cat stroke + Highlight Sorotan Column Type: Jenis kolom: Each Design Represents: Setiap desain mewakili: Event: Peristiwa: diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 6845ed74e..77d8c0a20 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1277,6 +1277,9 @@ file-filter Templates Modelli Hide Live Paint Gaps Nascondi spazi vuoti pittura dinamica Select Fills Seleziona riempimenti Select Strokes Seleziona tracce + Paint Fills Colora riempimenti + Paint Strokes Colora tracce + Highlight Evidenziazione Gaussian Blur… Sfocatura gaussiana… General Generale General › Placement › X Generale › Posizionamento › X diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index cfe312e9e..fea333848 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -292,6 +292,9 @@ digits Malayalam マラヤーラム数字 Hide Live Paint Gaps ライブペイントの間隔を隠す Select Fills 塗りを選択 Select Strokes 線を選択 + Paint Fills 塗りをペイント + Paint Strokes 線をペイント + Highlight ハイライト Image Trace 画像トレース Image Tracing 画像トレースオブジェクト Tracing Presets トレースプリセット diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index d6f951f6e..586b59cc5 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -5339,6 +5339,9 @@ imageTrace Outline კონტური Scale in Perspective პერსპექტივაში მასშტაბირება Select Fills შევსებების მონიშვნა Select Strokes კონტურების მონიშვნა + Paint Fills შევსებების შეღებვა + Paint Strokes კონტურების შეღებვა + Highlight გამოკვეთა Set History Limit… ისტორიის ლიმიტის დაყენება… Shape Builder Delete ფიგურების კონსტრუქტორით წაშლა Sliding სრიალა diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 2df53a098..05d5ed4e6 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -285,6 +285,9 @@ digits Malayalam 말라얄람어 Hide Live Paint Gaps 라이브 페인트 간격 숨기기 Select Fills 칠 선택 Select Strokes 선 선택 + Paint Fills 칠 페인트 + Paint Strokes 선 페인트 + Highlight 강조 표시 Image Trace 이미지 추적 Image Tracing 이미지 추적 Tracing Presets 추적 프리셋 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 0591d4042..98a89cc6b 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -5323,6 +5323,9 @@ swatch Bright Violet Felviolet Close Gaps With Paths Openingen sluiten met paden Select Fills Vullingen selecteren Select Strokes Lijnen selecteren + Paint Fills Vullingen verven + Paint Strokes Lijnen verven + Highlight Markering Column Type: Kolomtype: Each Design Represents: Elk ontwerp staat voor: Event: Gebeurtenis: diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 6acc18ccf..08ab910c4 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -242,6 +242,9 @@ Hide Live Paint Gaps Ukryj przerwy aktywnego malowania Select Fills Zaznaczaj wypełnienia Select Strokes Zaznaczaj obrysy + Paint Fills Maluj wypełnienia + Paint Strokes Maluj obrysy + Highlight Podświetlenie Image Trace Obrys obrazu Text Wrap Oblewanie tekstem Text Wrap Options… Opcje oblewania tekstem… diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index f55b23cf2..4c8c88572 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1116,6 +1116,9 @@ Hide Live Paint Gaps Ocultar Lacunas da Pintura ao Vivo Select Fills Selecionar Preenchimentos Select Strokes Selecionar Traçados + Paint Fills Pintar Preenchimentos + Paint Strokes Pintar Traçados + Highlight Destaque Gaussian Blur… Desfoque Gaussiano… General Geral Generate CSS Gerar CSS diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 038c380a3..d330e4ccc 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -1275,6 +1275,9 @@ file-filter Templates Шаблоны Hide Live Paint Gaps Скрыть зазоры быстрой заливки Select Fills Выделять заливки Select Strokes Выделять обводки + Paint Fills Рисовать заливки + Paint Strokes Рисовать обводки + Highlight Подсветка Gaussian Blur… Размытие по Гауссу… General Общие General › Placement › X Общие › Размещение › X diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 91c94e58e..d18203ddf 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -1232,6 +1232,9 @@ font-technology Variable Variabla Hide Live Paint Gaps Dölj mellanrum i direktmålning Select Fills Markera fyllningar Select Strokes Markera konturer + Paint Fills Måla fyllningar + Paint Strokes Måla konturer + Highlight Framhävning Gaussian Blur… Gaussisk oskärpa… General Allmänt General › Placement › X Allmänt › Placering › X diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 43682acca..7bf6aa584 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -5364,6 +5364,9 @@ theme Appearance Görünüm Overlap Örtüşme Select Fills Dolguları Seç Select Strokes Konturları Seç + Paint Fills Dolguları Boya + Paint Strokes Konturları Boya + Highlight Vurgulama Snap to Unit Birim Değerine Yasla The plug-in writes the whole document. Eklenti tüm belgeyi yazar. Attach İliştir diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index d6610c36b..f4ed70ce2 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -1271,6 +1271,9 @@ file-filter Templates Шаблони Hide Live Paint Gaps Приховати проміжки швидкої заливки Select Fills Виділяти заливки Select Strokes Виділяти обведення + Paint Fills Фарбувати заливки + Paint Strokes Фарбувати обведення + Highlight Підсвічування Gaussian Blur… Розмиття за Гауссом… General Загальні General › Placement › X Загальні › Розміщення › X diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 5315376b9..82e7600d2 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1486,6 +1486,9 @@ file-filter Templates Mẫu 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 + Paint Fills Tô vùng tô + Paint Strokes Tô nét + Highlight Làm nổi bật Gaussian Blur Mờ Gaussian Gaussian Blur… Làm mờ Gaussian… General Chung diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 156b25084..9cba58ad3 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1215,6 +1215,9 @@ file-filter Templates 模板 Hide Live Paint Gaps 隐藏实时上色间隙 Select Fills 选择填色 Select Strokes 选择描边 + Paint Fills 为填色上色 + Paint Strokes 为描边上色 + Highlight 突出显示 Gaussian Blur… 高斯模糊… General 常规 General › Placement › X 一般 › 位置 › X diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 33406bd06..ec99ae2e2 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1241,6 +1241,9 @@ file-filter Templates 範本 Hide Live Paint Gaps 隱藏即時上色間隙 Select Fills 選取填色 Select Strokes 選取筆畫 + Paint Fills 為填色上色 + Paint Strokes 為筆畫上色 + Highlight 強調顯示 Gaussian Blur… 高斯模糊… General 一般 General › Placement › X 一般 › 位置 › X diff --git a/crates/ui-egui/src/menus/ui_commands.rs b/crates/ui-egui/src/menus/ui_commands.rs index 28642f015..11f342420 100644 --- a/crates/ui-egui/src/menus/ui_commands.rs +++ b/crates/ui-egui/src/menus/ui_commands.rs @@ -691,7 +691,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "tool.options", "Tool Options…", "", - "{tool: id} what double-clicking a tool button opens: hand → fits the artboard in the window (view.fitArtboard), zoom → 100% (view.actualSize), rotate|scale|reflect|shear → that Object › Transform dialog (dialog `rotate`, `scale`, `reflect` or `shear`; error `nothing selected` without a selection), selection|directSelection|groupSelection → the Move dialog (dialog `move`; the same error), gradient|magicWand → the Gradient or Magic Wand panel (window.panel), artboard → Artboard Options of the active artboard (dialog `artboardOptions`, fields index, name, x, y, width, height, showCenterMark, showCrossHairs; OK runs artboard.setProps, and with other artboards selected with the Artboard tool a new width or height goes to each of them too, in its `artboards` list, each resized about the reference point the form used for that size, or about its top-left corner when the size was set in the fields directly), flare → Flare Tool Options (dialog `flareOptions`, fields diameter and pathLength (pt), opacity, brightness, growth, fuzziness, longest, rayFuzziness, largest (%), rays, rings, direction (°), raysOn, ringsOn; OK runs tool.setOption {tool: \"flare\", values}: the next flare dragged out uses them, and a Flare click's dialog, which also has x, y and preview (on initially), previews one there and accepts it as one Undo step; Cancel rolls back the preview), lineSegment|arc|rectangularGrid|polarGrid → that tool's Tool Options, its click dialog without x and y (dialog kind = the tool id, `__toolOptions: true`; lineSegment: length (pt), angle (°), fillLine; arc: lengthX, lengthY (pt), reference (0–8), arcType: open|closed, baseAlong: x|y, slope (−100…100), fillArc; rectangularGrid: width, height (pt, the Default Size), reference (0–8), rows, columns, rowSkew, columnSkew (%), frame, fill; polarGrid: the same with concentric, radial, concentricSkew, radialSkew and compound for rows, columns, the skews and frame; OK runs tool.setOption {tool, values} and draws nothing: the next drag uses them (Fill Line, the arc's type, base, slope and fill, the grids' dividers, skews and switches) and a click's dialog opens at them), columnGraph|stackedColumnGraph|barGraph|stackedBarGraph|lineGraph|areaGraph|scatterGraph|pieGraph|radarGraph → Graph Type for the selected graph (dialog `command` running graph.setType; an error without one), typeOnPath|verticalTypeOnPath → Type on a Path Options for the selected type on a path (dialog `command` running type.pathOptions; error `nothing selected` without a selection, and the command's error when no type on a path is selected),symbolSprayer|symbolShifter|symbolScruncher|symbolSizer|symbolSpinner|symbolStainer|symbolScreener|symbolStyler → Symbolism Tools Options (dialog `symbolismOptions`, fields tool, diameter (pt), intensity and density (1–10), shared by the eight tools; OK runs tool.setOption {tool, values}), blend → Blend Options, perspectiveGrid → Perspective Grid Options, eyedropper → Eyedropper Options (dialog `eyedropperOptions`, fields sampleSize, pickUp, apply; OK runs eyedropper.setOptions), printTiling → resets the print tiling (print.tiling.set {reset: true}), warp|twirl|pucker|bloat|scallop|crystallize|wrinkle → that tool's Tool Options (dialog `liquifyOptions`, fields tool, width, height, angle, intensity %, usePressure, detail, simplify, simplifyOn, rate, complexity, horizontal %, vertical %, affectAnchors, affectIn, affectOut, showBrush; OK runs tool.setOption {tool, values}), pencil|paintbrush|smooth|blobBrush|eraser → that tool's Tool Options (dialog `freehandOptions`, fields tool and the options the tool keeps: fidelity (pt), fill, keepSelected (on for pencil and paintbrush, off for blobBrush), mergeOnlySelection (off), closeWithin and editWithin (px, 0 is off), livePreview (pencil, on: off draws a thin line while the stroke is drawn, and the path's fill and stroke on release), size (pt), angle (°, −180–180; 0) and roundness (%, 1–100; 100) of the eraser's tip; OK runs tool.setOption {tool, values}), livePaintSelection → Live Paint Selection Tool Options (dialog `livePaintSelectionOptions`, fields selectFills and selectStrokes (both on), color (the highlight color, #rrggbb or a layer color's name; #ff2a2a), width (the highlight's width, 1–20 px; 4); OK runs tool.setOption {tool: \"livePaintSelection\", values}), shapeBuilder → Shape Builder Tool Options (dialog `shapeBuilderOptions`, fields gapDetection (off), gapLength (pt, 0–72; 3), openFilledAsClosed (on), clickStrokeSplitsPath (off), pickColorFrom: artwork|swatches (artwork), cursorSwatchPreview (off), selection: freeform|straightLine (freeform), highlightFill (on), highlightStroke (on), highlightColor (#rrggbb or a layer color's name; #ff4f4f); OK runs tool.setOption {tool: \"shapeBuilder\", values}), objectsOnPath → Objects on Path Tool Options (dialog `objectsOnPathOptions`, fields pivot (0–8, row by row from the top left; 4, the center) and rotate (°, −360–360; 0); Reset puts 4 and 0 back; Save runs tool.setOption {tool: \"objectsOnPath\", values}: the objects attached next use them, groups attached before keep theirs)", + "{tool: id} what double-clicking a tool button opens: hand → fits the artboard in the window (view.fitArtboard), zoom → 100% (view.actualSize), rotate|scale|reflect|shear → that Object › Transform dialog (dialog `rotate`, `scale`, `reflect` or `shear`; error `nothing selected` without a selection), selection|directSelection|groupSelection → the Move dialog (dialog `move`; the same error), gradient|magicWand → the Gradient or Magic Wand panel (window.panel), artboard → Artboard Options of the active artboard (dialog `artboardOptions`, fields index, name, x, y, width, height, showCenterMark, showCrossHairs; OK runs artboard.setProps, and with other artboards selected with the Artboard tool a new width or height goes to each of them too, in its `artboards` list, each resized about the reference point the form used for that size, or about its top-left corner when the size was set in the fields directly), flare → Flare Tool Options (dialog `flareOptions`, fields diameter and pathLength (pt), opacity, brightness, growth, fuzziness, longest, rayFuzziness, largest (%), rays, rings, direction (°), raysOn, ringsOn; OK runs tool.setOption {tool: \"flare\", values}: the next flare dragged out uses them, and a Flare click's dialog, which also has x, y and preview (on initially), previews one there and accepts it as one Undo step; Cancel rolls back the preview), lineSegment|arc|rectangularGrid|polarGrid → that tool's Tool Options, its click dialog without x and y (dialog kind = the tool id, `__toolOptions: true`; lineSegment: length (pt), angle (°), fillLine; arc: lengthX, lengthY (pt), reference (0–8), arcType: open|closed, baseAlong: x|y, slope (−100…100), fillArc; rectangularGrid: width, height (pt, the Default Size), reference (0–8), rows, columns, rowSkew, columnSkew (%), frame, fill; polarGrid: the same with concentric, radial, concentricSkew, radialSkew and compound for rows, columns, the skews and frame; OK runs tool.setOption {tool, values} and draws nothing: the next drag uses them (Fill Line, the arc's type, base, slope and fill, the grids' dividers, skews and switches) and a click's dialog opens at them), columnGraph|stackedColumnGraph|barGraph|stackedBarGraph|lineGraph|areaGraph|scatterGraph|pieGraph|radarGraph → Graph Type for the selected graph (dialog `command` running graph.setType; an error without one), typeOnPath|verticalTypeOnPath → Type on a Path Options for the selected type on a path (dialog `command` running type.pathOptions; error `nothing selected` without a selection, and the command's error when no type on a path is selected),symbolSprayer|symbolShifter|symbolScruncher|symbolSizer|symbolSpinner|symbolStainer|symbolScreener|symbolStyler → Symbolism Tools Options (dialog `symbolismOptions`, fields tool, diameter (pt), intensity and density (1–10), shared by the eight tools; OK runs tool.setOption {tool, values}), blend → Blend Options, perspectiveGrid → Perspective Grid Options, eyedropper → Eyedropper Options (dialog `eyedropperOptions`, fields sampleSize, pickUp, apply; OK runs eyedropper.setOptions), printTiling → resets the print tiling (print.tiling.set {reset: true}), warp|twirl|pucker|bloat|scallop|crystallize|wrinkle → that tool's Tool Options (dialog `liquifyOptions`, fields tool, width, height, angle, intensity %, usePressure, detail, simplify, simplifyOn, rate, complexity, horizontal %, vertical %, affectAnchors, affectIn, affectOut, showBrush; OK runs tool.setOption {tool, values}), pencil|paintbrush|smooth|blobBrush|eraser → that tool's Tool Options (dialog `freehandOptions`, fields tool and the options the tool keeps: fidelity (pt), fill, keepSelected (on for pencil and paintbrush, off for blobBrush), mergeOnlySelection (off), closeWithin and editWithin (px, 0 is off), livePreview (pencil, on: off draws a thin line while the stroke is drawn, and the path's fill and stroke on release), size (pt), angle (°, −180–180; 0) and roundness (%, 1–100; 100) of the eraser's tip; OK runs tool.setOption {tool, values}), livePaintSelection → Live Paint Selection Tool Options (dialog `livePaintSelectionOptions`, fields selectFills and selectStrokes (both on), color (the highlight color, #rrggbb or a layer color's name; #ff2a2a), width (the highlight's width, 1–20 px; 4); OK runs tool.setOption {tool: \"livePaintSelection\", values}), livePaintBucket → Live Paint Bucket Options (dialog `livePaintBucketOptions`, fields paintFills (on) and paintStrokes (off), highlight (on), color (the highlight color, #rrggbb or a layer color's name; #ff2a2a), width (the highlight's width, 1–20 px; 4); OK runs tool.setOption {tool: \"livePaintBucket\", values}), shapeBuilder → Shape Builder Tool Options (dialog `shapeBuilderOptions`, fields gapDetection (off), gapLength (pt, 0–72; 3), openFilledAsClosed (on), clickStrokeSplitsPath (off), pickColorFrom: artwork|swatches (artwork), cursorSwatchPreview (off), selection: freeform|straightLine (freeform), highlightFill (on), highlightStroke (on), highlightColor (#rrggbb or a layer color's name; #ff4f4f); OK runs tool.setOption {tool: \"shapeBuilder\", values}), objectsOnPath → Objects on Path Tool Options (dialog `objectsOnPathOptions`, fields pivot (0–8, row by row from the top left; 4, the center) and rotate (°, −360–360; 0); Reset puts 4 and 0 back; Save runs tool.setOption {tool: \"objectsOnPath\", values}: the objects attached next use them, groups attached before keep theirs)", ), ( "ui.colorGuideOptions", diff --git a/crates/ui-egui/src/toolbar.rs b/crates/ui-egui/src/toolbar.rs index a3bcaa83f..5ab3e389e 100644 --- a/crates/ui-egui/src/toolbar.rs +++ b/crates/ui-egui/src/toolbar.rs @@ -377,6 +377,8 @@ pub fn open_options(app: &mut VectorcraftApp, tool: &str) -> Result crate::dialogs::freehand::open(app, tool), // The Live Paint Selection tool: its Tool Options (what a click selects, the highlight). "livePaintSelection" => crate::dialogs::live_paint_selection::open(app), + // The Live Paint Bucket: its options (what a click paints, the highlight). + "livePaintBucket" => crate::dialogs::live_paint_bucket::open(app), // The Symbolism tools: the brush they share. _ if vectorcraft_tools::settings::SYMBOLISM.contains(&tool) => Ok(crate::dialogs::symbolism_options::open(app, tool)), // The graph tools: Graph Type for the selected graph. diff --git a/docs/mcp.md b/docs/mcp.md index 799c2427c..7acb9e5f4 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1701,6 +1701,15 @@ already walks Image Trace. Short agent entry points: release, and Escape between the press and the release removes the marquee so that the release runs nothing. The tool does not highlight or select the faces and edges of a Live Paint group that is locked, on a locked layer or on a locked artboard, or in isolation mode outside the isolated art. + The Live Paint Bucket's options (`tool.setOption {tool: "livePaintBucket", values}`; double-clicking the tool opens + them, dialog `livePaintBucketOptions`) are `paintFills` (on), `paintStrokes` (off), `highlight` (on), `color` (the + highlight color, `#rrggbb` or a layer color's name; `#ff2a2a`) and `width` (the highlight's width, 1 to 20 screen + pixels; 4). With `paintFills` alone on, a click runs `livePaint.fill` and Shift-click runs `livePaint.strokeEdge`. + With `paintStrokes` alone on, a click runs `livePaint.strokeEdge` on the edge within 4 screen pixels of the pointer, + or nothing when none is in reach, and Shift-click runs `livePaint.fill`. With both on, a click runs + `livePaint.strokeEdge` when an edge is within 4 screen pixels of the pointer and `livePaint.fill` otherwise, and + Shift-click runs `livePaint.fill`. With both off, a click runs nothing. With `highlight` off, the face or edge under + the pointer is not outlined. `livePaint.merge` keeps the bottom Live Paint group's id, name, lock, visibility, transparency, appearance, opacity mask and graphic style link. With a Live Paint group among the objects, `livePaint.make` adds the others to it as `livePaint.merge` does, and so does `livePaint.fill {ids}` when `point` is outside that group's faces. diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 8bf7e7c5c..377dd2d87 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -928,6 +928,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - 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. Object › Live Paint › Merge adds the other selected paths and Live Paint groups to the bottom selected Live Paint group ([Live Paint groups](https://web.archive.org/web/20241230205225/https://helpx.adobe.com/illustrator/using/live-paint-groups.html)), which keeps its id, the paint of its faces and edges, and its name, lock, visibility, transparency, appearance, opacity mask and graphic style link. Object › Live Paint › Make with a Live Paint group selected, and a Live Paint Bucket click on a selected path beside a selected Live Paint group, add the paths to that group the same way. Make, Merge and the Live Paint Bucket take paths, compound paths and groups of them; with type, an image or another object among the selection they report that it has to be converted to paths first and change nothing, as type and raster images can't go into a Live Paint group directly ([Create Live Paint groups](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/learn-painting-basics/create-live-paint-groups.html)); before, such an object went among the group's hidden sources and vanished from the canvas. Evidence: `tests_build::live_paint_refuses_type_and_images_and_changes_nothing`. With the Live Paint Bucket selected, Alt-click on vector art samples its fill, stroke and stroke weight into the paint defaults ([Paint with the Live Paint Bucket tool](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/learn-painting-basics/paint-with-the-live-paint-bucket-tool.html)). On a placed image it samples the image pixel as a solid fill with no stroke. Alt+Shift-click samples only the color under the pointer, including an intermediate gradient color or image pixels, into the active fill or stroke ([Default keyboard shortcuts](https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html)). Neither click paints or changes the selection or selected art, and the next Bucket click paints with the sampled paint. Evidence: `tests_build::live_paint_bucket_alt_sample_paints_next_face_without_changing_selection`. + Double-clicking the Live Paint Bucket opens Live Paint Bucket Options ([Paint with the Live Paint Bucket tool](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/learn-painting-basics/paint-with-the-live-paint-bucket-tool.html)): Paint Fills (on) and Paint Strokes (off) set whether a click paints faces, edges or both, Highlight turns the outline of the face or edge under the pointer on or off, and Color and Width set it (red and 4 px by default). With both on, a click paints the edge within 4 px of the pointer, else the face, and Shift switches to Paint Fills only; with one on, Shift switches to the other. The options last across tool switches and are saved with the preferences (`tool.setOption {tool: "livePaintBucket"}`). Evidence: `builder::tests::{bucket_options_choose_what_a_click_paints, bucket_options_set_the_highlight}`, `dialogs::live_paint_bucket::tests::double_clicking_the_tool_opens_its_options_and_ok_sets_them`. 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"}`).