diff --git a/src/app/mod.rs b/src/app/mod.rs index 6c14dcece..0e9b2276f 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -453,6 +453,9 @@ pub enum Message { /// User picked an item in the multi-functional grip popup menu — /// the index is into `grip_popup.items`. GripMenuPick(usize), + /// Timer pulse while the cursor is dwelling on a grip; drives the + /// dwell-to-popup transition without requiring further mouse motion. + GripDwellTick, WindowResized(f32, f32), /// Enter pressed globally — finalises the active command (no text-input involvement). CommandFinalize, diff --git a/src/app/update.rs b/src/app/update.rs index e84028d03..39303586b 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -3067,6 +3067,21 @@ impl OpenCADStudio { Task::none() } + Message::GripDwellTick => { + let i = self.active_tab; + // Reuse the move-time logic — `p` is the last cursor + // position the viewport saw, which is also what the + // hover state was last set with. + let p = self.tabs[i] + .scene + .selection + .borrow() + .last_move_pos + .unwrap_or(self.cursor_pos); + self.update_grip_hover(i, p); + Task::none() + } + Message::GripMenuPick(idx) => { let i = self.active_tab; let Some(popup) = self.grip_popup.take() else { diff --git a/src/app/view.rs b/src/app/view.rs index aee003b92..a0b83877f 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -932,9 +932,20 @@ impl OpenCADStudio { } else { Subscription::none() }; + // While the cursor sits over a grip, request animation frames + // so the multi-functional popup opens even when the user keeps + // the mouse perfectly still — `ViewportMove` alone would never + // fire again. Auto-stops once the hover clears or the popup is + // already open. + let grip_dwell = if self.grip_hover.is_some() && self.grip_popup.is_none() { + window::frames().map(|_| Message::GripDwellTick) + } else { + Subscription::none() + }; iced::Subscription::batch([ frames, history_tick, + grip_dwell, event::listen_with(|ev, status, win_id| { use iced::event::Status; match ev {