From d365b60704c1a7eec989fe760917e01bdd0c9fe3 Mon Sep 17 00:00:00 2001 From: aldozar Date: Mon, 12 Oct 2026 01:21:18 -0600 Subject: [PATCH] MIRROR and PASTE previews on the GPU; GPU previews in layouts too (#655) Squash of PR #655 by @aldozar. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/edit.rs | 6 +++++ crates/engine/src/cmd/modify.rs | 46 ++++++++++++++++++++++++++++++--- crates/engine/src/prompt.rs | 5 +++- crates/engine/src/tests.rs | 27 ++++++++++++++++--- crates/ui-egui/src/canvas.rs | 31 ++++++++++++++++------ 5 files changed, 99 insertions(+), 16 deletions(-) diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs index dfef8a75..822ef876 100644 --- a/crates/engine/src/cmd/edit.rs +++ b/crates/engine/src/cmd/edit.rs @@ -609,6 +609,12 @@ impl Interactive for PasteM { _ => Ok(Step::Continue), } } + fn drag(&self, s: &Session, c: Vec2) -> Option { + if self.mode == PasteMode::Orig || s.clipboard.is_empty() { + return None; + } + Some(crate::prompt::Drag { handles: Vec::new(), matrix: Mat3::translate(c - s.clipboard_base), extra: Vec::new(), clipboard: true }) + } fn preview(&self, s: &Session, c: Vec2) -> Vec { let m = Mat3::translate(c - s.clipboard_base); s.clipboard diff --git a/crates/engine/src/cmd/modify.rs b/crates/engine/src/cmd/modify.rs index 122365ed..e087fb22 100644 --- a/crates/engine/src/cmd/modify.rs +++ b/crates/engine/src/cmd/modify.rs @@ -1712,6 +1712,26 @@ impl SelectThen { } } +/// Objects whose text MIRROR keeps readable instead of mirroring it with the geometry. +fn keeps_reading_when_mirrored(k: &EntityKind) -> bool { + match k { + EntityKind::Insert(i) => !i.attribs.is_empty(), + k => matches!( + k, + EntityKind::Text(_) + | EntityKind::AttDef(_) + | EntityKind::MText(_) + | EntityKind::Dimension(_) + | EntityKind::MLeader(_) + | EntityKind::Table(_) + | EntityKind::Tolerance(_) + ), + } +} + +/// Text objects transformed for each frame of a MIRROR preview (on the CPU); more are not drawn. +const MAX_MIRROR_TEXT_PREVIEW: usize = 1000; + /// A finite length greater than zero (rejects 0, negatives, NaN and infinity). fn positive_length(v: Option) -> Option { v.filter(|f| f.is_finite() && *f > 1e-12) @@ -1927,8 +1947,7 @@ impl Interactive for SelectThen { out.push(line(base, c)); out } - fn drag(&self, _s: &Session, c: Vec2) -> Option { - // Mirror stays on `preview`: MIRRTEXT keeps text readable, which a plain matrix can't. + fn drag(&self, s: &Session, c: Vec2) -> Option { if (self.op == Op::Rotate && self.reference) || !self.sel.done { return None; } @@ -1938,9 +1957,30 @@ impl Interactive for SelectThen { Op::Rotate => Mat3::rotate_about(base, base.angle_to(c)), Op::Scale if !self.reference => Mat3::scale_about(base, base.dist(c).max(1e-9)), Op::Scale => Mat3::scale_about(base, self.ref_len.and_then(|r| positive_length(Some(base.dist(c) / r)))?), + Op::Mirror if !self.ask_erase => Mat3::mirror(base, c), _ => return None, }; - Some(crate::prompt::Drag { handles: self.objs.clone(), matrix, extra: vec![line(base, c)] }) + let mut extra = vec![line(base, c)]; + if self.op != Op::Mirror { + return Some(crate::prompt::Drag { handles: self.objs.clone(), matrix, extra, clipboard: false }); + } + // Mirrored text keeps reading left to right (MIRRTEXT 0), which the matrix alone can't + // draw: those objects are transformed one by one, the rest is mirrored on the GPU. + let d = s.doc().ok()?; + let mut handles = Vec::with_capacity(self.objs.len()); + let mut texts = 0; + for h in &self.objs { + let Some(e) = d.entity(*h) else { continue }; + if !keeps_reading_when_mirrored(&e.kind) { + handles.push(*h); + } else if texts < MAX_MIRROR_TEXT_PREVIEW { + texts += 1; + let mut k = e.kind.clone(); + k.transform(&matrix); + extra.push(k); + } + } + Some(crate::prompt::Drag { handles, matrix, extra, clipboard: false }) } } diff --git a/crates/engine/src/prompt.rs b/crates/engine/src/prompt.rs index a150fbba..180d8f9a 100644 --- a/crates/engine/src/prompt.rs +++ b/crates/engine/src/prompt.rs @@ -178,8 +178,11 @@ pub trait Interactive: Send { pub struct Drag { pub handles: Vec, pub matrix: Mat3, - /// Rubber-band geometry drawn as well (the base-to-cursor line). + /// Rubber-band geometry drawn as well (the base-to-cursor line), and dragged objects the + /// matrix can't draw (text MIRROR keeps readable), already transformed. pub extra: Vec, + /// The dragged objects are the clipboard's ([`Session::clipboard`]), not `handles`. + pub clipboard: bool, } /// Parse a typed point: `x,y[,z]`, `@dx,dy`, `@dist app.session.drag(c), _ => None, }; if let Some((drag, extra)) = drag { if let Ok(st) = app.session.state() { - draw_drag_gpu(&mut app.canvas, &painter, &xf, &st.doc, &drag, bg); + let serial = app.session.clipboard_source.as_ref().map_or(0, |c| c.serial); + draw_drag_gpu(&mut app.canvas, &painter, &xf, &st.doc, &drag, (&app.session.clipboard, serial), bg); let list = cadcraft_render::build_entities( &st.doc, extra.iter(),