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:
parent
5755c2e130
commit
6c1462932d
6 changed files with 443 additions and 2 deletions
|
|
@ -684,6 +684,69 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
|
||||
// Multi-functional grip popup (Phase 2). Floating column of
|
||||
// menu items at the grip's screen position, with the selected
|
||||
// item highlighted. Each item dispatches `GripMenuPick`.
|
||||
if let Some(popup) = self.grip_popup.as_ref() {
|
||||
if !tab.is_start {
|
||||
let anchor_x = (popup.anchor.x + 12.0).max(0.0);
|
||||
let anchor_y = (popup.anchor.y + 12.0).max(0.0);
|
||||
let mut col = column![].spacing(0);
|
||||
for (idx, item) in popup.items.iter().enumerate() {
|
||||
let is_sel = idx == popup.selected;
|
||||
let label = item.label;
|
||||
let btn = button(text(label).size(12).color(Color::WHITE))
|
||||
.on_press(Message::GripMenuPick(idx))
|
||||
.padding([4, 10])
|
||||
.style(move |_: &Theme, status| iced::widget::button::Style {
|
||||
background: Some(Background::Color(match (is_sel, status) {
|
||||
(true, _) => Color {
|
||||
r: 0.20,
|
||||
g: 0.45,
|
||||
b: 0.95,
|
||||
a: 0.95,
|
||||
},
|
||||
(_, iced::widget::button::Status::Hovered) => Color {
|
||||
r: 0.20,
|
||||
g: 0.20,
|
||||
b: 0.20,
|
||||
a: 0.95,
|
||||
},
|
||||
_ => Color {
|
||||
r: 0.10,
|
||||
g: 0.10,
|
||||
b: 0.10,
|
||||
a: 0.92,
|
||||
},
|
||||
})),
|
||||
border: Border {
|
||||
color: Color {
|
||||
r: 0.35,
|
||||
g: 0.35,
|
||||
b: 0.35,
|
||||
a: 1.0,
|
||||
},
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
text_color: Color::WHITE,
|
||||
..Default::default()
|
||||
});
|
||||
col = col.push(iced::widget::opaque(btn));
|
||||
}
|
||||
let popup_layer = iced::widget::column![
|
||||
Space::new().height(iced::Length::Fixed(anchor_y)),
|
||||
iced::widget::row![
|
||||
Space::new().width(iced::Length::Fixed(anchor_x)),
|
||||
col,
|
||||
],
|
||||
]
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
viewport_stack = viewport_stack.push(popup_layer);
|
||||
}
|
||||
}
|
||||
|
||||
// Properties / layers panels carry no useful state on the Start tab.
|
||||
// Replace the properties panel with a Recent Documents list there.
|
||||
let properties_el: Element<'_, Message> = if tab.is_start {
|
||||
|
|
|
|||
Loading…
Reference in a new issue