feat(leader): dynamic MLEADER text placement, grips, and consistent arrows

Reworks MLEADER text/leader geometry and grip editing, and aligns LEADER
to MLEADER's conventions (kept as separate entities).

Text & landing (render path):
- Text is side-anchored on the leader-facing edge and reads outward; the
  side, alignment and the horizontal landing are recomputed every frame
  from the text grip's position relative to the leader, so moving the
  arrow or the text re-mirrors the whole layout and the landing stays
  attached to the text's near edge.
- Fixes #49: text was centred on the leader endpoint regardless of side.

Grips:
- Text-location grip plus an MTEXT-style triangle wrap-width grip at the
  box's far edge; dragging it sets the MText wrap width.
- Multifunction grip menu restored and made functional: leader-line
  vertices offer Add Leader / Remove Leader (Add grabs the new arrow so it
  follows the cursor — click places, Esc removes it); the text grip offers
  Move Independent (text only) and Move with Leader (translates the whole
  multileader via a move-all sentinel grip).

Arrows:
- LEADER and MLEADER arrowheads now render at the same size (text height),
  matching previews.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-03 16:17:29 +03:00
1 parent 6f64d8d0d9
commit 8b3d083789
6 files changed
+395 -162

No files matched your search

+2 -2
View File
@@ -1,6 +1,6 @@
// LEADER command
//
// Flow (matching the classic AutoCAD LEADER):
// Flow:
// 1. Click the arrowhead point, then one or more bend points.
// 2. Enter (≥2 points) places the leader line plus a linked, empty MText
// annotation at the landing, and opens the in-place MText editor so the
@@ -132,7 +132,7 @@ fn build_mtext(text: &str, pos: Vec3, height: f64, attach: AttachmentPoint) -> M
fn preview_wire(pts: &[Vec3]) -> WireModel {
let mut points: Vec<[f32; 3]> = pts.iter().map(|p| [p.x, p.y, p.z]).collect();
if pts.len() >= 2 {
let [w1, w2] = arrowhead_wings(pts[0], pts[1], 2.0);
let [w1, w2] = arrowhead_wings(pts[0], pts[1], 2.5);
points.push([f32::NAN; 3]);
points.push([w1.x, w1.y, w1.z]);
points.push([pts[0].x, pts[0].y, pts[0].z]);
+20 -10
View File
@@ -102,25 +102,35 @@ fn build_mleader(text: &str, verts: &[Vec3]) -> MultiLeader {
ml.arrowhead_size = 2.5;
ml.dogleg_length = 2.5;
// Direction: from last leader pt toward content
if let (Some(last_leader), Some(root)) =
(leader_pts.last(), ml.context.leader_roots.first_mut())
{
let dx = (content_pt.x - last_leader.x) as f64;
let dy = (content_pt.y - last_leader.y) as f64;
let len = (dx * dx + dy * dy).sqrt().max(1e-9);
root.direction = Vector3::new(dx / len, dy / len, 0.0);
const DOGLEG: f64 = 2.5;
// Which side of the leader the text sits on, from the last leader
// segment's horizontal direction. The landing is always horizontal.
let last_leader = leader_pts.last().copied().unwrap_or(content_pt);
let to_right = (content_pt.x - last_leader.x) >= 0.0;
let sign = if to_right { 1.0 } else { -1.0 };
if let Some(root) = ml.context.leader_roots.first_mut() {
// Leader ends at the clicked point; a horizontal landing runs from
// there toward the text.
root.direction = Vector3::new(sign, 0.0, 0.0);
root.connection_point = content_v3;
root.landing_distance = 2.5;
root.landing_distance = DOGLEG;
}
// Seed the text one landing-length past the leader end, on the side the
// user dragged toward. The actual horizontal alignment and the landing are
// recomputed every frame from the text grip's position relative to the
// leader, so moving the arrow or the text re-mirrors the layout.
ml.context.text_location =
Vector3::new(content_v3.x + sign * DOGLEG, content_v3.y, content_v3.z);
ml
}
fn preview_wire(pts: &[Vec3]) -> WireModel {
let mut points: Vec<[f32; 3]> = pts.iter().map(|p| [p.x, p.y, p.z]).collect();
if pts.len() >= 2 {
let [w1, w2] = arrowhead_wings(pts[0], pts[1], 2.0);
let [w1, w2] = arrowhead_wings(pts[0], pts[1], 2.5);
points.push([f32::NAN; 3]);
points.push([w1.x, w1.y, w1.z]);
points.push([pts[0].x, pts[0].y, pts[0].z]);