From fa3d6d92981f356f6e31786ada47a7a5ea904fb0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 22 Oct 2026 01:09:25 +0000 Subject: [PATCH 1/5] G11: open Arc tool options on canvas click (WIP) Co-authored-by: nrq6993-a11y --- crates/ui-egui/src/dialogs/form.rs | 5 +++-- crates/ui-egui/src/dialogs/mod.rs | 2 +- crates/ui-egui/src/dialogs/shapes.rs | 30 +++++++++++++++++++++++----- crates/ui-egui/src/dialogs/tests.rs | 14 +++++++++++++ crates/ui-egui/src/dialogs/tools.rs | 10 ++++++++++ 5 files changed, 53 insertions(+), 8 deletions(-) diff --git a/crates/ui-egui/src/dialogs/form.rs b/crates/ui-egui/src/dialogs/form.rs index e7092235d..6df5da3e4 100644 --- a/crates/ui-egui/src/dialogs/form.rs +++ b/crates/ui-egui/src/dialogs/form.rs @@ -63,7 +63,7 @@ fn lengths(kind: &str) -> &'static [&'static str] { "roundedRectangle" => &["width", "height", "radius"], "polygon" => &["radius"], "star" => &["radius1", "radius2"], - "lineSegment" => &["length"], + "lineSegment" | "arc" => &["length"], "offsetPath" => &["offset"], _ => &[], } @@ -73,7 +73,7 @@ fn lengths(kind: &str) -> &'static [&'static str] { /// number: fields that ↑/↓ step by one (Shift: ten) and that do math, as the distances do. fn suffix(kind: &str, key: &str) -> Option<&'static str> { match (kind, key) { - ("rotate" | "shear" | "reflect" | "move", "angle") => Some("°"), + ("rotate" | "shear" | "reflect" | "move" | "arc" | "lineSegment", "angle") => Some("°"), ("scale", "sx" | "sy") => Some("%"), _ => None, } @@ -96,6 +96,7 @@ fn order(kind: &str) -> &'static [&'static str] { "rectangularGrid" => &["width", "height", "rows", "rowSkew", "columns", "columnSkew"], "polarGrid" => &["width", "height", "concentric", "concentricSkew", "radial", "radialSkew"], "artboardOptions" => &["name", "width", "height"], + "arc" | "lineSegment" => &["length", "angle"], _ => &[], } } diff --git a/crates/ui-egui/src/dialogs/mod.rs b/crates/ui-egui/src/dialogs/mod.rs index ac2c422fb..b75313615 100644 --- a/crates/ui-egui/src/dialogs/mod.rs +++ b/crates/ui-egui/src/dialogs/mod.rs @@ -246,7 +246,7 @@ macro_rules! registry { registry! { DimensionOptions: [dimension::KIND] => dimension::SPEC, NewDocument: [new_document::KIND] => new_document::SPEC, - Shape: ["rectangle", "roundedRectangle", "ellipse", "polygon", "star", "lineSegment", "rectangularGrid", "polarGrid"] => shapes::SPEC, + Shape: ["rectangle", "roundedRectangle", "ellipse", "polygon", "star", "lineSegment", "arc", "rectangularGrid", "polarGrid"] => shapes::SPEC, Transform: ["move", "rotate", "scale", "reflect", "shear"] => transform::SPEC, PathOp: ["average", "offsetPath", "simplify", "splitIntoGrid"] => path_ops::SPEC, DocumentSetup: ["documentSetup"] => document_setup::SPEC, diff --git a/crates/ui-egui/src/dialogs/shapes.rs b/crates/ui-egui/src/dialogs/shapes.rs index b0a3a1fd0..3ef81ff32 100644 --- a/crates/ui-egui/src/dialogs/shapes.rs +++ b/crates/ui-egui/src/dialogs/shapes.rs @@ -21,6 +21,7 @@ fn title(kind: &str) -> &'static str { "star" => tl!("Star"), "rectangularGrid" => tl!("Rectangular Grid"), "polarGrid" => tl!("Polar Grid"), + "arc" => tl!("Arc"), _ => tl!("Line Segment Tool Options"), } } @@ -80,6 +81,10 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { }); } else { form::grid(ui, d, unit); + if d.kind == "arc" { + ui.add_space(8.0); + form::check(ui, d, "closed", tl!("Closed")); + } } let own = match d.kind.as_str() { "rectangularGrid" => ("frame", tl!("Use Outside Rectangle As Frame")), @@ -109,11 +114,13 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { "star" => Some(("points", 5.0, 2.0)), _ => None, }; - let options = count.map(|(key, default, min)| { - let mut values = json!({}); - values[key] = json!(d.f64(key, default).clamp(min, 1000.0) as u64); - values - }); + let options = count + .map(|(key, default, min)| { + let mut values = json!({}); + values[key] = json!(d.f64(key, default).clamp(min, 1000.0) as u64); + values + }) + .or_else(|| (d.kind == "arc").then(|| json!({"closed": d.bool("closed")}))); let (x, y) = (d.f64("x", 0.0), d.f64("y", 0.0)); let (bx, by) = box_origin(d); let (id, params) = match d.kind.as_str() { @@ -140,6 +147,19 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { let (l, a) = (d.f64("length", 100.0), d.f64("angle", 0.0).to_radians()); ("shape.line", json!({"x1": x, "y1": y, "x2": x + l * a.cos(), "y2": y - l * a.sin()})) } + "arc" => { + let (l, a) = (d.f64("length", 100.0), d.f64("angle", 0.0).to_radians()); + ( + "shape.arc", + json!({ + "x1": x, + "y1": y, + "x2": x + l * a.cos(), + "y2": y - l * a.sin(), + "closed": d.bool("closed"), + }), + ) + } _ => ( "shape.rectangle", json!({"x": bx, "y": by, "width": d.f64("width", 100.0), "height": d.f64("height", 100.0), "radius": d.f64("radius", 0.0)}), diff --git a/crates/ui-egui/src/dialogs/tests.rs b/crates/ui-egui/src/dialogs/tests.rs index 8fbdf2d3a..6f1f807c5 100644 --- a/crates/ui-egui/src/dialogs/tests.rs +++ b/crates/ui-egui/src/dialogs/tests.rs @@ -41,6 +41,7 @@ fn kinds_map_to_their_dialogs() { ("newDocument", DialogKind::NewDocument), ("roundedRectangle", DialogKind::Shape), ("lineSegment", DialogKind::Shape), + ("arc", DialogKind::Shape), ("rectangularGrid", DialogKind::Shape), ("polarGrid", DialogKind::Shape), ("shear", DialogKind::Transform), @@ -88,6 +89,7 @@ fn every_dialog_draws() { "polygon", "star", "lineSegment", + "arc", "rectangularGrid", "polarGrid", "rotate", @@ -1711,6 +1713,18 @@ fn polar_grid_shared_canvas_click_defaults_units_counts_and_history() { assert_eq!(app.session.doc().unwrap().doc, made); } +/// Part of gap G11: an Arc tool click opens its options at the pointer; OK draws an arc there. +#[test] +fn arc_click_opens_options_and_ok_draws() { + use vectorcraft_doc::NodeKind; + let mut app = app(); + open_tool_dialog(&mut app, "arc", json!({"x": 100.0, "y": 50.0})); + assert_eq!(app.ui.dialog.as_ref().unwrap().kind, "arc"); + super::confirm(&mut app).unwrap(); + let n = app.session.doc().unwrap().doc.layers[0].children().unwrap().last().unwrap(); + assert!(matches!(n.kind, NodeKind::Path { .. })); +} + /// #1377: Polar Grid's dialog has a skew slider under each divider count, Create Compound Path /// From Ellipses and Fill Grid, opened with the tool's stored values; OK draws with them and /// stores them. diff --git a/crates/ui-egui/src/dialogs/tools.rs b/crates/ui-egui/src/dialogs/tools.rs index acd76f3f4..d13e25cdf 100644 --- a/crates/ui-egui/src/dialogs/tools.rs +++ b/crates/ui-egui/src/dialogs/tools.rs @@ -20,6 +20,16 @@ pub fn open_tool_dialog(app: &mut VectorcraftApp, kind: &str, p: Value) { Dialog::new(kind, json!({"x": x, "y": y, "radius1": "50 pt", "radius2": "25 pt", "points": app.session.tool_options_of(kind)["points"]})) } "lineSegment" => Dialog::new(kind, json!({"x": x, "y": y, "length": "100 pt", "angle": 0})), + "arc" => Dialog::new( + kind, + json!({ + "x": x, + "y": y, + "length": "100 pt", + "angle": 0, + "closed": app.session.tool_options_of(kind).get("closed").and_then(Value::as_bool).unwrap_or(false), + }), + ), // The grids: the tool's stored options (divider counts and skews, switches) at the default size. "rectangularGrid" | "polarGrid" => { let mut obj = match app.session.tool_options_of(kind) { From d6e64cb080c55c31887c5e690c7143baa8c6edac Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 22 Oct 2026 01:34:46 +0000 Subject: [PATCH 2/5] G11: open Arc tool options on canvas click ## Summary A click on the artboard with the Arc tool (no drag) opens an Arc options dialog at the pointer, matching Illustrator. Length, angle, and Closed follow the same form as Line Segment; OK runs shape.arc and remembers Closed via tool.setOption. ## Tests - dialogs::tests::arc_click_opens_options_and_ok_draws - Existing draw2::family::tests::click_opens_dialog_and_arc_shift (tools) Part of gap G11 Co-authored-by: nrq6993-a11y --- crates/ui-egui/src/dialogs/shapes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ui-egui/src/dialogs/shapes.rs b/crates/ui-egui/src/dialogs/shapes.rs index 3ef81ff32..e043474d7 100644 --- a/crates/ui-egui/src/dialogs/shapes.rs +++ b/crates/ui-egui/src/dialogs/shapes.rs @@ -1,5 +1,5 @@ //! Shape size dialogs (a click with a shape tool): Rectangle, Rounded Rectangle, Ellipse, Polygon, -//! Star, Line Segment Tool Options and the two grids (size, divider counts and skews, switches). +//! Star, Line Segment and Arc tool options, and the two grids (size, divider counts and skews, switches). use serde_json::{Value, json}; use vectorcraft_geom::{Point, Rect}; From 5359c92a19ca2de2d4f7b518cc87ce378cbccd26 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 22 Oct 2026 04:42:04 +0000 Subject: [PATCH 3/5] G11: Arc segment dialog options wired to shape.arc Replace length/angle with Length X/Y, reference point, Type, Base Along, Slope and Fill Arc; extend geom and shape.arc params. Co-authored-by: nrq6993-a11y --- crates/engine/src/cmd/create.rs | 41 ++++++++++-- crates/geom/src/shapes.rs | 38 ++++++++--- crates/tools/src/settings.rs | 2 +- crates/ui-egui/src/dialogs/form.rs | 7 +- crates/ui-egui/src/dialogs/shapes.rs | 98 ++++++++++++++++++++++++---- crates/ui-egui/src/dialogs/tests.rs | 9 ++- crates/ui-egui/src/dialogs/tools.rs | 32 ++++++--- 7 files changed, 186 insertions(+), 41 deletions(-) diff --git a/crates/engine/src/cmd/create.rs b/crates/engine/src/cmd/create.rs index 617f3a529..a9a86195d 100644 --- a/crates/engine/src/cmd/create.rs +++ b/crates/engine/src/cmd/create.rs @@ -32,7 +32,15 @@ pub fn specs() -> Vec { ), cmd!("shape.line", "Line Segment", [], None, "{x1, y1, x2, y2} → {id}", has_doc, line), cmd!("shape.spiral", "Spiral", [], None, "{cx, cy, radius, decay=80 (%), segments=10, clockwise?} → {id}", has_doc, spiral), - cmd!("shape.arc", "Arc", [], None, "{x1, y1, x2, y2, closed?} → {id}", has_doc, arc), + cmd!( + "shape.arc", + "Arc", + [], + None, + "{x1, y1, x2, y2, closed?, slope?} or {x, y, lengthX, lengthY, reference?, baseAlong?: x|y, slope?, closed?, fillArc?} → {id}", + has_doc, + arc + ), cmd!( "shape.rectangularGrid", "Rectangular Grid", @@ -393,9 +401,34 @@ fn spiral(s: &mut Session, p: &Value) -> Result { } fn arc(s: &mut Session, p: &Value) -> Result { - let a = Point::new(f64_req(p, "x1", "shape.arc")?, f64_req(p, "y1", "shape.arc")?); - let b = Point::new(f64_req(p, "x2", "shape.arc")?, f64_req(p, "y2", "shape.arc")?); - add_art(s, "Arc", path_kind(shapes::arc(a, b, 0.0, switch_or(p, "closed", false, "shape.arc")?), None), None) + let closed = switch_or(p, "closed", false, "shape.arc")?; + let fill_arc = switch_or(p, "fillArc", false, "shape.arc")?; + let base_x = !matches!(p.get("baseAlong").and_then(Value::as_str), Some("y" | "Y")); + let path = if p.get("lengthX").is_some() || p.get("lengthY").is_some() { + let anchor = Point::new(f64_req(p, "x", "shape.arc")?, f64_req(p, "y", "shape.arc")?); + let reference = p.get("reference").and_then(Value::as_u64).unwrap_or(0) as usize; + shapes::arc_segment( + anchor, + reference, + number_or(p, "lengthX", 100.0, "shape.arc")?, + number_or(p, "lengthY", 100.0, "shape.arc")?, + base_x, + number_or(p, "slope", 50.0, "shape.arc")?, + closed, + ) + } else { + let a = Point::new(f64_req(p, "x1", "shape.arc")?, f64_req(p, "y1", "shape.arc")?); + let b = Point::new(f64_req(p, "x2", "shape.arc")?, f64_req(p, "y2", "shape.arc")?); + let slope = number_or(p, "slope", 100.0, "shape.arc")?; + shapes::arc(a, b, slope, closed, base_x) + }; + let fill = if fill_arc { s.paint.fill.clone() } else { vectorcraft_color::Paint::None }; + let look = s.new_art_look( + fill, + if s.paint.stroke.is_none() { vectorcraft_color::Paint::solid(vectorcraft_color::Color::BLACK) } else { s.paint.stroke.clone() }, + s.paint.stroke_width.max(0.1), + ); + add_look(s, "Arc", path_kind(path, None), look, None) } /// The grid `paths` (bottom first) as one selected group in the new-art stroke, filled with the diff --git a/crates/geom/src/shapes.rs b/crates/geom/src/shapes.rs index e677d34ff..4d9f92281 100644 --- a/crates/geom/src/shapes.rs +++ b/crates/geom/src/shapes.rs @@ -176,17 +176,33 @@ pub fn line(a: Point, b: Point) -> PathData { PathData::single(SubPath::polyline(&[a, b], false)) } -/// Quarter-ellipse arc from `a` to `b` (Arc tool default: concave, open, x-axis base). -pub fn arc(a: Point, b: Point, slope: f64, closed: bool) -> PathData { - // Corner of the arc's bounding box is at (a.x, b.y): the arc bows away from it. - let k = KAPPA * (1.0 + slope.clamp(-1.0, 1.0) * 0.0); - let corner = Point::new(b.x, a.y); - let h1 = a + (corner - a) * k; - let h2 = b + (corner - b) * k; +/// Bounding box of an arc segment when reference point `reference` (0…8) is placed on `anchor`. +pub fn arc_bbox(anchor: Point, reference: usize, length_x: f64, length_y: f64) -> Rect { + let w = length_x.abs().max(1e-9); + let h = length_y.abs().max(1e-9); + let rp = crate::reference_point(Rect::new(0.0, 0.0, w, h), reference.min(8)); + Rect::new(anchor.x - rp.x, anchor.y - rp.y, anchor.x - rp.x + w, anchor.y - rp.y + h) +} + +/// Arc segment from the Arc tool dialog: lengths, reference point, base axis, slope and open/closed. +pub fn arc_segment(anchor: Point, reference: usize, length_x: f64, length_y: f64, base_along_x: bool, slope: f64, closed: bool) -> PathData { + let r = arc_bbox(anchor, reference, length_x, length_y).abs(); + let (a, b) = if base_along_x { (Point::new(r.x0, r.y0), Point::new(r.x1, r.y1)) } else { (Point::new(r.x0, r.y1), Point::new(r.x1, r.y0)) }; + arc(a, b, slope, closed, base_along_x) +} + +/// Quarter-ellipse arc from `a` to `b` (Arc tool default: open, x-axis base when `base_along_x`). +/// `slope` is −100…100 (concave…convex); 0 is a straight chord. +pub fn arc(a: Point, b: Point, slope: f64, closed: bool, base_along_x: bool) -> PathData { + let t = slope.clamp(-100.0, 100.0) / 100.0; + let corner = if t >= 0.0 { Point::new(b.x, a.y) } else { Point::new(a.x, b.y) }; + let k = KAPPA * t.abs(); + let h1 = if k == 0.0 { a } else { a + (corner - a) * k }; + let h2 = if k == 0.0 { b } else { b + (corner - b) * k }; let mut anchors = vec![Anchor { p: a, h_in: a, h_out: h1, kind: AnchorKind::Corner }, Anchor { p: b, h_in: h2, h_out: b, kind: AnchorKind::Corner }]; if closed { - let o = Point::new(a.x, b.y); + let o = if base_along_x { Point::new(a.x, b.y) } else { Point::new(b.x, a.y) }; anchors.push(Anchor::corner(o)); } PathData::single(SubPath::new(anchors, closed)) @@ -531,7 +547,9 @@ mod tests { #[test] fn arc_open_and_closed() { - assert_eq!(arc(Point::ZERO, Point::new(10.0, 10.0), 0.0, false).anchor_count(), 2); - assert!(arc(Point::ZERO, Point::new(10.0, 10.0), 0.0, true).subpaths[0].closed); + assert_eq!(arc(Point::ZERO, Point::new(10.0, 10.0), 0.0, false, true).anchor_count(), 2); + assert!(arc(Point::ZERO, Point::new(10.0, 10.0), 0.0, true, true).subpaths[0].closed); + let seg = arc_segment(Point::ZERO, 0, 100.0, 50.0, true, 50.0, false); + assert_eq!(seg.anchor_count(), 2); } } diff --git a/crates/tools/src/settings.rs b/crates/tools/src/settings.rs index 8abb10f44..e395105c0 100644 --- a/crates/tools/src/settings.rs +++ b/crates/tools/src/settings.rs @@ -26,7 +26,7 @@ const ROWS: &[Row] = &[ (SYMBOLISM, Some("symbolism"), &["diameter", "intensity", "density"]), (&["mirrorCut"], None, &["axis", "keep"]), (&["puppetWarp"], None, &["showMesh", "expand"]), - (&["arc"], None, &["closed"]), + (&["arc"], None, &["closed", "lengthX", "lengthY", "reference", "baseAlong", "slope", "fillArc"]), (&["spiral"], None, &["decay", "segments", "clockwise"]), (&["rectangularGrid"], None, &["rows", "columns", "rowSkew", "columnSkew", "frame", "fill"]), (&["polarGrid"], None, &["concentric", "radial", "concentricSkew", "radialSkew", "compound", "fill"]), diff --git a/crates/ui-egui/src/dialogs/form.rs b/crates/ui-egui/src/dialogs/form.rs index 6df5da3e4..ffa232135 100644 --- a/crates/ui-egui/src/dialogs/form.rs +++ b/crates/ui-egui/src/dialogs/form.rs @@ -63,7 +63,8 @@ fn lengths(kind: &str) -> &'static [&'static str] { "roundedRectangle" => &["width", "height", "radius"], "polygon" => &["radius"], "star" => &["radius1", "radius2"], - "lineSegment" | "arc" => &["length"], + "lineSegment" => &["length"], + "arc" => &["lengthX", "lengthY"], "offsetPath" => &["offset"], _ => &[], } @@ -73,7 +74,7 @@ fn lengths(kind: &str) -> &'static [&'static str] { /// number: fields that ↑/↓ step by one (Shift: ten) and that do math, as the distances do. fn suffix(kind: &str, key: &str) -> Option<&'static str> { match (kind, key) { - ("rotate" | "shear" | "reflect" | "move" | "arc" | "lineSegment", "angle") => Some("°"), + ("rotate" | "shear" | "reflect" | "move" | "lineSegment", "angle") => Some("°"), ("scale", "sx" | "sy") => Some("%"), _ => None, } @@ -96,7 +97,7 @@ fn order(kind: &str) -> &'static [&'static str] { "rectangularGrid" => &["width", "height", "rows", "rowSkew", "columns", "columnSkew"], "polarGrid" => &["width", "height", "concentric", "concentricSkew", "radial", "radialSkew"], "artboardOptions" => &["name", "width", "height"], - "arc" | "lineSegment" => &["length", "angle"], + "lineSegment" => &["length", "angle"], _ => &[], } } diff --git a/crates/ui-egui/src/dialogs/shapes.rs b/crates/ui-egui/src/dialogs/shapes.rs index e043474d7..da21b9fb2 100644 --- a/crates/ui-egui/src/dialogs/shapes.rs +++ b/crates/ui-egui/src/dialogs/shapes.rs @@ -2,6 +2,7 @@ //! Star, Line Segment and Arc tool options, and the two grids (size, divider counts and skews, switches). use serde_json::{Value, json}; +use vectorcraft_doc::Unit; use vectorcraft_geom::{Point, Rect}; use super::{DialogSpec, form, run_and_close}; @@ -21,7 +22,7 @@ fn title(kind: &str) -> &'static str { "star" => tl!("Star"), "rectangularGrid" => tl!("Rectangular Grid"), "polarGrid" => tl!("Polar Grid"), - "arc" => tl!("Arc"), + "arc" => tl!("Arc Segment Tool Options"), _ => tl!("Line Segment Tool Options"), } } @@ -49,6 +50,21 @@ fn set_grid_placed(d: &mut Dialog, r: Rect) { d.fields.insert("height".into(), json!(r.height())); } +/// Arc segment dialog: keep reference `refi` on the canvas click (`__anchorX`/`__anchorY`). +pub(crate) fn arc_pin_to_anchor(d: &mut Dialog) { + let anchor = Point::new(d.f64("__anchorX", d.f64("x", 0.0)), d.f64("__anchorY", d.f64("y", 0.0))); + let refi = d.fields.get("reference").and_then(|v| v.as_u64()).map(|n| (n as usize).min(8)).unwrap_or(0); + let w = d.f64("lengthX", 100.0).abs().max(1e-9); + let h = d.f64("lengthY", 100.0).abs().max(1e-9); + let rp = vectorcraft_geom::reference_point(Rect::new(0.0, 0.0, w, h), refi); + d.fields.insert("x".into(), json!(anchor.x - rp.x)); + d.fields.insert("y".into(), json!(anchor.y - rp.y)); +} + +fn arc_reference(d: &Dialog) -> usize { + d.fields.get("reference").and_then(|v| v.as_u64()).map(|n| (n as usize).min(8)).unwrap_or(0) +} + /// Top-left `x`/`y` so reference point `refi` sits on the canvas click (`__anchorX`/`__anchorY`). pub(crate) fn grid_pin_to_anchor(d: &mut Dialog) { let anchor = Point::new(d.f64("__anchorX", d.f64("x", 0.0)), d.f64("__anchorY", d.f64("y", 0.0))); @@ -79,12 +95,10 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { set_grid_placed(d, artboard_rect(was, refi, None, None, Some(w), Some(h), false)); } }); + } else if d.kind == "arc" { + arc_form(ui, d, unit); } else { form::grid(ui, d, unit); - if d.kind == "arc" { - ui.add_space(8.0); - form::check(ui, d, "closed", tl!("Closed")); - } } let own = match d.kind.as_str() { "rectangularGrid" => ("frame", tl!("Use Outside Rectangle As Frame")), @@ -98,6 +112,50 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { false } +fn arc_form(ui: &mut egui::Ui, d: &mut Dialog, unit: Unit) { + ui.horizontal(|ui| { + let refi = arc_reference(d); + if let Some(i) = widgets::reference_point(ui, refi) { + d.fields.insert("reference".into(), json!(i)); + arc_pin_to_anchor(d); + } + }); + ui.add_space(4.0); + let rail = crate::theme::Tokens::get(ui.ctx()).input_border; + egui::Grid::new("arc-dlg").num_columns(2).spacing([10.0, 8.0]).show(ui, |ui| { + let lx0 = d.f64("lengthX", 100.0); + form::length_field(ui, d, "lengthX", tl!("Length X-Axis:"), unit); + if d.f64("lengthX", 100.0) != lx0 { + arc_pin_to_anchor(d); + } + let ly0 = d.f64("lengthY", 100.0); + form::length_field(ui, d, "lengthY", tl!("Length Y-Axis:"), unit); + if d.f64("lengthY", 100.0) != ly0 { + arc_pin_to_anchor(d); + } + form::choice(ui, d, "arcType", tl!("Type:"), (form::FIELD_W, form::FIELD_W - 12.0), &[("open", tl!("Open")), ("closed", tl!("Closed"))]); + form::choice( + ui, + d, + "baseAlong", + tl!("Base Along:"), + (form::FIELD_W, form::FIELD_W - 12.0), + &[ + ("x", crate::i18n::tr_ctx(crate::i18n::current(), "axis", "X Axis")), + ("y", crate::i18n::tr_ctx(crate::i18n::current(), "axis", "Y Axis")), + ], + ); + form::row_label(ui, tl!("Slope:")); + ui.vertical(|ui| { + form::slider_field_named(ui, d, "slope", tl!("Slope:"), -100.0..=100.0, "", &|_| rail); + form::slider_ends(ui, form::SLIDER_LABEL, (tl!("Concave"), tl!("Convex"))); + }); + ui.end_row(); + }); + ui.add_space(8.0); + form::check(ui, d, "fillArc", tl!("Fill Arc")); +} + /// The top-left corner of a rectangle or ellipse: the click itself, or with Alt held (draw from centre) the click minus half the size. fn box_origin(d: &Dialog) -> (f64, f64) { let (x, y) = (d.f64("x", 0.0), d.f64("y", 0.0)); @@ -120,7 +178,19 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { values[key] = json!(d.f64(key, default).clamp(min, 1000.0) as u64); values }) - .or_else(|| (d.kind == "arc").then(|| json!({"closed": d.bool("closed")}))); + .or_else(|| { + (d.kind == "arc").then(|| { + json!({ + "closed": d.str("arcType").eq_ignore_ascii_case("closed"), + "lengthX": d.f64("lengthX", 100.0), + "lengthY": d.f64("lengthY", 100.0), + "reference": arc_reference(d), + "baseAlong": d.str("baseAlong"), + "slope": d.f64("slope", 50.0), + "fillArc": d.bool("fillArc"), + }) + }) + }); let (x, y) = (d.f64("x", 0.0), d.f64("y", 0.0)); let (bx, by) = box_origin(d); let (id, params) = match d.kind.as_str() { @@ -148,15 +218,19 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { ("shape.line", json!({"x1": x, "y1": y, "x2": x + l * a.cos(), "y2": y - l * a.sin()})) } "arc" => { - let (l, a) = (d.f64("length", 100.0), d.f64("angle", 0.0).to_radians()); + let anchor = Point::new(d.f64("__anchorX", x), d.f64("__anchorY", y)); ( "shape.arc", json!({ - "x1": x, - "y1": y, - "x2": x + l * a.cos(), - "y2": y - l * a.sin(), - "closed": d.bool("closed"), + "x": anchor.x, + "y": anchor.y, + "lengthX": d.f64("lengthX", 100.0), + "lengthY": d.f64("lengthY", 100.0), + "reference": arc_reference(d), + "baseAlong": d.str("baseAlong"), + "slope": d.f64("slope", 50.0), + "closed": d.str("arcType").eq_ignore_ascii_case("closed"), + "fillArc": d.bool("fillArc"), }), ) } diff --git a/crates/ui-egui/src/dialogs/tests.rs b/crates/ui-egui/src/dialogs/tests.rs index 6f1f807c5..775232b5e 100644 --- a/crates/ui-egui/src/dialogs/tests.rs +++ b/crates/ui-egui/src/dialogs/tests.rs @@ -1719,10 +1719,17 @@ fn arc_click_opens_options_and_ok_draws() { use vectorcraft_doc::NodeKind; let mut app = app(); open_tool_dialog(&mut app, "arc", json!({"x": 100.0, "y": 50.0})); - assert_eq!(app.ui.dialog.as_ref().unwrap().kind, "arc"); + let d = app.ui.dialog.as_ref().unwrap(); + assert_eq!(d.kind, "arc"); + assert!(d.fields.contains_key("lengthX") && d.fields.contains_key("lengthY")); super::confirm(&mut app).unwrap(); let n = app.session.doc().unwrap().doc.layers[0].children().unwrap().last().unwrap(); assert!(matches!(n.kind, NodeKind::Path { .. })); + let pd = n.path_data().unwrap(); + let a0 = pd.subpaths[0].anchors[0].p; + let a1 = pd.subpaths[0].anchors[1].p; + assert!((a0.x - 100.0).abs() < 1e-6 && (a0.y - 50.0).abs() < 1e-6); + assert!((a1.x - 200.0).abs() < 1e-6 && (a1.y - 150.0).abs() < 1e-6); } /// #1377: Polar Grid's dialog has a skew slider under each divider count, Create Compound Path diff --git a/crates/ui-egui/src/dialogs/tools.rs b/crates/ui-egui/src/dialogs/tools.rs index d13e25cdf..bcad596f8 100644 --- a/crates/ui-egui/src/dialogs/tools.rs +++ b/crates/ui-egui/src/dialogs/tools.rs @@ -20,16 +20,28 @@ pub fn open_tool_dialog(app: &mut VectorcraftApp, kind: &str, p: Value) { Dialog::new(kind, json!({"x": x, "y": y, "radius1": "50 pt", "radius2": "25 pt", "points": app.session.tool_options_of(kind)["points"]})) } "lineSegment" => Dialog::new(kind, json!({"x": x, "y": y, "length": "100 pt", "angle": 0})), - "arc" => Dialog::new( - kind, - json!({ - "x": x, - "y": y, - "length": "100 pt", - "angle": 0, - "closed": app.session.tool_options_of(kind).get("closed").and_then(Value::as_bool).unwrap_or(false), - }), - ), + "arc" => { + let opts = app.session.tool_options_of(kind); + let closed = opts.get("closed").and_then(Value::as_bool).unwrap_or(false); + let mut d = Dialog::new( + kind, + json!({ + "__anchorX": x, + "__anchorY": y, + "x": x, + "y": y, + "lengthX": opts.get("lengthX").cloned().unwrap_or(json!("100 pt")), + "lengthY": opts.get("lengthY").cloned().unwrap_or(json!("100 pt")), + "reference": opts.get("reference").cloned().unwrap_or(json!(0)), + "arcType": if closed { "closed" } else { "open" }, + "baseAlong": opts.get("baseAlong").cloned().unwrap_or(json!("x")), + "slope": opts.get("slope").cloned().unwrap_or(json!(50)), + "fillArc": opts.get("fillArc").and_then(Value::as_bool).unwrap_or(false), + }), + ); + super::shapes::arc_pin_to_anchor(&mut d); + d + } // The grids: the tool's stored options (divider counts and skews, switches) at the default size. "rectangularGrid" | "polarGrid" => { let mut obj = match app.session.tool_options_of(kind) { From 71b5c9c45cea96ccbdec5ab8e817bc8806da51a4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 22 Oct 2026 05:12:53 +0000 Subject: [PATCH 4/5] G11: Arc tool keeps segment options and uses them on drag Co-authored-by: nrq6993-a11y --- crates/tools/src/draw2/family.rs | 107 +++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 6 deletions(-) diff --git a/crates/tools/src/draw2/family.rs b/crates/tools/src/draw2/family.rs index 9b068bc05..ff2cc8853 100644 --- a/crates/tools/src/draw2/family.rs +++ b/crates/tools/src/draw2/family.rs @@ -9,7 +9,7 @@ use serde_json::{Value, json}; use vectorcraft_geom::Point; -use crate::guides::{DrawSnap, Leave, square}; +use crate::guides::{DrawSnap, Leave}; use crate::shape::drag_rect; use crate::{Action, Cursor, Mods, Overlay, PointerEvent, PointerKind, Tool, ToolContext, ToolKey}; @@ -20,6 +20,12 @@ pub struct FamilyTool { mods: Mods, began: bool, pub closed: bool, + length_x: f64, + length_y: f64, + reference: usize, + base_along: &'static str, + slope: f64, + fill_arc: bool, pub decay: f64, pub segments: u32, pub clockwise: bool, @@ -53,6 +59,12 @@ impl FamilyTool { mods: Mods::default(), began: false, closed: false, + length_x: 100.0, + length_y: 100.0, + reference: 0, + base_along: "x", + slope: 50.0, + fill_arc: false, decay: 80.0, segments: 10, clockwise: true, @@ -101,9 +113,24 @@ impl FamilyTool { (format!("shape.{}", self.id), params) } _ => { - let d = if m.shift { square(p - start) } else { p - start }; - let (a, b) = if m.alt { (start - d, start + d) } else { (start, start + d) }; - ("shape.arc".into(), json!({"x1": a.x, "y1": a.y, "x2": b.x, "y2": b.y, "closed": self.closed})) + let r = drag_rect(start, p, m).abs(); + let (w, h) = (r.width().max(1e-9), r.height().max(1e-9)); + let rp = vectorcraft_geom::reference_point(vectorcraft_geom::Rect::new(0.0, 0.0, w, h), self.reference.min(8)); + let anchor = Point::new(r.x0 + rp.x, r.y0 + rp.y); + ( + "shape.arc".into(), + json!({ + "x": anchor.x, + "y": anchor.y, + "lengthX": w, + "lengthY": h, + "reference": self.reference, + "baseAlong": self.base_along, + "slope": self.slope, + "closed": self.closed, + "fillArc": self.fill_arc, + }), + ) } } } @@ -219,12 +246,30 @@ impl Tool for FamilyTool { "polarGrid" => json!({ "concentric": self.rows, "radial": self.columns, "concentricSkew": self.skew[0], "radialSkew": self.skew[1], "compound": self.compound, "fill": self.fill, }), - _ => json!({"closed": self.closed}), + _ => json!({ + "closed": self.closed, + "lengthX": self.length_x, + "lengthY": self.length_y, + "reference": self.reference, + "baseAlong": self.base_along, + "slope": self.slope, + "fillArc": self.fill_arc, + }), } } fn set_option(&mut self, key: &str, v: &Value) { match key { "closed" => self.closed = v.as_bool().unwrap_or(self.closed), + "lengthX" => self.length_x = arc_length(v, self.length_x), + "lengthY" => self.length_y = arc_length(v, self.length_y), + "reference" => self.reference = v.as_u64().unwrap_or(self.reference as u64).min(8) as usize, + "baseAlong" => { + if let Some(s) = v.as_str() { + self.base_along = if s.eq_ignore_ascii_case("y") { "y" } else { "x" }; + } + } + "slope" => self.slope = v.as_f64().filter(|n| n.is_finite()).map_or(self.slope, |n| n.clamp(-100.0, 100.0)), + "fillArc" => self.fill_arc = v.as_bool().unwrap_or(self.fill_arc), "decay" => self.decay = v.as_f64().unwrap_or(self.decay).clamp(5.0, 150.0), "segments" => self.segments = v.as_u64().unwrap_or(10).clamp(2, 1000) as u32, "clockwise" => self.clockwise = v.as_bool().unwrap_or(self.clockwise), @@ -240,6 +285,11 @@ impl Tool for FamilyTool { } } +/// Arc segment length from a dialog or preference value. +fn arc_length(v: &Value, current: f64) -> f64 { + v.as_f64().filter(|n| n.is_finite()).map_or(current, |n| n.abs().max(1e-9)) +} + /// A grid divider skew from option value `v`: a finite number within the range, else `current`. fn grid_skew(v: &Value, current: f64) -> f64 { let max = vectorcraft_geom::shapes::GRID_SKEW_MAX; @@ -327,7 +377,52 @@ mod tests { let t = FamilyTool::new("arc"); let (c, v) = t.command(Point::new(0.0, 0.0), Point::new(10.0, 4.0), Mods { shift: true, ..Default::default() }); assert_eq!(c, "shape.arc"); - assert_eq!(v, json!({"x1": 0.0, "y1": 0.0, "x2": 10.0, "y2": 10.0, "closed": false})); + assert_eq!( + v, + json!({ + "x": 0.0, + "y": 0.0, + "lengthX": 10.0, + "lengthY": 10.0, + "reference": 0, + "baseAlong": "x", + "slope": 50.0, + "closed": false, + "fillArc": false, + }) + ); + } + + #[test] + fn arc_drag_uses_stored_segment_options() { + let mut t = FamilyTool::new("arc"); + for (key, value) in [ + ("lengthX", json!(80.0)), + ("lengthY", json!(40.0)), + ("reference", json!(4)), + ("baseAlong", json!("y")), + ("slope", json!(-25.0)), + ("closed", json!(true)), + ("fillArc", json!(true)), + ] { + t.set_option(key, &value); + } + let (command, params) = t.command(Point::new(10.0, 20.0), Point::new(50.0, 70.0), Mods::default()); + assert_eq!(command, "shape.arc"); + assert_eq!( + params, + json!({ + "x": 30.0, + "y": 45.0, + "lengthX": 40.0, + "lengthY": 50.0, + "reference": 4, + "baseAlong": "y", + "slope": -25.0, + "closed": true, + "fillArc": true, + }) + ); } /// The start and the dragged corner snap to Smart Guides (#506), Shift sliding the corner of From b1befcfa026465bd325b3dea0fcdb48f005d7d5d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 22 Oct 2026 06:34:12 +0000 Subject: [PATCH 5/5] G11: translate Arc Segment Tool Options dialog in all catalogs Co-authored-by: nrq6993-a11y # Conflicts: # crates/ui-egui/src/i18n/bg.tsv # crates/ui-egui/src/i18n/cs.tsv # crates/ui-egui/src/i18n/da.tsv # crates/ui-egui/src/i18n/de.tsv # crates/ui-egui/src/i18n/el.tsv # crates/ui-egui/src/i18n/es.tsv # crates/ui-egui/src/i18n/fi.tsv # crates/ui-egui/src/i18n/fr.tsv # crates/ui-egui/src/i18n/hu.tsv # crates/ui-egui/src/i18n/id.tsv # crates/ui-egui/src/i18n/it.tsv # crates/ui-egui/src/i18n/ja.tsv # crates/ui-egui/src/i18n/ka.tsv # crates/ui-egui/src/i18n/ko.tsv # crates/ui-egui/src/i18n/nl.tsv # crates/ui-egui/src/i18n/pl.tsv # crates/ui-egui/src/i18n/pt-br.tsv # crates/ui-egui/src/i18n/ru.tsv # crates/ui-egui/src/i18n/sv.tsv # crates/ui-egui/src/i18n/tr.tsv # crates/ui-egui/src/i18n/uk.tsv # crates/ui-egui/src/i18n/vi.tsv # crates/ui-egui/src/i18n/zh-hans.tsv # crates/ui-egui/src/i18n/zh-hant.tsv --- crates/ui-egui/src/i18n/bg.tsv | 10 ++++++++++ crates/ui-egui/src/i18n/cs.tsv | 10 ++++++++++ crates/ui-egui/src/i18n/da.tsv | 10 ++++++++++ crates/ui-egui/src/i18n/de.tsv | 11 +++++++++++ crates/ui-egui/src/i18n/el.tsv | 10 ++++++++++ crates/ui-egui/src/i18n/es.tsv | 10 ++++++++++ crates/ui-egui/src/i18n/fi.tsv | 10 ++++++++++ crates/ui-egui/src/i18n/fr.tsv | 10 ++++++++++ crates/ui-egui/src/i18n/hu.tsv | 10 ++++++++++ crates/ui-egui/src/i18n/id.tsv | 10 ++++++++++ crates/ui-egui/src/i18n/it.tsv | 11 +++++++++++ crates/ui-egui/src/i18n/ja.tsv | 11 +++++++++++ crates/ui-egui/src/i18n/ka.tsv | 10 ++++++++++ crates/ui-egui/src/i18n/ko.tsv | 11 +++++++++++ crates/ui-egui/src/i18n/nl.tsv | 10 ++++++++++ crates/ui-egui/src/i18n/pl.tsv | 10 ++++++++++ crates/ui-egui/src/i18n/pt-br.tsv | 10 ++++++++++ crates/ui-egui/src/i18n/ru.tsv | 11 +++++++++++ crates/ui-egui/src/i18n/sv.tsv | 10 ++++++++++ crates/ui-egui/src/i18n/tr.tsv | 11 +++++++++++ crates/ui-egui/src/i18n/uk.tsv | 11 +++++++++++ crates/ui-egui/src/i18n/vi.tsv | 11 +++++++++++ crates/ui-egui/src/i18n/zh-hans.tsv | 11 +++++++++++ crates/ui-egui/src/i18n/zh-hant.tsv | 11 +++++++++++ 24 files changed, 250 insertions(+) diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index b66475eb0..5f74d8a0c 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -5358,6 +5358,16 @@ theme Appearance Външен вид Fresco Фреска Effect Gallery… Галерия с ефекти… Effect Gallery Dialog Диалог „Галерия с ефекти“ + Arc Segment Tool Options Опции на инструмента „Дъгов сегмент“ + Base Along: Основа по: + Closed Затворен + Convex Изпъкнал + Fill Arc Запълване на дъга + Length X-Axis: Дължина по X: + Length Y-Axis: Дължина по Y: + Slope: Наклон: +axis X Axis X ос +axis Y Axis Y ос @msg Packaged {n} file(s) in {folder} Пакетирани са {n} файла в {folder} @msg Packaged {n} file(s) {note} Пакетирани са {n} файла {note} @msg Packaged {n} file(s) Пакетирани са {n} файла diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 9a5256f57..2c031b25b 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -5402,6 +5402,16 @@ mark-style Japanese Japonské Fresco Freska Effect Gallery… Galerie efektů… Effect Gallery Dialog Dialog Galerie efektů + Arc Segment Tool Options Možnosti nástroje Obloukový segment + Base Along: Základna podél: + Closed Uzavřený + Convex Konvexní + Fill Arc Výplň oblouku + Length X-Axis: Délka na ose X: + Length Y-Axis: Délka na ose Y: + Slope: Sklon: +axis X Axis osa X +axis Y Axis osa Y @msg Packaged {n} file(s) in {folder} Zabaleno souborů: {n} do {folder} @msg Packaged {n} file(s) {note} Zabaleno souborů: {n} {note} @msg Packaged {n} file(s) Zabaleno souborů: {n} diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index ea3b4cf9f..1e9d5a076 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -5374,6 +5374,16 @@ swatch Bright Violet Kraftig violet Fresco Fresko Effect Gallery… Effektgalleri… Effect Gallery Dialog Dialogboksen Effektgalleri + Arc Segment Tool Options Indstillinger for buesegmentværktøj + Base Along: Base langs: + Closed Lukket + Convex Konvex + Fill Arc Udfyld bue + Length X-Axis: Længde X-akse: + Length Y-Axis: Længde Y-akse: + Slope: Hældning: +axis X Axis X-akse +axis Y Axis Y-akse @msg Packaged {n} file(s) in {folder} Pakkede {n} fil(er) i {folder} @msg Packaged {n} file(s) {note} Pakkede {n} fil(er) {note} @msg Packaged {n} file(s) Pakkede {n} fil(er) diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index be635c5d5..8681b03b1 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4983,6 +4983,17 @@ effect Soft Weich Fresco Fresko Effect Gallery… Effekt-Galerie… Effect Gallery Dialog Dialogfeld „Effekt-Galerie“ + Arc Segment Tool Options Optionen für das Bogensegment-Werkzeug + Base Along: Basis entlang: + Closed Geschlossen + Concave Konkav + Convex Konvex + Fill Arc Bogen füllen + Length X-Axis: Länge X-Achse: + Length Y-Axis: Länge Y-Achse: + Slope: Steigung: +axis X Axis X-Achse +axis Y Axis Y-Achse @msg Packaged {n} file(s) in {folder} {n} Datei(en) in {folder} verpackt @msg Packaged {n} file(s) {note} {n} Datei(en) verpackt {note} @msg Packaged {n} file(s) {n} Datei(en) verpackt diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 3d5783a54..4358fb5f5 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -5354,6 +5354,16 @@ effect Points Σημεία Fresco Νωπογραφία Effect Gallery… Συλλογή εφέ… Effect Gallery Dialog Παράθυρο διαλόγου Συλλογή εφέ + Arc Segment Tool Options Επιλογές εργαλείου τμήματος τόξου + Base Along: Βάση κατά: + Closed Κλειστό + Convex Κυρτό + Fill Arc Γέμισμα τόξου + Length X-Axis: Μήκος άξονα X: + Length Y-Axis: Μήκος άξονα Y: + Slope: Κλίση: +axis X Axis Άξονας X +axis Y Axis Άξονας Y @msg Packaged {n} file(s) in {folder} Συσκευάστηκαν {n} αρχεία στον φάκελο {folder} @msg Packaged {n} file(s) {note} Συσκευάστηκαν {n} αρχεία {note} @msg Packaged {n} file(s) Συσκευάστηκαν {n} αρχεία diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 369d8c0a7..fcb70e9a4 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -5344,6 +5344,16 @@ mark-style Japanese Japonesas Fresco Fresco Effect Gallery… Galería de efectos… Effect Gallery Dialog Cuadro de diálogo Galería de efectos + Arc Segment Tool Options Opciones de la herramienta Segmento de arco + Base Along: Base a lo largo de: + Closed Cerrado + Convex Convexo + Fill Arc Rellenar arco + Length X-Axis: Longitud eje X: + Length Y-Axis: Longitud eje Y: + Slope: Pendiente: +axis X Axis Eje X +axis Y Axis Eje Y @msg Packaged {n} file(s) in {folder} Se empaquetaron {n} archivo(s) en {folder} @msg Packaged {n} file(s) {note} Se empaquetaron {n} archivo(s) {note} @msg Packaged {n} file(s) Se empaquetaron {n} archivo(s) diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 7d93f0751..0c3e3d646 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -5433,6 +5433,16 @@ swatch Bright Violet Kirkas violetti Fresco Fresko Effect Gallery… Tehostegalleria… Effect Gallery Dialog Tehostegalleria-valintaikkuna + Arc Segment Tool Options Kaarisegmenttityökalun asetukset + Base Along: Pohja pitkin: + Closed Suljettu + Convex Kupera + Fill Arc Täytä kaari + Length X-Axis: Pituus X-akseli: + Length Y-Axis: Pituus Y-akseli: + Slope: Kaltevuus: +axis X Axis X-akseli +axis Y Axis Y-akseli @msg Packaged {n} file(s) in {folder} Paketoitiin {n} tiedosto(a) kansioon {folder} @msg Packaged {n} file(s) {note} Paketoitiin {n} tiedosto(a) {note} @msg Packaged {n} file(s) Paketoitiin {n} tiedosto(a) diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 2e2c13196..c8f714e95 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -5401,6 +5401,16 @@ theme Appearance Apparence Fresco Fresque Effect Gallery… Galerie d'effets… Effect Gallery Dialog Boîte de dialogue Galerie d'effets + Arc Segment Tool Options Options de l’outil Segment d’arc + Base Along: Base le long de : + Closed Fermé + Convex Convexe + Fill Arc Remplir l’arc + Length X-Axis: Longueur axe X : + Length Y-Axis: Longueur axe Y : + Slope: Pente : +axis X Axis Axe X +axis Y Axis Axe Y @msg Packaged {n} file(s) in {folder} {n} fichier(s) assemblé(s) dans {folder} @msg Packaged {n} file(s) {note} {n} fichier(s) assemblé(s) {note} @msg Packaged {n} file(s) {n} fichier(s) assemblé(s) diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 44166d5a0..8ccba9237 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -5388,6 +5388,16 @@ imageTrace Outline Körvonal Fresco Freskó Effect Gallery… Effektusgaléria… Effect Gallery Dialog Effektusgaléria párbeszédpanel + Arc Segment Tool Options Ívszakasz eszköz beállításai + Base Along: Alap mentén: + Closed Zárt + Convex Domború + Fill Arc Ív kitöltése + Length X-Axis: Hossz X tengely: + Length Y-Axis: Hossz Y tengely: + Slope: Lejtés: +axis X Axis X tengely +axis Y Axis Y tengely @msg Packaged {n} file(s) in {folder} Becsomagolt fájlok: {n}, mappa: {folder} @msg Packaged {n} file(s) {note} Becsomagolt fájlok: {n} {note} @msg Packaged {n} file(s) Becsomagolt fájlok: {n} diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 0d18d3799..4f096a63c 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -5374,6 +5374,16 @@ swatch Bright Violet Violet cerah Fresco Fresko Effect Gallery… Galeri Efek… Effect Gallery Dialog Dialog Galeri Efek + Arc Segment Tool Options Opsi alat segmen busur + Base Along: Dasar sepanjang: + Closed Tertutup + Convex Cembung + Fill Arc Isi busur + Length X-Axis: Panjang sumbu X: + Length Y-Axis: Panjang sumbu Y: + Slope: Kemiringan: +axis X Axis Sumbu X +axis Y Axis Sumbu Y @msg Packaged {n} file(s) in {folder} Berhasil mengemas {n} file di {folder} @msg Packaged {n} file(s) {note} Berhasil mengemas {n} file {note} @msg Packaged {n} file(s) Berhasil mengemas {n} file diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index fdb0f9e19..e93bde038 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5017,6 +5017,17 @@ lighting Light Luce Fresco Affresco Effect Gallery… Galleria effetti… Effect Gallery Dialog Finestra di dialogo Galleria effetti + Arc Segment Tool Options Opzioni strumento Segmento arco + Base Along: Base lungo: + Closed Chiuso + Concave Concavo + Convex Convesso + Fill Arc Riempi arco + Length X-Axis: Lunghezza asse X: + Length Y-Axis: Lunghezza asse Y: + Slope: Pendenza: +axis X Axis Asse X +axis Y Axis Asse Y @msg Packaged {n} file(s) in {folder} Pacchetto creato con {n} file in {folder} @msg Packaged {n} file(s) {note} Pacchetto creato con {n} file {note} @msg Packaged {n} file(s) Pacchetto creato con {n} file diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 671a3f835..2c1e7210a 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -5079,6 +5079,17 @@ lighting Light ライト Simulate prints overprints as Overprint Preview shows them; separations always keep them. A preset flattens transparency before printing (PostScript files always flatten it). シミュレートでは、オーバープリントプレビューと同じ見え方でオーバープリントを印刷します。分版では常にオーバープリントが保持されます。プリセットを選ぶと、印刷前に透明が分割・統合されます (PostScript ファイルでは常に分割・統合されます)。 Effect Gallery… 効果ギャラリー… Effect Gallery Dialog 効果ギャラリーダイアログ + Arc Segment Tool Options 円弧セグメントツールオプション + Base Along: 基準方向: + Closed 閉じる + Concave 凹 + Convex 凸 + Fill Arc 円弧を塗りつぶす + Length X-Axis: X 軸の長さ: + Length Y-Axis: Y 軸の長さ: + Slope: 勾配: +axis X Axis X 軸 +axis Y Axis Y 軸 @msg Packaged {n} file(s) in {folder} {n} 個のファイルを {folder} にパッケージしました @msg Packaged {n} file(s) {note} {n} 個のファイルをパッケージしました {note} @msg Packaged {n} file(s) {n} 個のファイルをパッケージしました diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index e5bd4b2fc..6a2ec9d6d 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -5311,6 +5311,16 @@ imageTrace Outline კონტური Select artwork to apply effects აირჩიეთ ნამუშევარი ეფექტების გამოსაყენებლად Effect Gallery… ეფექტების გალერეა… Effect Gallery Dialog ეფექტების გალერეა დიალოგი + Arc Segment Tool Options რკალის სეგმენტის ხელსაწყოს პარამეტრები + Base Along: საფუძველი გასწვრივ: + Closed დახურული + Convex ამობზარი + Fill Arc რკალის შევსება + Length X-Axis: სიგრძე X ღერძზე: + Length Y-Axis: სიგრძე Y ღერძზე: + Slope: დახრა: +axis X Axis X ღერძი +axis Y Axis Y ღერძი @msg Packaged {n} file(s) in {folder} შეფუთულია ფაილი ({n}) საქაღალდეში {folder} @msg Packaged {n} file(s) {note} შეფუთულია ფაილი ({n}) {note} @msg Packaged {n} file(s) შეფუთულია ფაილი ({n}) diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index bcb591a38..193f0286b 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4980,6 +4980,17 @@ lighting Light 광원 Fresco 프레스코 Effect Gallery… 효과 갤러리… Effect Gallery Dialog 효과 갤러리 대화 상자 + Arc Segment Tool Options 호 세그먼트 도구 옵션 + Base Along: 기준 방향: + Closed 닫힘 + Concave 오목 + Convex 볼록 + Fill Arc 호 채우기 + Length X-Axis: X축 길이: + Length Y-Axis: Y축 길이: + Slope: 기울기: +axis X Axis X축 +axis Y Axis Y축 @msg Packaged {n} file(s) in {folder} {folder}에 파일 {n}개를 패키지했습니다 @msg Packaged {n} file(s) {note} 파일 {n}개를 패키지했습니다 {note} @msg Packaged {n} file(s) 파일 {n}개를 패키지했습니다 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 6666f4515..2fc7256a7 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -5417,6 +5417,16 @@ swatch Bright Violet Felviolet Fresco Fresco Effect Gallery… Effectgalerij… Effect Gallery Dialog Dialoogvenster Effectgalerij + Arc Segment Tool Options Opties boogsegmentgereedschap + Base Along: Basis langs: + Closed Gesloten + Convex Concaaf + Fill Arc Boog vullen + Length X-Axis: Lengte X-as: + Length Y-Axis: Lengte Y-as: + Slope: Helling: +axis X Axis X-as +axis Y Axis Y-as @msg Packaged {n} file(s) in {folder} {n} bestand(en) verpakt in {folder} @msg Packaged {n} file(s) {note} {n} bestand(en) verpakt {note} @msg Packaged {n} file(s) {n} bestand(en) verpakt diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 6728f08a3..bfd546675 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -5254,6 +5254,16 @@ mark-style Japanese Japońskie Fresco Fresk Effect Gallery… Galeria efektów… Effect Gallery Dialog Okno dialogowe Galeria efektów + Arc Segment Tool Options Opcje narzędzia Segment łuku + Base Along: Podstawa wzdłuż: + Closed Zamknięty + Convex Wypukła + Fill Arc Wypełnij łuk + Length X-Axis: Długość osi X: + Length Y-Axis: Długość osi Y: + Slope: Nachylenie: +axis X Axis Oś X +axis Y Axis Oś Y @msg Packaged {n} file(s) in {folder} Spakowane pliki: {n} w {folder} @msg Packaged {n} file(s) {note} Spakowane pliki: {n} {note} @msg Packaged {n} file(s) Spakowane pliki: {n} diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 2652b733e..8434a666f 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -5319,6 +5319,16 @@ mark-style Japanese Japonesas Fresco Afresco Effect Gallery… Galeria de Efeitos… Effect Gallery Dialog Caixa de diálogo Galeria de Efeitos + Arc Segment Tool Options Opções da Ferramenta Segmento de Arco + Base Along: Base ao longo de: + Closed Fechado + Convex Convexo + Fill Arc Preencher arco + Length X-Axis: Comprimento eixo X: + Length Y-Axis: Comprimento eixo Y: + Slope: Inclinação: +axis X Axis Eixo X +axis Y Axis Eixo Y @msg Packaged {n} file(s) in {folder} {n} arquivo(s) empacotado(s) em {folder} @msg Packaged {n} file(s) {note} {n} arquivo(s) empacotado(s) {note} @msg Packaged {n} file(s) {n} arquivo(s) empacotado(s) diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 121c69b02..23f269d9a 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -5013,6 +5013,17 @@ lighting Light Свет Fresco Фреска Effect Gallery… Галерея эффектов… Effect Gallery Dialog Диалог «Галерея эффектов» + Arc Segment Tool Options Параметры инструмента «Дуговой сегмент» + Base Along: Основание вдоль: + Closed Замкнутый + Concave Вогнутый + Convex Выпуклый + Fill Arc Заливка дуги + Length X-Axis: Длина по оси X: + Length Y-Axis: Длина по оси Y: + Slope: Уклон: +axis X Axis Ось X +axis Y Axis Ось Y @msg Packaged {n} file(s) in {folder} Упаковано файлов: {n} в {folder} @msg Packaged {n} file(s) {note} Упаковано файлов: {n} {note} @msg Packaged {n} file(s) Упаковано файлов: {n} diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index e667d47fe..794858f95 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -5358,6 +5358,16 @@ theme Appearance Utseende Fresco Fresk Effect Gallery… Effektgalleri… Effect Gallery Dialog Dialogrutan Effektgalleri + Arc Segment Tool Options Alternativ för bågsegmentsverktyget + Base Along: Bas längs: + Closed Stängd + Convex Konvex + Fill Arc Fyll båge + Length X-Axis: Längd X-axel: + Length Y-Axis: Längd Y-axel: + Slope: Lutning: +axis X Axis X-axel +axis Y Axis Y-axel @msg Packaged {n} file(s) in {folder} Paketerade {n} fil(er) i {folder} @msg Packaged {n} file(s) {note} Paketerade {n} fil(er) {note} @msg Packaged {n} file(s) Paketerade {n} fil(er) diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index d2bd8ad07..712abedc8 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -5361,6 +5361,17 @@ theme Appearance Görünüm Fresco Fresk Effect Gallery… Efekt Galerisi… Effect Gallery Dialog Efekt Galerisi İletişim Kutusu + Arc Segment Tool Options Yay Segmenti Aracı Seçenekleri + Base Along: Taban boyunca: + Closed Kapalı + Convex dışbükey + Fill Arc Yayı doldur + Length X-Axis: X ekseni uzunluğu: + Length Y-Axis: Y ekseni uzunluğu: + Slope: Eğim: +axis X Axis X ekseni +axis Y Axis Y ekseni + @msg Packaged {n} file(s) in {folder} {folder} biçiminde paketlenmiş {n} dosyası/dosyaları @msg Packaged {n} file(s) {note} Paketlenmiş {n} dosyası/dosyaları {note} @msg Packaged {n} file(s) Paketlenmiş {n} dosyası/dosyaları diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 5aeb9b144..2bf4ccc2b 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4997,6 +4997,17 @@ lighting Light Світло Fresco Фреска Effect Gallery… Галерея ефектів… Effect Gallery Dialog Діалог «Галерея ефектів» + Arc Segment Tool Options Параметри інструменту «Дуговий сегмент» + Base Along: Основа вздовж: + Closed Замкнений + Concave Увігнутий + Convex Опуклий + Fill Arc Заливка дуги + Length X-Axis: Довжина вздовж осі X: + Length Y-Axis: Довжина вздовж осі Y: + Slope: Ухил: +axis X Axis Вісь X +axis Y Axis Вісь Y @msg Packaged {n} file(s) in {folder} Упаковано файли: {n} {folder} @msg Packaged {n} file(s) {note} Упаковано файли: {n} {note} @msg Packaged {n} file(s) Упаковано файли: {n} diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 00f5cf042..a66258d11 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4997,6 +4997,17 @@ theme Appearance Giao diện Fresco Bích họa Effect Gallery… Thư viện hiệu ứng… Effect Gallery Dialog Hộp thoại Thư viện hiệu ứng + Arc Segment Tool Options Tùy chọn công cụ cung phân đoạn + Base Along: Cơ sở dọc theo: + Closed Đóng + Concave Lõm + Convex Lồi + Fill Arc Tô cung + Length X-Axis: Chiều dài trục X: + Length Y-Axis: Chiều dài trục Y: + Slope: Độ dốc: +axis X Axis Trục X +axis Y Axis Trục Y @msg Packaged {n} file(s) in {folder} Đã đóng gói {n} tệp trong {folder} @msg Packaged {n} file(s) {note} Đã đóng gói {n} tệp {note} @msg Packaged {n} file(s) Đã đóng gói {n} tệp diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index be7bdf10b..0c0687a1b 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -5241,6 +5241,17 @@ lighting Light 光源 Fresco 壁画 Effect Gallery… 效果库… Effect Gallery Dialog 效果库对话框 + Arc Segment Tool Options 弧段工具选项 + Base Along: 基线方向: + Closed 闭合 + Concave 凹 + Convex 凸 + Fill Arc 填充弧 + Length X-Axis: X 轴长度: + Length Y-Axis: Y 轴长度: + Slope: 斜率: +axis X Axis X 轴 +axis Y Axis Y 轴 @msg Packaged {n} file(s) in {folder} 已将 {n} 个文件打包到 {folder} @msg Packaged {n} file(s) {note} 已打包 {n} 个文件 {note} @msg Packaged {n} file(s) 已打包 {n} 个文件 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 01e3a90b3..94344b535 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -5036,6 +5036,17 @@ lighting Light 光源 Fresco 壁畫 Effect Gallery… 效果庫… Effect Gallery Dialog 效果庫對話框 + Arc Segment Tool Options 弧段工具選項 + Base Along: 基線方向: + Closed 封閉 + Concave 凹 + Convex 凸 + Fill Arc 填色弧形 + Length X-Axis: X 軸長度: + Length Y-Axis: Y 軸長度: + Slope: 斜率: +axis X Axis X 軸 +axis Y Axis Y 軸 @msg Packaged {n} file(s) in {folder} 已將 {n} 個檔案封裝到 {folder} @msg Packaged {n} file(s) {note} 已封裝 {n} 個檔案 {note} @msg Packaged {n} file(s) 已封裝 {n} 個檔案