fix(otrack): add 250ms dwell timeout before acquiring a tracking point
A tracking point was created as soon as a snap point was highlighted, so moving the mouse across the view spawned accidental tracking points. Acquisition now requires the cursor to rest near the snap point for 250ms (wall-clock) rather than a fixed number of move events. Closes #110 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e71ca80ed4
commit
ca8d249173
2 changed files with 33 additions and 13 deletions
|
|
@ -2481,8 +2481,12 @@ impl OpenCADStudio {
|
||||||
// distance can place a point along it — issue #69).
|
// distance can place a point along it — issue #69).
|
||||||
let otrack_hit = {
|
let otrack_hit = {
|
||||||
let snap_world = self.tabs[i].snap_result.map(|s| s.world);
|
let snap_world = self.tabs[i].snap_result.map(|s| s.world);
|
||||||
self.snapper
|
self.snapper.update_otrack_dwell(
|
||||||
.update_otrack_dwell(snap_world, view_proj, bounds);
|
snap_world,
|
||||||
|
view_proj,
|
||||||
|
bounds,
|
||||||
|
Instant::now(),
|
||||||
|
);
|
||||||
if self.tabs[i].snap_result.is_none() {
|
if self.tabs[i].snap_result.is_none() {
|
||||||
let step = if self.polar_mode {
|
let step = if self.polar_mode {
|
||||||
Some(self.polar_increment_deg)
|
Some(self.polar_increment_deg)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
//! Extension, Insertion, Perpendicular, Nearest, ApparentIntersection, Grid, Tangent
|
//! Extension, Insertion, Perpendicular, Nearest, ApparentIntersection, Grid, Tangent
|
||||||
|
|
||||||
use glam::{Mat4, Vec3};
|
use glam::{Mat4, Vec3};
|
||||||
|
use iced::time::Instant;
|
||||||
use iced::{Point, Rectangle};
|
use iced::{Point, Rectangle};
|
||||||
|
|
||||||
use crate::command::TangentObject;
|
use crate::command::TangentObject;
|
||||||
|
|
@ -98,8 +99,10 @@ pub struct Snapper {
|
||||||
pub tracking_points: Vec<Vec3>,
|
pub tracking_points: Vec<Vec3>,
|
||||||
/// Last snap world position (for dwell detection).
|
/// Last snap world position (for dwell detection).
|
||||||
pub last_snap_world: Option<Vec3>,
|
pub last_snap_world: Option<Vec3>,
|
||||||
/// How many consecutive moves the cursor has been near last_snap_world.
|
/// When the cursor first rested near `last_snap_world`.
|
||||||
pub dwell_count: u32,
|
pub dwell_since: Option<Instant>,
|
||||||
|
/// Whether the current dwell already acquired/removed a point (fire once).
|
||||||
|
pub dwell_acquired: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Snapper {
|
impl Default for Snapper {
|
||||||
|
|
@ -121,7 +124,8 @@ impl Default for Snapper {
|
||||||
otrack_enabled: false,
|
otrack_enabled: false,
|
||||||
tracking_points: Vec::new(),
|
tracking_points: Vec::new(),
|
||||||
last_snap_world: None,
|
last_snap_world: None,
|
||||||
dwell_count: 0,
|
dwell_since: None,
|
||||||
|
dwell_acquired: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -172,19 +176,25 @@ impl Snapper {
|
||||||
snap_world: Option<Vec3>,
|
snap_world: Option<Vec3>,
|
||||||
view_proj: glam::Mat4,
|
view_proj: glam::Mat4,
|
||||||
bounds: iced::Rectangle,
|
bounds: iced::Rectangle,
|
||||||
|
now: Instant,
|
||||||
) {
|
) {
|
||||||
if !self.otrack_enabled {
|
if !self.otrack_enabled {
|
||||||
self.dwell_count = 0;
|
|
||||||
self.last_snap_world = None;
|
self.last_snap_world = None;
|
||||||
|
self.dwell_since = None;
|
||||||
|
self.dwell_acquired = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const DWELL_THRESHOLD: u32 = 4;
|
// The cursor must rest near a snap point for this long before it is
|
||||||
|
// acquired, so that brushing past snap points while moving the mouse
|
||||||
|
// does not create accidental tracking points.
|
||||||
|
const DWELL_MS: u128 = 250;
|
||||||
const DWELL_PX: f32 = 8.0;
|
const DWELL_PX: f32 = 8.0;
|
||||||
|
|
||||||
match snap_world {
|
match snap_world {
|
||||||
None => {
|
None => {
|
||||||
self.dwell_count = 0;
|
|
||||||
self.last_snap_world = None;
|
self.last_snap_world = None;
|
||||||
|
self.dwell_since = None;
|
||||||
|
self.dwell_acquired = false;
|
||||||
}
|
}
|
||||||
Some(p) => {
|
Some(p) => {
|
||||||
// Convert to screen to measure pixel distance.
|
// Convert to screen to measure pixel distance.
|
||||||
|
|
@ -198,8 +208,11 @@ impl Snapper {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
if is_same {
|
if is_same {
|
||||||
self.dwell_count += 1;
|
let elapsed = self
|
||||||
if self.dwell_count == DWELL_THRESHOLD {
|
.dwell_since
|
||||||
|
.map_or(0, |t| now.duration_since(t).as_millis());
|
||||||
|
if !self.dwell_acquired && elapsed >= DWELL_MS {
|
||||||
|
self.dwell_acquired = true;
|
||||||
// Dwelling over an already-acquired point removes it;
|
// Dwelling over an already-acquired point removes it;
|
||||||
// otherwise acquire it (max 4 tracked points).
|
// otherwise acquire it (max 4 tracked points).
|
||||||
let existing = self.tracking_points.iter().position(|t| {
|
let existing = self.tracking_points.iter().position(|t| {
|
||||||
|
|
@ -219,8 +232,9 @@ impl Snapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.dwell_count = 1;
|
|
||||||
self.last_snap_world = Some(p);
|
self.last_snap_world = Some(p);
|
||||||
|
self.dwell_since = Some(now);
|
||||||
|
self.dwell_acquired = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -291,8 +305,9 @@ impl Snapper {
|
||||||
/// Clear all acquired tracking points (e.g. when command ends).
|
/// Clear all acquired tracking points (e.g. when command ends).
|
||||||
pub fn clear_tracking(&mut self) {
|
pub fn clear_tracking(&mut self) {
|
||||||
self.tracking_points.clear();
|
self.tracking_points.clear();
|
||||||
self.dwell_count = 0;
|
|
||||||
self.last_snap_world = None;
|
self.last_snap_world = None;
|
||||||
|
self.dwell_since = None;
|
||||||
|
self.dwell_acquired = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Only runs Tangent snap — used when a command needs object picks via tangent.
|
/// Only runs Tangent snap — used when a command needs object picks via tangent.
|
||||||
|
|
@ -317,7 +332,8 @@ impl Snapper {
|
||||||
otrack_enabled: false,
|
otrack_enabled: false,
|
||||||
tracking_points: Vec::new(),
|
tracking_points: Vec::new(),
|
||||||
last_snap_world: None,
|
last_snap_world: None,
|
||||||
dwell_count: 0,
|
dwell_since: None,
|
||||||
|
dwell_acquired: false,
|
||||||
};
|
};
|
||||||
tmp.snap(cursor_world, cursor_screen, wires, view_proj, bounds)
|
tmp.snap(cursor_world, cursor_screen, wires, view_proj, bounds)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue