mirror of
https://github.com/aphrody-labs/cadcraft.git
synced 2026-10-13 19:26:54 +00:00
MSPACE/PSPACE: work in model space through layout viewports
Double-click a viewport (or MS) to edit model space inside it; the screen view maps consistently so picks, snaps, grips and previews work in model units, and zoom/pan change the viewport's view (or the sheet when the viewport is locked). PS or a double-click outside returns to paper space. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8188c7ed0a
commit
c35fa82373
4 files changed
+230
-25
No files matched your search
@@ -67,9 +67,70 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
.menu(&["File", "Export to PDF..."])
|
||||
.params("{path? (else returns base64 `data`), layout?, paper?, landscape?, fit?, lineweights?}")
|
||||
.noundo(),
|
||||
CommandSpec::new("mspace", "Model Space (in viewport)", run_mspace)
|
||||
.alias(&["ms"])
|
||||
.params("{handle?: viewport, at?: [x,y] paper point inside a viewport} (default: the last active or first viewport)")
|
||||
.noundo(),
|
||||
CommandSpec::new("pspace", "Paper Space", run_pspace).alias(&["ps"]).noundo(),
|
||||
]
|
||||
}
|
||||
|
||||
// ---------- model space through viewports ----------
|
||||
|
||||
fn layout_viewports(s: &Session) -> Result<Vec<(Handle, Viewport)>> {
|
||||
let sp = s.layout_space();
|
||||
if sp == Space::Model {
|
||||
return Err(bad("mspace", "** Command not allowed in Model Tab **"));
|
||||
}
|
||||
let d = s.doc()?;
|
||||
Ok(d.space(&sp)
|
||||
.map(|st| {
|
||||
st.iter()
|
||||
.filter_map(|e| match &e.kind {
|
||||
EntityKind::Viewport(v) if v.id != 1 && v.height > 1e-12 && v.view_height > 1e-12 => Some((e.handle, v.clone())),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
fn run_mspace(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let vps = layout_viewports(s)?;
|
||||
let at = p.get("at").and_then(|v| Some(Vec2::new(v.get(0)?.as_f64()?, v.get(1)?.as_f64()?)));
|
||||
let want = p.get("handle").and_then(|v| v.as_str().and_then(Handle::parse_hex).or_else(|| v.as_u64().map(Handle)));
|
||||
let pick = if let Some(h) = want {
|
||||
vps.iter().find(|(x, _)| *x == h).map(|(h, _)| *h).ok_or_else(|| bad("mspace", "`handle` is not a viewport in this layout"))?
|
||||
} else if let Some(a) = at {
|
||||
vps.iter()
|
||||
.rev()
|
||||
.find(|(_, v)| (a.x - v.center.x).abs() <= v.width / 2.0 && (a.y - v.center.y).abs() <= v.height / 2.0)
|
||||
.map(|(h, _)| *h)
|
||||
.ok_or_else(|| bad("mspace", "no viewport at that point"))?
|
||||
} else {
|
||||
let last = s.state()?.mspace;
|
||||
match last.filter(|h| vps.iter().any(|(x, _)| x == h)).or_else(|| vps.first().map(|(h, _)| *h)) {
|
||||
Some(h) => h,
|
||||
None => return Err(bad("mspace", "There are no active model space viewports.")),
|
||||
}
|
||||
};
|
||||
let st = s.state_mut()?;
|
||||
st.mspace = Some(pick);
|
||||
st.selection.clear();
|
||||
s.echo("MSPACE");
|
||||
Ok(json!({ "viewport": pick.hex() }))
|
||||
}
|
||||
|
||||
fn run_pspace(s: &mut Session, _p: &Value) -> Result<Value> {
|
||||
if s.layout_space() == Space::Model {
|
||||
return Err(bad("pspace", "** Command not allowed in Model Tab **"));
|
||||
}
|
||||
let st = s.state_mut()?;
|
||||
st.mspace = None;
|
||||
st.selection.clear();
|
||||
Ok(json!({ "space": "paper" }))
|
||||
}
|
||||
|
||||
// ---------- layouts ----------
|
||||
|
||||
/// Characters AutoCAD does not allow in layout names.
|
||||
@@ -149,6 +210,7 @@ fn run_delete(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
st.views.retain(|(sp, _)| sp != &gone);
|
||||
if st.space == gone {
|
||||
st.space = Space::Model;
|
||||
st.mspace = None;
|
||||
st.selection.clear();
|
||||
}
|
||||
s.touch();
|
||||
@@ -156,7 +218,7 @@ fn run_delete(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
|
||||
fn run_rename(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let from = match (str_param(p, "from"), s.space()) {
|
||||
let from = match (str_param(p, "from"), s.layout_space()) {
|
||||
(Some(f), _) => f.to_string(),
|
||||
(None, Space::Paper(n)) => n,
|
||||
(None, Space::Model) => return Err(bad("layout.rename", "`from` is required in the Model tab")),
|
||||
@@ -207,7 +269,7 @@ fn run_list(s: &mut Session, _p: &Value) -> Result<Value> {
|
||||
let d = s.doc()?;
|
||||
let mut ls: Vec<&Layout> = d.layouts.iter().collect();
|
||||
ls.sort_by_key(|l| l.tab_order);
|
||||
let current = s.space();
|
||||
let current = s.layout_space();
|
||||
Ok(Value::Array(
|
||||
ls.iter()
|
||||
.map(|l| {
|
||||
@@ -268,7 +330,7 @@ fn target_layout(s: &Session, cmd: &str, p: &Value) -> Result<Space> {
|
||||
if let Some(n) = str_param(p, "layout") {
|
||||
return Ok(Space::Paper(layout_name(cmd, s.doc()?, n)?));
|
||||
}
|
||||
match s.space() {
|
||||
match s.layout_space() {
|
||||
Space::Model => Err(bad(cmd, "** Command not allowed in Model Tab ** (switch to a layout or pass `layout`)")),
|
||||
sp => Ok(sp),
|
||||
}
|
||||
@@ -884,4 +946,36 @@ mod tests {
|
||||
assert!(s.execute(c, &p).is_err(), "{c} {p}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mspace_edits_model_through_viewport() {
|
||||
let mut s = session_with_model();
|
||||
let before = s.doc().unwrap().model.len();
|
||||
s.execute("layout.new", &json!({"name": "L"})).unwrap();
|
||||
s.execute("layout.set", &json!({"name": "L"})).unwrap();
|
||||
assert!(s.execute("mspace", &json!({"at": [-1000, -1000]})).is_err());
|
||||
let vps = viewports(&s, "L");
|
||||
let (h, vp) = vps.iter().find(|(_, v)| v.id != 1).cloned().unwrap();
|
||||
s.execute("mspace", &json!({"at": [vp.center.x, vp.center.y]})).unwrap();
|
||||
assert_eq!(s.space(), Space::Model);
|
||||
assert_eq!(s.layout_space(), Space::Paper("L".into()));
|
||||
// The screen view in MSPACE maps the viewport centre to its model view centre.
|
||||
let st = s.state().unwrap();
|
||||
let (pv, v) = (st.paper_view(), st.view());
|
||||
let k = vp.view_height / vp.height;
|
||||
assert!((v.height - pv.height * k).abs() < 1e-9);
|
||||
// New geometry goes to model space.
|
||||
s.cmdline("circle 10,10 3").unwrap();
|
||||
assert_eq!(s.doc().unwrap().model.len(), before + 1);
|
||||
// Zooming changes the viewport's model view, not the sheet.
|
||||
s.state_mut().unwrap().set_view(crate::View { center: v.center, height: v.height / 2.0 });
|
||||
let vp2 = viewports(&s, "L").into_iter().find(|(x, _)| *x == h).unwrap().1;
|
||||
assert!((vp2.view_height - vp.view_height / 2.0).abs() < 1e-9);
|
||||
assert_eq!(s.state().unwrap().paper_view(), pv);
|
||||
s.execute("pspace", &json!({})).unwrap();
|
||||
assert_eq!(s.space(), Space::Paper("L".into()));
|
||||
s.execute("mspace", &json!({})).unwrap();
|
||||
s.execute("layout.set", &json!({"name": "Model"})).unwrap();
|
||||
assert!(s.state().unwrap().mspace.is_none());
|
||||
}
|
||||
}
|
||||
@@ -206,6 +206,7 @@ fn run_layout_set(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
};
|
||||
let st = s.state_mut()?;
|
||||
st.space = space.clone();
|
||||
st.mspace = None;
|
||||
st.selection.clear();
|
||||
if let Space::Paper(n) = &space
|
||||
&& !st.views.iter().any(|(sp, _)| sp == &space)
|
||||
|
||||
@@ -89,6 +89,8 @@ pub struct DocState {
|
||||
pub saved: Arc<Drawing>,
|
||||
pub revision: u64,
|
||||
pub space: Space,
|
||||
/// MSPACE: the layout viewport whose model space is being edited (None = paper space).
|
||||
pub mspace: Option<Handle>,
|
||||
pub views: Vec<(Space, View)>,
|
||||
pub view_history: Vec<View>,
|
||||
pub uid: u64,
|
||||
@@ -110,6 +112,7 @@ impl DocState {
|
||||
title: title.into(),
|
||||
revision: 1,
|
||||
space: Space::Model,
|
||||
mspace: None,
|
||||
views: Vec::new(),
|
||||
view_history: Vec::new(),
|
||||
uid: NEXT_UID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
|
||||
@@ -118,9 +121,67 @@ impl DocState {
|
||||
pub fn is_dirty(&self) -> bool {
|
||||
!Arc::ptr_eq(&self.doc, &self.saved)
|
||||
}
|
||||
pub fn view(&self) -> View {
|
||||
/// The space edits, picks and snaps act on: model space inside an active viewport.
|
||||
pub fn edit_space(&self) -> Space {
|
||||
if self.active_viewport().is_some() { Space::Model } else { self.space.clone() }
|
||||
}
|
||||
/// The active MSPACE viewport, if it still exists in the current layout.
|
||||
pub fn active_viewport(&self) -> Option<(Handle, cadcraft_doc::Viewport)> {
|
||||
let h = self.mspace?;
|
||||
let e = self.doc.space(&self.space)?.get(h)?;
|
||||
match &e.kind {
|
||||
cadcraft_doc::EntityKind::Viewport(v) if v.height > 1e-12 && v.view_height > 1e-12 => Some((h, v.clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn paper_view(&self) -> View {
|
||||
self.views.iter().find(|(s, _)| *s == self.space).map(|(_, v)| *v).unwrap_or_default()
|
||||
}
|
||||
/// The screen view. Inside an MSPACE viewport this is the model-space view that maps the
|
||||
/// screen consistently with the paper view, so picks, snaps and previews work in model units.
|
||||
pub fn view(&self) -> View {
|
||||
let pv = self.paper_view();
|
||||
match self.active_viewport() {
|
||||
Some((_, vp)) => {
|
||||
let k = vp.view_height / vp.height;
|
||||
View { center: vp.view_center + (pv.center - vp.center.xy()) * k, height: pv.height * k }
|
||||
}
|
||||
None => pv,
|
||||
}
|
||||
}
|
||||
/// Map a requested screen view to the paper view, or (in MSPACE) to the viewport's model view.
|
||||
fn store_view(&mut self, v: View) {
|
||||
if let Some((h, vp)) = self.active_viewport() {
|
||||
let pv = self.paper_view();
|
||||
let k = v.height / pv.height.max(1e-12);
|
||||
if vp.locked {
|
||||
// A locked viewport keeps its scale: pan/zoom the sheet instead.
|
||||
let k0 = vp.view_height / vp.height;
|
||||
let pc = vp.center.xy() + (v.center - vp.view_center) / k0;
|
||||
self.put_paper_view(View { center: pc, height: v.height / k0 });
|
||||
} else {
|
||||
let center = v.center - (pv.center - vp.center.xy()) * k;
|
||||
let height = vp.height * k;
|
||||
let doc = Arc::make_mut(&mut self.doc);
|
||||
if let Some(store) = doc.space_mut(&self.space.clone()) {
|
||||
store.modify(h, |e| {
|
||||
if let cadcraft_doc::EntityKind::Viewport(x) = &mut e.kind {
|
||||
x.view_center = center;
|
||||
x.view_height = height;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
self.put_paper_view(v);
|
||||
}
|
||||
fn put_paper_view(&mut self, v: View) {
|
||||
match self.views.iter_mut().find(|(s, _)| *s == self.space) {
|
||||
Some((_, slot)) => *slot = v,
|
||||
None => self.views.push((self.space.clone(), v)),
|
||||
}
|
||||
}
|
||||
pub fn set_view(&mut self, v: View) {
|
||||
if !(v.center.is_finite() && v.height.is_finite() && v.height > 1e-12) {
|
||||
return;
|
||||
@@ -132,20 +193,14 @@ impl DocState {
|
||||
self.view_history.remove(0);
|
||||
}
|
||||
}
|
||||
match self.views.iter_mut().find(|(s, _)| *s == self.space) {
|
||||
Some((_, slot)) => *slot = v,
|
||||
None => self.views.push((self.space.clone(), v)),
|
||||
}
|
||||
self.store_view(v);
|
||||
}
|
||||
/// Set without recording view history (realtime pan/zoom frames).
|
||||
pub fn set_view_quiet(&mut self, v: View) {
|
||||
if !(v.center.is_finite() && v.height.is_finite() && v.height > 1e-12) {
|
||||
return;
|
||||
}
|
||||
match self.views.iter_mut().find(|(s, _)| *s == self.space) {
|
||||
Some((_, slot)) => *slot = v,
|
||||
None => self.views.push((self.space.clone(), v)),
|
||||
}
|
||||
self.store_view(v);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,7 +371,12 @@ impl Session {
|
||||
pub fn doc_mut(&mut self) -> Result<&mut Drawing> {
|
||||
Ok(Arc::make_mut(&mut self.state_mut()?.doc))
|
||||
}
|
||||
/// The space commands act on (model space while working inside a layout viewport).
|
||||
pub fn space(&self) -> Space {
|
||||
self.state().map(|s| s.edit_space()).unwrap_or_default()
|
||||
}
|
||||
/// The tab being displayed: Model or a layout, regardless of MSPACE.
|
||||
pub fn layout_space(&self) -> Space {
|
||||
self.state().map(|s| s.space.clone()).unwrap_or_default()
|
||||
}
|
||||
pub fn selection(&self) -> Vec<Handle> {
|
||||
|
||||
@@ -78,7 +78,7 @@ pub fn apply_hot_grip(app: &mut CadApp, to: Vec2) {
|
||||
#[derive(Default)]
|
||||
pub struct CanvasState {
|
||||
pub list: Option<DisplayList>,
|
||||
key: (u64, u64, i32, bool),
|
||||
key: (u64, u64, i32, bool, usize),
|
||||
/// Set when the app runs on wgpu: entities are drawn by [`crate::gpu`]; otherwise on the CPU.
|
||||
pub gpu: Option<crate::gpu::GpuTarget>,
|
||||
/// The mesh last handed to the GPU (see [`crate::gpu::CanvasCallback::key`]).
|
||||
@@ -114,7 +114,7 @@ fn ensure_list(app: &mut CadApp, px: f64) {
|
||||
let Ok(st) = app.session.state() else { return };
|
||||
// Rebuild when the drawing changes or the zoom moves by more than 2x (tessellation band).
|
||||
let band = px.max(1e-300).log2().floor() as i32;
|
||||
let key = (st.uid, st.revision, band, app.session.settings.lwdisplay);
|
||||
let key = (st.uid, st.revision, band, app.session.settings.lwdisplay, std::sync::Arc::as_ptr(&st.doc) as usize);
|
||||
if app.canvas.list.is_some() && app.canvas.key == key {
|
||||
return;
|
||||
}
|
||||
@@ -424,7 +424,7 @@ fn effective_point(app: &mut CadApp, raw: Vec2, xf: &Xf) -> Vec2 {
|
||||
}
|
||||
let ap = s.aperture / xf.scale;
|
||||
if let Ok(st) = app.session.state()
|
||||
&& let Some(hit) = snap::osnap(&st.doc, &st.space, raw, ap, s.osmode, base)
|
||||
&& let Some(hit) = snap::osnap(&st.doc, &st.edit_space(), raw, ap, s.osmode, base)
|
||||
{
|
||||
app.canvas.snap = Some(hit);
|
||||
return hit.point;
|
||||
@@ -464,7 +464,7 @@ pub fn show(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
app.canvas.xf = Some(xf);
|
||||
|
||||
// ---------- input ----------
|
||||
let (hover_pos, scroll, mods, middle_down, pressed_primary, pressed_secondary, dbl_middle) = ui.input(|i| {
|
||||
let (hover_pos, scroll, mods, middle_down, pressed_primary, pressed_secondary, dbl_middle, dbl_primary) = ui.input(|i| {
|
||||
(
|
||||
i.pointer.hover_pos(),
|
||||
i.smooth_scroll_delta.y,
|
||||
@@ -473,6 +473,7 @@ pub fn show(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
i.pointer.primary_clicked(),
|
||||
i.pointer.secondary_clicked(),
|
||||
i.pointer.button_double_clicked(egui::PointerButton::Middle),
|
||||
i.pointer.button_double_clicked(egui::PointerButton::Primary),
|
||||
)
|
||||
});
|
||||
let inside = hover_pos.is_some_and(|p| rect.contains(p)) && resp.hovered();
|
||||
@@ -516,6 +517,33 @@ pub fn show(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
let scale = f64::from(rect.height()) / view.height.max(1e-12);
|
||||
let xf = Xf { rect, center: view.center, scale };
|
||||
app.canvas.xf = Some(xf);
|
||||
// The paper (display) transform; equal to `xf` except inside an MSPACE viewport.
|
||||
let (xf_paper, active_vp) = match app.session.state() {
|
||||
Ok(st) => {
|
||||
let pv = st.paper_view();
|
||||
let xp = Xf { rect, center: pv.center, scale: f64::from(rect.height()) / pv.height.max(1e-12) };
|
||||
(if st.mspace.is_some() { xp } else { xf }, st.active_viewport().map(|(_, v)| v))
|
||||
}
|
||||
Err(_) => (xf, None),
|
||||
};
|
||||
// Double-click a viewport to work inside it; double-click the sheet outside it to return.
|
||||
if inside
|
||||
&& dbl_primary
|
||||
&& app.session.running.is_none()
|
||||
&& matches!(app.session.layout_space(), cadcraft_doc::Space::Paper(_))
|
||||
&& let Some(hp) = hover_pos
|
||||
{
|
||||
let pp = xf_paper.to_world(hp);
|
||||
let in_active = active_vp.as_ref().is_some_and(|v| (pp.x - v.center.x).abs() <= v.width / 2.0 && (pp.y - v.center.y).abs() <= v.height / 2.0);
|
||||
if !in_active {
|
||||
app.canvas.hot_grip = None;
|
||||
if app.run("mspace", serde_json::json!({"at": [pp.x, pp.y]})).is_err() && active_vp.is_some() {
|
||||
let _ = app.run("pspace", serde_json::json!({}));
|
||||
}
|
||||
app.canvas.list = None;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let raw_world = hover_pos.filter(|_| inside).map(|p| xf.to_world(p));
|
||||
if let Some(w) = raw_world {
|
||||
@@ -565,7 +593,7 @@ pub fn show(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
if hover_pos != app.canvas.hover_at
|
||||
&& let (Some(w), Ok(st)) = (raw_world, app.session.state())
|
||||
{
|
||||
app.canvas.hover = cadcraft_engine::select::pick(&st.doc, &st.space, w, app.session.settings.pickbox.max(1.0) * 1.5 / scale);
|
||||
app.canvas.hover = cadcraft_engine::select::pick(&st.doc, &st.edit_space(), w, app.session.settings.pickbox.max(1.0) * 1.5 / scale);
|
||||
app.canvas.hover_at = hover_pos;
|
||||
}
|
||||
} else {
|
||||
@@ -576,20 +604,20 @@ pub fn show(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
// ---------- paint ----------
|
||||
painter.rect_filled(rect, 0.0, t.canvas);
|
||||
let t0 = crate::now_ms();
|
||||
ensure_list(app, 1.0 / scale);
|
||||
ensure_list(app, 1.0 / xf_paper.scale);
|
||||
let sheet = app.canvas.list.as_ref().and_then(|l| l.sheet);
|
||||
let bg = match sheet {
|
||||
Some(sh) => {
|
||||
// Paper space: grey surround, the sheet with a shadow, the printable area dashed.
|
||||
painter.rect_filled(rect, 0.0, Color32::from_rgb(0x50, 0x57, 0x63));
|
||||
let a = xf.to_screen(Vec2::new(0.0, sh.size.y));
|
||||
let b = xf.to_screen(Vec2::new(sh.size.x, 0.0));
|
||||
let a = xf_paper.to_screen(Vec2::new(0.0, sh.size.y));
|
||||
let b = xf_paper.to_screen(Vec2::new(sh.size.x, 0.0));
|
||||
let paper = Rect::from_two_pos(a, b);
|
||||
painter.rect_filled(paper.translate(vec2(5.0, 5.0)), 0.0, Color32::from_black_alpha(110));
|
||||
painter.rect_filled(paper, 0.0, Color32::WHITE);
|
||||
let pa = Rect::from_two_pos(
|
||||
xf.to_screen(Vec2::new(sh.printable.min.x, sh.printable.max.y)),
|
||||
xf.to_screen(Vec2::new(sh.printable.max.x, sh.printable.min.y)),
|
||||
xf_paper.to_screen(Vec2::new(sh.printable.min.x, sh.printable.max.y)),
|
||||
xf_paper.to_screen(Vec2::new(sh.printable.max.x, sh.printable.min.y)),
|
||||
);
|
||||
let pts = [pa.left_top(), pa.right_top(), pa.right_bottom(), pa.left_bottom(), pa.left_top()];
|
||||
painter.extend(Shape::dashed_line(&pts, Stroke::new(1.0, Color32::from_gray(150)), 4.0, 4.0));
|
||||
@@ -602,11 +630,33 @@ pub fn show(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
};
|
||||
let sel = app.session.selection();
|
||||
if app.canvas.gpu.is_some() {
|
||||
draw_list_gpu(&mut app.canvas, &painter, &xf, bg, app.session.settings.lwdisplay);
|
||||
draw_list_gpu(&mut app.canvas, &painter, &xf_paper, bg, app.session.settings.lwdisplay);
|
||||
} else if let Some(list) = &app.canvas.list {
|
||||
draw_list(&painter, &xf, list, bg, app.session.settings.lwdisplay);
|
||||
draw_list(&painter, &xf_paper, list, bg, app.session.settings.lwdisplay);
|
||||
}
|
||||
if let Some(list) = &app.canvas.list {
|
||||
if let Some(vp) = &active_vp {
|
||||
// The active viewport: a heavy border, highlights in model units clipped to it.
|
||||
let r = Rect::from_two_pos(
|
||||
xf_paper.to_screen(Vec2::new(vp.center.x - vp.width / 2.0, vp.center.y + vp.height / 2.0)),
|
||||
xf_paper.to_screen(Vec2::new(vp.center.x + vp.width / 2.0, vp.center.y - vp.height / 2.0)),
|
||||
);
|
||||
painter.rect_stroke(r, 0.0, Stroke::new(3.0, Color32::from_rgb(0x2a, 0x2a, 0x2a)), egui::StrokeKind::Middle);
|
||||
if let Ok(st) = app.session.state() {
|
||||
let mut hs = sel.clone();
|
||||
hs.extend(app.canvas.hover.filter(|h| !sel.contains(h)));
|
||||
let ents: Vec<_> = hs.iter().filter_map(|h| st.doc.model.get(*h)).map(|e| e.as_ref()).collect();
|
||||
if !ents.is_empty() {
|
||||
let list = cadcraft_render::build_entities(&st.doc, ents, &cadcraft_render::Options { tolerance: 0.5 / scale, ..Default::default() });
|
||||
let clipped = painter.with_clip_rect(r.intersect(rect));
|
||||
if let Some(h) = app.canvas.hover
|
||||
&& !sel.contains(&h)
|
||||
{
|
||||
draw_highlight(&clipped, &xf, &list, &[h], t.hover, 2.0, false);
|
||||
}
|
||||
draw_highlight(&clipped, &xf, &list, &sel, t.selection, 1.5, true);
|
||||
}
|
||||
}
|
||||
} else if let Some(list) = &app.canvas.list {
|
||||
if let Some(h) = app.canvas.hover
|
||||
&& !sel.contains(&h)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user