From fa3d6d92981f356f6e31786ada47a7a5ea904fb0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 22 Oct 2026 01:09:25 +0000 Subject: [PATCH] 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) {