diff --git a/crates/engine/src/cmd/objects_on_path.rs b/crates/engine/src/cmd/objects_on_path.rs index 4829d770c..eebdd3e04 100644 --- a/crates/engine/src/cmd/objects_on_path.rs +++ b/crates/engine/src/cmd/objects_on_path.rs @@ -204,7 +204,7 @@ fn attach(s: &mut Session, p: &Value) -> Result { // Without them in `p`, the tool's options (Objects on Path Tool Options) give Pivot and Rotate. let saved = s.tool_options_of(TOOL); let tool_pivot = saved.get("pivot").and_then(Value::as_u64).filter(|n| *n <= 8).map_or(CENTER, |n| n as u8); - let tool_rotate = saved.get("rotate").and_then(Value::as_f64).filter(|r| r.is_finite()).unwrap_or(0.0); + let tool_rotate = saved.get("rotate").and_then(Value::as_f64).filter(|r| r.is_finite()).map_or(0.0, |r| r.clamp(-360.0, 360.0)); let (pivot, rotate) = pivot_rotate(p, C, tool_pivot, tool_rotate)?; let roots = match p.get("ids") { Some(_) => roots_of(&s.doc()?.doc, checked_ids_param(s, p, "ids", C)?), diff --git a/crates/tools/src/objects_on_path.rs b/crates/tools/src/objects_on_path.rs index d5be19525..f08dc9313 100644 --- a/crates/tools/src/objects_on_path.rs +++ b/crates/tools/src/objects_on_path.rs @@ -98,8 +98,8 @@ impl Tool for ObjectsOnPathTool { json!({ "pivot": self.pivot, "rotate": self.rotate }) } - /// `pivot` takes a whole number from 0 to 8 and `rotate` a finite number; other values are - /// ignored. + /// `pivot` takes a whole number from 0 to 8 and `rotate` a finite number, clamped to -360 to + /// 360; other values are ignored. fn set_option(&mut self, key: &str, value: &Value) { match key { "pivot" => { @@ -109,7 +109,7 @@ impl Tool for ObjectsOnPathTool { } "rotate" => { if let Some(r) = value.as_f64().filter(|r| r.is_finite()) { - self.rotate = r; + self.rotate = r.clamp(-360.0, 360.0); } } _ => {} @@ -180,6 +180,12 @@ mod tests { t.set_option("pivot", &json!(9)); t.set_option("rotate", &json!("x")); assert_eq!(t.options(), json!({ "pivot": 0, "rotate": 45.5 })); + // Rotate is kept within the dialog's -360 to 360. + t.set_option("rotate", &json!(450)); + assert_eq!(t.options()["rotate"], 360.0); + t.set_option("rotate", &json!(-720.5)); + assert_eq!(t.options()["rotate"], -360.0); + t.set_option("rotate", &json!(45.5)); t.pointer(&cx, &ev(PointerKind::Down, 200.0, 300.0)); let acts = t.pointer(&cx, &ev(PointerKind::Up, 200.0, 300.0)); assert!(matches!(acts.as_slice(), [Action::Exec(_, p)] if p["pivot"] == 0 && p["rotate"] == 45.5), "{acts:?}");