From 7a93ba82b35d4481fe80e7b3f4b0186e3744255d Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Tue, 21 Jul 2026 23:58:05 +0300 Subject: [PATCH] feat(dyn): locked input fields constrain the preview and the click (#356) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A typed dynamic-input value (TAB-locked angle or distance) now shapes the rubber-band: the cursor point is routed through dyn_resolve_point — the same resolution Enter commits — in the mouse-move preview and the click commit, so a locked 30° pins the line's direction while the free field keeps tracking the cursor. The lock wins over osnap/ortho/polar. TAB (and the comma advance) reshape the preview immediately instead of waiting for the next mouse move. Co-Authored-By: Claude Fable 5 --- src/app/update/command.rs | 1 + src/app/update/dynamic.rs | 9 +++++++++ src/app/update/mod.rs | 3 +++ src/app/update/viewport.rs | 26 ++++++++++++++++++++++++++ 4 files changed, 39 insertions(+) diff --git a/src/app/update/command.rs b/src/app/update/command.rs index f1f2ab6e7..b88e11d9a 100644 --- a/src/app/update/command.rs +++ b/src/app/update/command.rs @@ -83,6 +83,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { if s == "," && self.dyn_input && !self.tabs[i].dyn_fields.is_empty() { self.dyn_comma_advance(); self.command_line.autocomplete_cursor = None; + self.refresh_active_cmd_preview(i); return self.focus_cmd_input(); } // While dynamic input is showing fields, numeric and diff --git a/src/app/update/dynamic.rs b/src/app/update/dynamic.rs index e8eebaed4..0be8aa4fc 100644 --- a/src/app/update/dynamic.rs +++ b/src/app/update/dynamic.rs @@ -581,6 +581,15 @@ impl OpenCADStudio { if self.tabs[i].active_cmd.is_none() { return; } + // Re-resolve through the locked dyn fields first so a freshly typed + // value reshapes the preview immediately, without waiting for the + // next mouse move (#356). Idempotent for the polar/cartesian arms, so + // feeding the already-resolved cursor back in is stable. + if self.tabs[i].dyn_fields.iter().any(|f| f.buffer.is_some()) { + if let Some(r) = self.dyn_resolve_point() { + self.tabs[i].last_cursor_world = r; + } + } let cur = self.tabs[i].last_cursor_world; let previews = self.tabs[i] .active_cmd diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index afd950489..81ed06887 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -799,6 +799,9 @@ impl OpenCADStudio { let n = self.tabs[i].dyn_fields.len(); if n > 0 { self.tabs[i].dyn_active = (self.tabs[i].dyn_active + 1) % n; + // TAB locks the value just typed — reshape the rubber-band + // to the constrained point now (#356). + self.refresh_active_cmd_preview(i); } self.focus_cmd_input() } diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index 526e96846..1846a0886 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -1060,6 +1060,21 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { } pt }; + // Dynamic-input locked fields constrain the preview point + // (#356): a typed angle pins the direction, a typed + // distance pins the radius — the same resolution the Enter + // commit uses, so preview and commit agree. The lock wins + // over osnap/ortho/polar. + let effective = { + let locked = self.tabs[i].active_cmd.is_some() + && self.tabs[i].dyn_fields.iter().any(|f| f.buffer.is_some()); + if locked { + self.tabs[i].last_cursor_world = effective; + self.dyn_resolve_point().unwrap_or(effective) + } else { + effective + } + }; self.tabs[i].last_cursor_world = effective; self.tabs[i].last_cursor_screen = p_full; // Project the step anchor (an explicit `dyn_anchor` or the @@ -1970,6 +1985,17 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { } } } + // A click while dynamic-input fields hold typed values + // commits the CONSTRAINED point — the same resolution + // the preview shows and Enter would commit (#356). + if self.tabs[i].active_cmd.is_some() + && self.tabs[i].dyn_fields.iter().any(|f| f.buffer.is_some()) + { + self.tabs[i].last_cursor_world = pt; + if let Some(r) = self.dyn_resolve_point() { + pt = r; + } + } pt };