fix(visual): exclude Extension and intersection guides from SNAPMARKERSIZE transform

This commit is contained in:
gianlucafiore committed 2026-10-16 13:57:47 -03:00
1 parent 903501d7b6
commit 363534bb5c
1 file changed
+72 -76
+72 -76
View File
@@ -1761,6 +1761,73 @@ impl canvas::Program<Message> for SelectionCanvas {
style: canvas::Style::Solid(marker),
..Default::default()
};
// Construction guides are canvas-space geometry, NOT snap glyphs.
// They must be drawn before the SNAPMARKERSIZE transform below.
// Scaling a guide around the moving snap position makes a fixed
// acquired endpoint visibly slide as the cursor moves.
let extension_ray = self.snap_ext_base.and_then(|base| {
let acquired = self.snap_ext_dir.or_else(|| {
self.otrack_lines.iter().find_map(|&(origin, tip)| {
let offset = (origin.x - base.x).hypot(origin.y - base.y);
(offset <= 2.0)
.then_some(Point::new(tip.x - origin.x, tip.y - origin.y))
})
});
extension_guide_tip(base, sp, acquired)
.map(|(unit, tip)| (base, unit, tip))
});
let guide_dash = canvas::Stroke {
line_dash: canvas::LineDash {
segments: &[4.0, 4.0],
offset: 0,
},
..canvas::Stroke::default().with_color(marker).with_width(1.0)
};
match snap_type {
SnapType::Extension => {
if let Some((base, _, tip)) = extension_ray {
if let Some((a, b)) = clip_seg(base, tip, bounds) {
frame.stroke(&canvas::Path::line(a, b), guide_dash.clone());
}
#[cfg(not(target_arch = "wasm32"))]
if std::env::var_os("OCS_EXT_DIAG").is_some() {
frame.stroke(
&canvas::Path::line(base, tip),
canvas::Stroke::default()
.with_color(Color::from_rgb(1.0, 0.0, 1.0))
.with_width(1.0),
);
frame.fill(
&canvas::Path::circle(base, 5.0),
Color::from_rgb(0.0, 1.0, 0.0),
);
}
}
}
SnapType::Intersection => {
// Extended-intersection guide origins must also remain
// fixed when SNAPMARKERSIZE changes.
for base in [self.snap_ext_base, self.snap_ext_base2]
.into_iter()
.flatten()
{
let dx = sp.x - base.x;
let dy = sp.y - base.y;
let len = dx.hypot(dy);
if len > 1e-3 {
let tip = Point::new(
sp.x + dx / len * 18.0,
sp.y + dy / len * 18.0,
);
if let Some((a, b)) = clip_seg(base, tip, bounds) {
frame.stroke(&canvas::Path::line(a, b), guide_dash.clone());
}
}
}
}
_ => {}
}
// SNAPMARKERSIZE scales the glyphs drawn for the default 5 px.
let scale = self.crosshair.snap_size.clamp(1.0, 20.0) / 5.0;
frame.with_save(|frame| {
@@ -1858,35 +1925,8 @@ impl canvas::Program<Message> for SelectionCanvas {
frame.stroke(&path, stroke);
}
SnapType::Intersection => {
// An extended intersection sets one or two extension bases;
// draw a dashed guide from each endpoint through the crossing
// so both contributing extension paths stay visible. A real
// on-segment crossing carries no bases, so nothing draws here.
// (#247, #259)
for base in [self.snap_ext_base, self.snap_ext_base2]
.into_iter()
.flatten()
{
let dx = sp.x - base.x;
let dy = sp.y - base.y;
let len = (dx * dx + dy * dy).sqrt();
if len > 1e-3 {
let dash = canvas::Stroke {
line_dash: canvas::LineDash {
segments: &[4.0, 4.0],
offset: 0,
},
..canvas::Stroke::default().with_color(marker).with_width(1.0)
};
let tip =
Point::new(sp.x + dx / len * 18.0, sp.y + dy / len * 18.0);
// `base` is a projected entity endpoint: clip before
// stroking (see `clip_seg`).
if let Some((a, b)) = clip_seg(base, tip, bounds) {
frame.stroke(&canvas::Path::line(a, b), dash);
}
}
}
// The guide is drawn unscaled above; only the X glyph
// belongs inside the SNAPMARKERSIZE transform.
let r = 5.0_f32;
let p1 = canvas::Path::new(|b| {
b.move_to(Point::new(sp.x - r, sp.y - r));
@@ -1974,53 +2014,9 @@ impl canvas::Program<Message> for SelectionCanvas {
frame.stroke(&path, stroke);
}
SnapType::Extension => {
// The engine provides the exact acquired world ray.
// Its long-baseline projected direction is passed by the
// view and remains stable even with OTRACK (F11) off.
let dir = self.snap_ext_base.and_then(|base| {
let acquired = self.snap_ext_dir.or_else(|| {
self.otrack_lines.iter().find_map(|&(origin, tip)| {
let offset = (origin.x - base.x).hypot(origin.y - base.y);
(offset <= 2.0).then_some(Point::new(
tip.x - origin.x,
tip.y - origin.y,
))
})
});
extension_guide_tip(base, sp, acquired)
.map(|(unit, tip)| (base, unit, tip))
});
if let Some((base, _, tip)) = dir {
let dash = canvas::Stroke {
line_dash: canvas::LineDash {
segments: &[4.0, 4.0],
offset: 0,
},
..canvas::Stroke::default().with_color(marker).with_width(1.0)
};
if let Some((a, b)) = clip_seg(base, tip, bounds) {
frame.stroke(&canvas::Path::line(a, b), dash);
}
}
// Diagnostic only: green dot = the actual guide
// origin given to the canvas; magenta = complete solid
// ray. This distinguishes a moving base from a dashed
// stroke/rendering issue without changing normal mode.
#[cfg(not(target_arch = "wasm32"))]
if std::env::var_os("OCS_EXT_DIAG").is_some() {
if let Some((base, _, tip)) = dir {
frame.stroke(
&canvas::Path::line(base, tip),
canvas::Stroke::default()
.with_color(Color::from_rgb(1.0, 0.0, 1.0))
.with_width(1.0),
);
frame.fill(
&canvas::Path::circle(base, 5.0),
Color::from_rgb(0.0, 1.0, 0.0),
);
}
}
// Only the three-dot marker is scaled. The actual
// guide and diagnostic dot are drawn in canvas space.
let dir = extension_ray;
// Three dots stay aligned with the *same* acquired ray
// used by the dashed guide (rather than cursor jitter).
let u = dir.map(|(_, u, _)| u).unwrap_or(Point::new(1.0, 0.0));