mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-25 19:36:49 -06:00
110 lines
4.9 KiB
Rust
110 lines
4.9 KiB
Rust
//! ZOOM inside a layout viewport (MSPACE) works on that viewport, as in AutoCAD: Extents and
|
||
//! Window fit the viewport, nX keeps its centre, nXP sets its scale (#370).
|
||
|
||
use cadcraft_doc::{EntityKind, Viewport};
|
||
use cadcraft_engine::Session;
|
||
use serde_json::json;
|
||
|
||
fn viewport(s: &Session, h: &str) -> Viewport {
|
||
let d = s.doc().unwrap();
|
||
let e = d.layout("Layout1").unwrap().entities.iter().find(|e| e.handle.hex() == h).unwrap();
|
||
match &e.kind {
|
||
EntityKind::Viewport(v) => v.clone(),
|
||
_ => panic!("not a viewport"),
|
||
}
|
||
}
|
||
|
||
/// A 1000 × 500 model and a 100 × 80 viewport on Layout1, entered with MSPACE.
|
||
fn session() -> (Session, String) {
|
||
let mut s = Session::new();
|
||
s.new_drawing(true);
|
||
s.viewport_px = (1600.0, 900.0);
|
||
s.execute("line", &json!({"points": [[0, 0], [1000, 0]]})).unwrap();
|
||
s.execute("line", &json!({"points": [[0, 0], [0, 500]]})).unwrap();
|
||
s.execute("layout.set", &json!({"name": "Layout1"})).unwrap();
|
||
let r = s.execute("mview", &json!({"p1": [20, 20], "p2": [120, 100]})).unwrap();
|
||
let h = r["viewports"][0].as_str().unwrap().to_string();
|
||
s.execute("viewport.set", &json!({"handle": h, "scale": 1, "center": [0, 0]})).unwrap();
|
||
s.execute("mspace", &json!({"handle": h})).unwrap();
|
||
(s, h)
|
||
}
|
||
|
||
#[test]
|
||
fn zoom_inside_a_viewport_acts_on_the_viewport() {
|
||
let (mut s, h) = session();
|
||
let paper = s.state().unwrap().paper_view();
|
||
|
||
// Extents fit the viewport (aspect 1.25): 1000 wide needs a view height of 800.
|
||
s.cmdline("zoom e").unwrap();
|
||
let v = viewport(&s, &h);
|
||
assert!(v.view_center.near(cadcraft_geom::Vec2::new(500.0, 250.0), 1e-6), "{:?}", v.view_center);
|
||
assert!(v.view_height >= 800.0 && v.view_height < 900.0, "{}", v.view_height);
|
||
|
||
// nX zooms about the viewport centre.
|
||
s.cmdline("zoom 2x").unwrap();
|
||
let v2 = viewport(&s, &h);
|
||
assert!(v2.view_center.near(v.view_center, 1e-6), "{:?}", v2.view_center);
|
||
assert!((v2.view_height - v.view_height / 2.0).abs() < 1e-9);
|
||
|
||
// nXP sets the scale (paper units per model unit), whatever the zoom before.
|
||
for (t, scale) in [("zoom 0.1xp", 0.1), ("zoom 1/50xp", 0.02), ("zoom 1/50xp", 0.02)] {
|
||
s.cmdline(t).unwrap();
|
||
let v = viewport(&s, &h);
|
||
assert!((v.height / v.view_height - scale).abs() < 1e-12, "{t}: {}", v.height / v.view_height);
|
||
assert!(v.view_center.near(cadcraft_geom::Vec2::new(500.0, 250.0), 1e-6), "{t}: {:?}", v.view_center);
|
||
}
|
||
|
||
// A window fills the viewport: 200 × 100 at aspect 1.25 → height 160.
|
||
s.cmdline("zoom w 400,200 600,300").unwrap();
|
||
let v = viewport(&s, &h);
|
||
assert!(v.view_center.near(cadcraft_geom::Vec2::new(500.0, 250.0), 1e-6), "{:?}", v.view_center);
|
||
assert!((v.view_height - 160.0).abs() < 1e-9, "{}", v.view_height);
|
||
|
||
// The JSON form agrees, and the sheet itself never moves.
|
||
s.execute("zoom", &json!({"mode": "scale", "factor": 0.25, "xp": true})).unwrap();
|
||
assert!((viewport(&s, &h).view_height - 320.0).abs() < 1e-9);
|
||
assert_eq!(s.state().unwrap().paper_view(), paper);
|
||
}
|
||
|
||
#[test]
|
||
fn a_view_too_large_for_the_viewport_is_refused_and_keeps_the_viewport() {
|
||
// A small paper view makes a finite but huge request map to an infinite model view (#714).
|
||
let (mut s, h) = session();
|
||
s.execute("pspace", &json!({})).unwrap();
|
||
s.execute("view.set", &json!({"center": [70, 60], "height": 0.5})).unwrap();
|
||
s.execute("mspace", &json!({"handle": h})).unwrap();
|
||
let before = viewport(&s, &h);
|
||
assert!(s.execute("view.set", &json!({"center": [0, 0], "height": 1e308})).is_err());
|
||
let after = viewport(&s, &h);
|
||
assert_eq!(after, before);
|
||
assert!(after.view_center.is_finite() && after.view_height.is_finite());
|
||
// A representable view still applies.
|
||
s.execute("view.set", &json!({"center": [0, 0], "height": 1})).unwrap();
|
||
let v = viewport(&s, &h);
|
||
assert!(v.view_height.is_finite() && v.view_height != before.view_height, "{}", v.view_height);
|
||
}
|
||
|
||
#[test]
|
||
fn zoom_in_a_locked_viewport_or_on_the_model_tab_is_unchanged() {
|
||
// A locked viewport keeps its view: ZOOM moves the sheet instead.
|
||
let (mut s, h) = session();
|
||
s.execute("viewport.set", &json!({"handle": h, "locked": true})).unwrap();
|
||
let before = viewport(&s, &h);
|
||
let paper = s.state().unwrap().paper_view();
|
||
s.cmdline("zoom e").unwrap();
|
||
s.cmdline("zoom 0.1xp").unwrap();
|
||
assert_eq!(viewport(&s, &h), before);
|
||
assert_ne!(s.state().unwrap().paper_view(), paper);
|
||
|
||
// The Model tab still fits the drawing window; nXP there acts like nX.
|
||
let mut s = Session::new();
|
||
s.new_drawing(true);
|
||
s.viewport_px = (1600.0, 900.0);
|
||
s.execute("line", &json!({"points": [[0, 0], [1000, 0]]})).unwrap();
|
||
s.cmdline("zoom e").unwrap();
|
||
let v = s.state().unwrap().view();
|
||
assert!((v.height - 1000.0 / (1600.0 / 900.0) * 1.05).abs() < 1e-6, "{}", v.height);
|
||
s.cmdline("zoom 2xp").unwrap();
|
||
assert!((s.state().unwrap().view().height - v.height / 2.0).abs() < 1e-9);
|
||
}
|