feat(grip): popup menus for polylines, spline, mline, hatch, leaders, text

Vocabulary added: AddFitPoint, RemoveFitPoint, Refit, RefineVertices,
TangentDirection, MoveWithText, StackText, UnstackText.

Polyline/Polyline2D/Polyline3D: vertex Add/Remove. MLine: same.
Spline: CV Add/Remove plus Refine Vertices (chord-midpoint insertion,
weights kept in sync, knots cleared so to_truck rebuilds uniform vec).
Hatch: centroid Origin/Angle/Scale (Angle and Scale prompt for value).
Leader: vertex Add/Remove (arrow + centroid stay Stretch-only).
MultiLeader: vertex Add/Remove Leader; MText location Move-with-Leader /
Move-Independent (menu live, structural edits stubbed). Text/MText:
Rotate prompts for degrees and sets rotation.

Spline, Leader, MultiLeader switched from impl_entity_basics! to manual
trait impls so grip_menu can be overridden.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-05-29 15:14:43 +03:00
commit 4b4fe5ca6d
9 changed files with 580 additions and 3 deletions

View file

@ -315,6 +315,52 @@ impl Grippable for Text {
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
apply_grip(self, grip_id, apply);
}
fn grip_menu(
&self,
_grip_id: usize,
) -> Vec<crate::scene::object::GripMenuItem> {
use crate::scene::object::{GripMenuAction, GripMenuItem};
vec![
GripMenuItem { label: "Stretch", action: GripMenuAction::Stretch },
GripMenuItem { label: "Move with Text", action: GripMenuAction::MoveWithText },
GripMenuItem { label: "Rotate", action: GripMenuAction::RotateText },
]
}
fn apply_grip_menu(
&mut self,
_grip_id: usize,
_action: crate::scene::object::GripMenuAction,
) {
// Move-with-Text falls through to Stretch (single grip moves
// the whole text); Rotate needs a follow-up angle handled by
// `apply_grip_menu_value`.
}
fn grip_menu_value_prompt(
&self,
_grip_id: usize,
action: crate::scene::object::GripMenuAction,
) -> Option<&'static str> {
use crate::scene::object::GripMenuAction as A;
match action {
A::RotateText => Some("Rotation (deg)"),
_ => None,
}
}
fn apply_grip_menu_value(
&mut self,
_grip_id: usize,
action: crate::scene::object::GripMenuAction,
value: f64,
) {
use crate::scene::object::GripMenuAction as A;
if matches!(action, A::RotateText) {
self.rotation = value.to_radians();
}
}
}
impl PropertyEditable for Text {