feat(draw): LINE deferred tangent — a line tangent to two circles

A line whose FIRST point is a Tangent snap on a circle can't be fixed
until its direction is known, and a line tangent to TWO circles is their
common tangent — neither is resolvable at a single pick. A plain LINE
dropped the tangent object, so it drew a wrong line (TAN-TAN between two
circles).

Add an opt-in on_point_with_tangent hook (default ignores the tangent, so
every other command is byte-for-byte unchanged) and route the running
Tangent object through the point commit. LINE defers a first-point circle
tangent and resolves it on the next pick: against a second circle tangent
it picks the common tangent of the two circles (up to four — chosen by
the two cursor hits, so hovering selects inner/outer + side); against a
plain point it draws the tangent from that point to the circle.
last_point syncs to the resolved anchor. Common-tangent and point-tangent
math is f64 with perpendicularity unit tests.

Refs #274

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-05 13:06:36 +03:00
1 parent 2da21146c4
commit e366e47b05
3 files changed
+223 -4

No files matched your search

+22 -2
View File
@@ -2020,10 +2020,30 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
self.dyn_user_reshaped = false;
self.sync_dyn_fields();
self.reset_tracking_after_point();
self.tabs[i]
// A running Tangent snap may carry a tangent object; a
// command can consume it to resolve a deferred tangent
// (LINE tangent to two circles, which needs both). When
// it does, sync last_point to the command's resolved
// anchor since it replaced the picked coordinate.
let handled = self.tabs[i]
.active_cmd
.as_mut()
.map(|c| c.on_point(world_pt))
.and_then(|c| c.on_point_with_tangent(world_pt, tangent_obj_at_click));
if handled.is_some() {
if let Some(a) = self.tabs[i]
.active_cmd
.as_ref()
.and_then(|c| c.resolved_anchor())
{
self.last_point = Some(a.as_vec3());
}
handled
} else {
self.tabs[i]
.active_cmd
.as_mut()
.map(|c| c.on_point(world_pt))
}
};
if let Some(r) = result {