feat(grip): multi-functional hover popup menu

Phase 2 of the grip overhaul: when the cursor dwells on a selected
entity's grip the overlay opens a popup menu with entity-specific
options (Stretch / Add Vertex / Remove Vertex / Convert to
Arc-Line / …). Picking an item dispatches `apply_grip_menu`; the
default `Stretch` entry is a no-op that lets the user click the
grip to start the normal drag.

- `GripMenuItem` + `GripMenuAction` (full vocabulary covering
  stretch, add/remove vertex, convert to arc/line, reverse arrows,
  text-position variants, hatch parameters, tangent direction).
- `Grippable` trait gains `grip_menu` + `apply_grip_menu` with
  trait-default `Stretch`-only impls so every existing entity gets
  the popup for free.
- `EntityTypeOps` dispatches both methods across the same entity
  list as the existing grip / property machinery.
- `OpenCADStudio` carries `grip_hover` (dwell tracker) +
  `grip_popup` (open menu). `update_grip_hover` runs from
  `ViewportMove`: hit-tests grips, refreshes the dwell timer, and
  opens the popup after 600 ms. Cursor drift past 80 px or a grip
  click dismisses it.
- `view.rs` paints the popup as an absolute-positioned column of
  buttons at the grip's screen anchor; each button publishes
  `Message::GripMenuPick(idx)`.
- `LwPolyline` overrides `Grippable` manually (skips the standard
  macro) so its vertex grip exposes Add/Remove Vertex and its
  segment-midpoint grip exposes Add Vertex + Convert to Arc/Line.
  `apply_grip_menu` performs the edits: vertex insertion at the
  chord midpoint inheriting the previous bulge, vertex removal,
  and bulge toggle for the arc/line conversion.
This commit is contained in:
Hakan Seven committed 2026-05-29 09:30:54 +03:00
1 parent 5755c2e130
commit 6c1462932d
6 files changed
+443 -2

No files matched your search

+33
View File
@@ -23,6 +23,26 @@ pub struct TileDrag {
pub orient: TileEdgeOrient,
pub last_applied: f32,
}
/// Cursor dwelling over a grip. When `started.elapsed() >=` the popup
/// threshold the multi-functional menu opens (`grip_popup`).
#[derive(Clone, Debug)]
pub struct GripHover {
pub handle: acadrust::Handle,
pub grip_id: usize,
pub screen: iced::Point,
pub started: std::time::Instant,
}
/// Open multi-functional-grip popup state.
#[derive(Clone, Debug)]
pub struct GripPopup {
pub handle: acadrust::Handle,
pub grip_id: usize,
pub anchor: iced::Point,
pub items: Vec<crate::scene::object::GripMenuItem>,
pub selected: usize,
}
use crate::snap::Snapper;
use crate::ui::{AppMenu, CommandLine, Ribbon, StatusBar};
use acadrust::types::{Color as AcadColor, LineWeight};
@@ -94,6 +114,14 @@ pub(super) struct OpenCADStudio {
/// reverting to the command-default when `has_base` flips. Cleared
/// on point commit / command start. See #35.
dyn_user_reshaped: bool,
/// Grip the cursor is currently dwelling on. Set when the cursor
/// stops within `GRIP_THRESHOLD_PX` of a grip; cleared when it
/// drifts away. The instant lets `ViewportMove` detect when the
/// dwell crosses the popup-open threshold.
grip_hover: Option<GripHover>,
/// Open multi-functional grip popup. Persists across mouse moves
/// until dismissed (click outside, ESC, cursor leaves the grip).
grip_popup: Option<GripPopup>,
/// Show the UCS icon in the bottom-left corner of model space (UCSICON).
show_ucs_icon: bool,
/// Whether the ViewCube 3D gizmo is visible in model space (NAVVCUBE).
@@ -422,6 +450,9 @@ pub enum Message {
ViewportScroll(mouse::ScrollDelta),
ViewportExit,
ViewCubeSnap(CubeRegion),
/// User picked an item in the multi-functional grip popup menu —
/// the index is into `grip_popup.items`.
GripMenuPick(usize),
WindowResized(f32, f32),
/// Enter pressed globally — finalises the active command (no text-input involvement).
CommandFinalize,
@@ -770,6 +801,8 @@ impl OpenCADStudio {
awaiting_vports: false,
tile_drag: None,
dyn_user_reshaped: false,
grip_hover: None,
grip_popup: None,
show_ucs_icon: true,
show_viewcube: true,
show_properties: true,