1pub use crate::frame::glyph_buffer::MAX_ZOOM;
7
8pub fn view(scene_px: (u32, u32), clip_px: (u32, u32), centre: (f32, f32), zoom: u8) -> (f32, f32) {
18 let zoom = f32::from(zoom.clamp(1, MAX_ZOOM));
19 #[allow(clippy::cast_precision_loss)] let axis = |scene: u32, clip: u32, centre: f32| {
21 let shown = clip as f32 / zoom;
22 let room = scene as f32 - shown;
25 let (first, last) = (room.min(0.0) / 2.0, room.max(room / 2.0));
26 let centre = if centre.is_finite() { centre } else { 0.0 };
27 (centre - shown / 2.0).clamp(first, last)
28 };
29 (
30 axis(scene_px.0, clip_px.0, centre.0),
31 axis(scene_px.1, clip_px.1, centre.1),
32 )
33}
34
35#[cfg(test)]
36mod tests {
37 use proptest::prelude::*;
38
39 use super::*;
40
41 const SCENE: (u32, u32) = (224, 128);
43 const CLIP: (u32, u32) = (384, 224);
44
45 #[test]
46 fn centres_the_window_on_the_point() {
47 assert_eq!(view(SCENE, CLIP, (112.0, 64.0), 4), (64.0, 36.0));
49 assert_eq!(view(SCENE, CLIP, (100.5, 40.25), 4), (52.5, 12.25));
50 assert_eq!(view(SCENE, CLIP, (112.0, 64.0), 2), (16.0, 8.0));
52 assert_eq!(view(SCENE, CLIP, (60.0, 90.0), 4), (12.0, 62.0));
54 }
55
56 #[test]
57 fn stops_at_every_edge() {
58 assert_eq!(view(SCENE, CLIP, (0.0, 0.0), 4), (0.0, 0.0));
59 assert_eq!(view(SCENE, CLIP, (47.0, 27.0), 4), (0.0, 0.0));
60 assert_eq!(view(SCENE, CLIP, (49.0, 29.0), 4), (1.0, 1.0));
61 assert_eq!(view(SCENE, CLIP, (-500.0, 64.0), 4), (0.0, 36.0));
62 assert_eq!(view(SCENE, CLIP, (112.0, -500.0), 4), (64.0, 0.0));
63 assert_eq!(view(SCENE, CLIP, (224.0, 128.0), 4), (128.0, 72.0));
65 assert_eq!(view(SCENE, CLIP, (9e9, 64.0), 4), (128.0, 36.0));
66 assert_eq!(view(SCENE, CLIP, (112.0, 9e9), 4), (64.0, 72.0));
67 assert_eq!(view(SCENE, CLIP, (175.0, 99.0), 4), (127.0, 71.0));
68 assert_eq!(
69 view(SCENE, CLIP, (f32::INFINITY, f32::NEG_INFINITY), 4),
70 (0.0, 0.0)
71 );
72 assert_eq!(view(SCENE, CLIP, (f32::NAN, 64.0), 4), (0.0, 36.0));
73 }
74
75 #[test]
76 fn centres_a_scene_smaller_than_the_window() {
77 assert_eq!(view(SCENE, CLIP, (0.0, 0.0), 1), (-80.0, -48.0));
79 assert_eq!(view(SCENE, CLIP, (999.0, 999.0), 1), (-80.0, -48.0));
80 let (x, y) = view((100, 128), CLIP, (50.0, 128.0), 3);
82 assert!((x + 14.0).abs() < 1e-4, "{x}");
83 assert!((y - (128.0 - 224.0 / 3.0)).abs() < 1e-4, "{y}");
84 assert_eq!(view((192, 112), CLIP, (150.0, 100.0), 2), (0.0, 0.0));
86 assert_eq!(view((0, 0), (0, 0), (5.0, 5.0), 2), (0.0, 0.0));
88 assert_eq!(view((0, 0), CLIP, (5.0, 5.0), 1), (-192.0, -112.0));
89 }
90
91 #[test]
92 fn a_zoom_outside_the_steps_is_clamped() {
93 let at = |zoom| view(SCENE, CLIP, (112.0, 64.0), zoom);
94 assert_eq!(at(0), at(1));
95 assert_eq!(at(5), at(4));
96 assert_eq!(at(u8::MAX), at(4));
97 assert_ne!(at(3), at(4));
98 }
99
100 proptest! {
101 #[test]
102 fn the_window_stays_inside_a_scene_at_least_its_size(
103 scene_w in 1u32..4000, scene_h in 1u32..2000,
104 clip_w in 1u32..801, clip_h in 1u32..513,
105 cx in prop_oneof![-5000f32..5000.0, any::<f32>()],
106 cy in prop_oneof![-5000f32..5000.0, any::<f32>()],
107 zoom in 1u8..=MAX_ZOOM,
108 ) {
109 let (x, y) = view((scene_w, scene_h), (clip_w, clip_h), (cx, cy), zoom);
110 let check = |origin: f32, scene: u32, clip: u32| {
111 #[allow(clippy::cast_precision_loss)]
112 let (scene, shown) = (scene as f32, clip as f32 / f32::from(zoom));
113 if scene >= shown {
114 assert!(origin >= 0.0, "{origin}");
115 assert!(origin + shown <= scene + 1e-3, "{origin} + {shown} > {scene}");
116 } else {
117 assert!((origin * 2.0 - (scene - shown)).abs() < 1e-3);
119 }
120 };
121 check(x, scene_w, clip_w);
122 check(y, scene_h, clip_h);
123 }
124
125 #[test]
126 fn the_point_is_in_the_middle_when_there_is_room(
127 cx in 48f32..176.0, cy in 28f32..100.0,
128 ) {
129 let (x, y) = view(SCENE, CLIP, (cx, cy), 4);
130 prop_assert!((x + 48.0 - cx).abs() < 1e-3);
131 prop_assert!((y + 28.0 - cy).abs() < 1e-3);
132 }
133 }
134}