Merge pull request #4148 from nrq6993-a11y/stream/vc-feat-g1-star-radii-measure-label

G1: Star tool shows inner and outer radii in measurement labels
This commit is contained in:
BFlat
2026-10-26 09:13:01 +07:00
committed by GitHub
2 changed files with 87 additions and 8 deletions
+4
View File
@@ -392,6 +392,10 @@ impl ToolContext<'_> {
pub fn size_label(&self, w: f64, h: f64) -> String {
format!("W: {}\nH: {}", self.len(w), self.len(h))
}
/// Star tool drag: outer (R1) and inner (R2) radii from the centre to the pointer.
pub fn star_radii_label(&self, outer: f64, inner: f64) -> String {
format!("R1: {}\nR2: {}", self.len(outer), self.len(inner))
}
/// A move label: `dX: …` over `dY: …`.
pub fn offset_label(&self, dx: f64, dy: f64) -> String {
format!("dX: {}\ndY: {}", self.len(dx), self.len(dy))
+83 -8
View File
@@ -308,15 +308,25 @@ impl Tool for ShapeTool {
cx.segment_measure_label(s, self.last)
} else {
// A box's size as drawn (from its centre with Alt, square with Shift).
let (w, h) = match self.id {
"polygon" | "star" => ((self.last - s).x.abs(), (self.last - s).y.abs()),
_ => {
// Along the Constrain Angle's axes, as the box is drawn (`command_at`).
let (r, _) = turned_box(s, self.last, self.mods, cx.constrain_angle);
(r.width(), r.height())
match self.id {
"star" => {
let outer = (self.last - s).hypot().max(0.01);
// The same ratio `command_at` draws with, so the label matches the preview
// (Ctrl/Cmd's held radius is already in `star_ratio`, clamped; Alt overrides it).
let ratio = if self.mods.alt { straight_ratio(self.points).unwrap_or(self.star_ratio) } else { self.star_ratio };
cx.star_radii_label(outer, outer * ratio)
}
};
cx.size_label(w, h)
_ => {
let (w, h) = match self.id {
"polygon" => ((self.last - s).x.abs(), (self.last - s).y.abs()),
_ => {
let (r, _) = turned_box(s, self.last, self.mods, cx.constrain_angle);
(r.width(), r.height())
}
};
cx.size_label(w, h)
}
}
};
o.push(Overlay::Measure { p, text });
}
@@ -396,6 +406,71 @@ mod tests {
assert!(matches!(a.last(), Some(Action::Preview(_, v)) if v["cx"] == 60.0 && v["cy"] == 60.0), "{a:?}");
}
#[test]
fn star_drag_measure_label_shows_outer_and_inner_radii() {
let (d, _) = doc_with_rect();
let s = Selection::default();
let p = paint();
let cx = ToolContext { measurement_labels: true, smart_guides: false, ..cx(&d, &s, &p) };
let mut t = ShapeTool::new("star");
t.star_ratio = 0.5;
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 10.0, 10.0));
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 50.0, 10.0));
let want = cx.star_radii_label(40.0, 20.0);
let label = t
.overlays(&cx)
.into_iter()
.find_map(|o| match o {
Overlay::Measure { text, .. } => Some(text),
_ => None,
})
.unwrap_or_default();
assert_eq!(label, want, "{label}");
}
/// The star label shows the radii the preview draws, also with Ctrl/Cmd + Alt and when a
/// Ctrl/Cmd drag shrinks the outer radius below the held inner one (the ratio is clamped).
#[test]
fn star_measure_label_matches_the_preview_with_modifiers_and_shrink() {
let (d, _) = doc_with_rect();
let s = Selection::default();
let p = paint();
let cx = ToolContext { measurement_labels: true, smart_guides: false, ..cx(&d, &s, &p) };
let label = |t: &ShapeTool| {
t.overlays(&cx)
.into_iter()
.find_map(|o| match o {
Overlay::Measure { text, .. } => Some(text),
_ => None,
})
.unwrap_or_default()
};
let radii = |a: &[Action]| match a.last() {
Some(Action::Preview(_, v)) => (v["radius1"].as_f64().unwrap(), v["radius2"].as_f64().unwrap()),
other => panic!("{other:?}"),
};
let cmd = Mods { cmd: true, ..Default::default() };
let cmd_alt = Mods { cmd: true, alt: true, ..Default::default() };
let mut t = ShapeTool::new("star");
t.star_ratio = 0.5;
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 10.0, 10.0));
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 50.0, 10.0));
// Ctrl/Cmd holds the 20 inner radius while the outer one grows.
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 70.0, 10.0).with_mods(cmd));
let (r1, r2) = radii(&a);
assert_eq!(label(&t), cx.star_radii_label(r1, r2));
// Ctrl/Cmd + Alt: the preview straightens the shoulders, and so does the label.
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 70.0, 10.0).with_mods(cmd_alt));
let (r1, r2) = radii(&a);
assert!((r2 - 20.0).abs() > 1e-6, "Alt should override the held radius: {r2}");
assert_eq!(label(&t), cx.star_radii_label(r1, r2));
// Shrinking below the held inner radius clamps the ratio; the label follows the preview.
let a = t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 20.0, 10.0).with_mods(cmd));
let (r1, r2) = radii(&a);
assert!(r2 <= r1 + 1e-9, "{r1} {r2}");
assert_eq!(label(&t), cx.star_radii_label(r1, r2));
}
/// During a star drag, Ctrl/Cmd keeps the inner radius while the outer one follows the pointer
/// (the new ratio stays once the key is let go), and Alt keeps the shoulders straight (#812).
#[test]