feat(grip): value-prompt actions Lengthen/Radius/Arc Length

Lengthen / Radius / Arc Length need a number after the pick. Stash
`grip_pending` on the picked action, push prompt to command line,
route next `CommandSubmit` parse into `apply_grip_menu_value`.
Escape cancels.

- `Grippable::grip_menu_value_prompt` returns label when action
  wants value; `apply_grip_menu_value` runs the edit. Dispatched
  via `EntityTypeOps`.
- Line: Lengthen extends either endpoint along line direction.
- Arc: Radius sets radius; Arc Length redrives end angle from
  `value / radius`; Lengthen extends start / end angle the same
  way.
This commit is contained in:
Hakan Seven committed 2026-05-29 14:58:35 +03:00
1 parent 86ca1b7033
commit 559cd76369
5 files changed
+243 -5

No files matched your search

+14
View File
@@ -43,6 +43,18 @@ pub struct GripPopup {
pub items: Vec<crate::scene::object::GripMenuItem>,
pub selected: usize,
}
/// Pending follow-up value for grip-menu actions that need a number
/// (Lengthen / Radius / Arc Length / Rotate Text). The next number
/// typed in the command line is parsed and routed into
/// `apply_grip_menu_value` for `(handle, grip_id, action)`.
#[derive(Clone, Debug)]
pub struct GripPendingValue {
pub handle: acadrust::Handle,
pub grip_id: usize,
pub action: crate::scene::object::GripMenuAction,
pub label: &'static str,
}
use crate::snap::Snapper;
use crate::ui::{AppMenu, CommandLine, Ribbon, StatusBar};
use acadrust::types::{Color as AcadColor, LineWeight};
@@ -122,6 +134,7 @@ pub(super) struct OpenCADStudio {
/// Open multi-functional grip popup. Persists across mouse moves
/// until dismissed (click outside, ESC, cursor leaves the grip).
grip_popup: Option<GripPopup>,
grip_pending: Option<GripPendingValue>,
/// 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).
@@ -806,6 +819,7 @@ impl OpenCADStudio {
dyn_user_reshaped: false,
grip_hover: None,
grip_popup: None,
grip_pending: None,
show_ucs_icon: true,
show_viewcube: true,
show_properties: true,
+57 -2
View File
@@ -1019,6 +1019,39 @@ impl OpenCADStudio {
// dropdown so the dispatched command's new prompt is
// immediately visible on the overlay.
self.command_line.close_history();
// Grip-menu value prompt — consume the typed number and
// route it through `apply_grip_menu_value`.
if let Some(pending) = self.grip_pending.take() {
let raw = self.command_line.input.trim().to_string();
self.command_line.input.clear();
let Ok(v) = raw.parse::<f64>() else {
self.command_line.push_error(&format!(
"{}: expected a number, got \"{raw}\"",
pending.label
));
return Task::none();
};
let i = self.active_tab;
use crate::entities::traits::EntityTypeOps;
self.push_undo_snapshot(i, pending.label);
if let Some(entity) = self
.tabs[i]
.scene
.document
.get_entity_mut(pending.handle)
{
entity.apply_grip_menu_value(
pending.grip_id,
pending.action,
v,
);
}
self.tabs[i].scene.bump_geometry();
self.tabs[i].dirty = true;
self.refresh_selected_grips();
self.refresh_properties();
return Task::none();
}
// Interactive VPORTS: the entry after a bare `VPORTS` is the
// tiled configuration. Empty input defaults to SINGLE.
if self.awaiting_vports {
@@ -1203,6 +1236,10 @@ impl OpenCADStudio {
self.grip_hover = None;
return Task::none();
}
if self.grip_pending.take().is_some() {
self.command_line.input.clear();
return Task::none();
}
// Cancel layout rename / context menus first, then fall through.
let i_e = self.active_tab;
{
@@ -3143,10 +3180,28 @@ impl OpenCADStudio {
use crate::entities::traits::EntityTypeOps;
use crate::scene::object::GripMenuAction;
if matches!(item.action, GripMenuAction::Stretch) {
// Default action: nothing — user can click the grip
// to start the normal drag.
return Task::none();
}
// Actions that need a follow-up number stash a pending
// state + prompt; the next typed value drives
// `apply_grip_menu_value`.
let prompt = self
.tabs[i]
.scene
.document
.get_entity(popup.handle)
.and_then(|e| e.grip_menu_value_prompt(popup.grip_id, item.action));
if let Some(label) = prompt {
self.grip_pending = Some(super::GripPendingValue {
handle: popup.handle,
grip_id: popup.grip_id,
action: item.action,
label,
});
self.command_line.push_info(&format!("{label}:"));
return self.focus_cmd_input();
}
// One-shot action — apply immediately.
self.push_undo_snapshot(i, item.label);
if let Some(entity) = self
.tabs[i]